root/lib/common/xml.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__tracking_xml_changes
  2. insert_prefix
  3. set_parent_flag
  4. pcmk__set_xml_doc_flag
  5. mark_xml_node_dirty
  6. reset_xml_node_flags
  7. pcmk__mark_xml_created
  8. pcmk__mark_xml_attr_dirty
  9. free_deleted_object
  10. reset_xml_private_data
  11. free_private_data
  12. new_private_data
  13. xml_track_changes
  14. xml_tracking_changes
  15. xml_document_dirty
  16. pcmk__xml_position
  17. marked_as_deleted
  18. accept_attr_deletions
  19. pcmk__xml_match
  20. xml_log_changes
  21. xml_accept_changes
  22. find_xml_node
  23. pcmk__xe_match
  24. copy_in_properties
  25. fix_plus_plus_recursive
  26. expand_plus_plus
  27. pcmk__xe_remove_matching_attrs
  28. getDocPtr
  29. add_node_copy
  30. add_node_nocopy
  31. create_xml_node
  32. pcmk_create_xml_text_node
  33. pcmk_create_html_node
  34. pcmk_free_xml_subtree
  35. free_xml_with_position
  36. free_xml
  37. copy_xml
  38. log_xmllib_err
  39. string2xml
  40. stdin2xml
  41. decompress_file
  42. pcmk__strip_xml_text
  43. filename2xml
  44. pcmk__xe_add_last_written
  45. crm_xml_sanitize_id
  46. crm_xml_set_id
  47. write_xml_stream
  48. write_xml_fd
  49. write_xml_file
  50. replace_text
  51. crm_xml_escape
  52. dump_xml_attr
  53. log_xml_node_and_children
  54. pcmk__xml_log
  55. log_xml_changes
  56. log_data_element
  57. dump_filtered_xml
  58. dump_xml_element
  59. dump_xml_text
  60. dump_xml_cdata
  61. dump_xml_comment
  62. pcmk__xml2text
  63. dump_xml_formatted_with_text
  64. dump_xml_formatted
  65. dump_xml_unformatted
  66. xml_has_children
  67. xml_remove_prop
  68. save_xml_to_file
  69. set_attrs_flag
  70. mark_attr_deleted
  71. mark_attr_changed
  72. mark_attr_moved
  73. xml_diff_old_attrs
  74. mark_created_attrs
  75. xml_diff_attrs
  76. mark_child_deleted
  77. mark_child_moved
  78. mark_xml_changes
  79. xml_calculate_significant_changes
  80. xml_calculate_changes
  81. can_prune_leaf
  82. pcmk__xc_match
  83. pcmk__xc_update
  84. pcmk__xml_update
  85. update_xml_child
  86. find_xml_children
  87. replace_xml_child
  88. sorted_xml
  89. first_named_child
  90. crm_next_same_xml
  91. crm_xml_init
  92. crm_xml_cleanup
  93. expand_idref
  94. pcmk__xml_artefact_root
  95. pcmk__xml_artefact_path
  96. pcmk__xe_set_propv
  97. pcmk__xe_set_props
  98. pcmk__xe_foreach_child
  99. find_entity
  100. crm_destroy_xml

   1 /*
   2  * Copyright 2004-2022 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 <stdio.h>
  13 #include <sys/types.h>
  14 #include <unistd.h>
  15 #include <time.h>
  16 #include <string.h>
  17 #include <stdlib.h>
  18 #include <stdarg.h>
  19 #include <bzlib.h>
  20 
  21 #include <libxml/parser.h>
  22 #include <libxml/tree.h>
  23 #include <libxml/xmlIO.h>  /* xmlAllocOutputBuffer */
  24 
  25 #include <crm/crm.h>
  26 #include <crm/msg_xml.h>
  27 #include <crm/common/xml.h>
  28 #include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
  29 #include "crmcommon_private.h"
  30 
  31 // Define this as 1 in development to get insanely verbose trace messages
  32 #ifndef XML_PARSER_DEBUG
  33 #define XML_PARSER_DEBUG 0
  34 #endif
  35 
  36 
  37 /* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
  38  * by libxml2, which is potentially ambiguous and dangerous. We should drop it
  39  * when we can break backward compatibility with configurations that might be
  40  * relying on it (i.e. pacemaker 3.0.0).
  41  *
  42  * It might be a good idea to have a transitional period where we first try
  43  * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
  44  * it, logging a warning if it succeeds.
  45  */
  46 #define PCMK__XML_PARSE_OPTS    (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
  47 
  48 bool
  49 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
     /* [previous][next][first][last][top][bottom][index][help] */
  50 {
  51     if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
  52         return FALSE;
  53     } else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  54                             pcmk__xf_tracking)) {
  55         return FALSE;
  56     } else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  57                                     pcmk__xf_lazy)) {
  58         return FALSE;
  59     }
  60     return TRUE;
  61 }
  62 
  63 /*!
  64  * \internal
  65  * \brief Append spaces to a buffer
  66  *
  67  * Spaces are appended only if the \p xml_log_option_formatted flag is set.
  68  *
  69  * \param[in]     options  Group of \p xml_log_options flags
  70  * \param[in,out] buffer   Where to append the spaces (must not be \p NULL)
  71  * \param[in]     depth    Current indentation level
  72  */
  73 static inline void
  74 insert_prefix(int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
  75 {
  76     int spaces = 0;
  77 
  78     if (!pcmk_is_set(options, xml_log_option_formatted)) {
  79         return;
  80     }
  81     CRM_ASSERT(buffer != NULL);
  82     CRM_CHECK(depth >= 0, depth = 0);
  83 
  84     /* With -O2, this loop is faster than one g_string_append_printf() call with
  85      * width == 2 * depth
  86      */
  87     spaces = 2 * depth;
  88     for (int lpc = 0; lpc < spaces; lpc++) {
  89         g_string_append_c(buffer, ' ');
  90     }
  91 }
  92 
  93 static inline void
  94 set_parent_flag(xmlNode *xml, long flag) 
     /* [previous][next][first][last][top][bottom][index][help] */
  95 {
  96     for(; xml; xml = xml->parent) {
  97         xml_node_private_t *nodepriv = xml->_private;
  98 
  99         if (nodepriv == NULL) {
 100             /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
 101         } else {
 102             pcmk__set_xml_flags(nodepriv, flag);
 103         }
 104     }
 105 }
 106 
 107 void
 108 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
 109 {
 110     if(xml && xml->doc && xml->doc->_private){
 111         /* During calls to xmlDocCopyNode(), xml->doc may be unset */
 112         xml_doc_private_t *docpriv = xml->doc->_private;
 113 
 114         pcmk__set_xml_flags(docpriv, flag);
 115     }
 116 }
 117 
 118 // Mark document, element, and all element's parents as changed
 119 static inline void
 120 mark_xml_node_dirty(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 121 {
 122     pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
 123     set_parent_flag(xml, pcmk__xf_dirty);
 124 }
 125 
 126 // Clear flags on XML node and its children
 127 static void
 128 reset_xml_node_flags(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 129 {
 130     xmlNode *cIter = NULL;
 131     xml_node_private_t *nodepriv = xml->_private;
 132 
 133     if (nodepriv) {
 134         nodepriv->flags = 0;
 135     }
 136 
 137     for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
 138          cIter = pcmk__xml_next(cIter)) {
 139         reset_xml_node_flags(cIter);
 140     }
 141 }
 142 
 143 // Set xpf_created flag on XML node and any children
 144 void
 145 pcmk__mark_xml_created(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 146 {
 147     xmlNode *cIter = NULL;
 148     xml_node_private_t *nodepriv = xml->_private;
 149 
 150     if (nodepriv && pcmk__tracking_xml_changes(xml, FALSE)) {
 151         if (!pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 152             pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
 153             mark_xml_node_dirty(xml);
 154         }
 155         for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
 156              cIter = pcmk__xml_next(cIter)) {
 157             pcmk__mark_xml_created(cIter);
 158         }
 159     }
 160 }
 161 
 162 void
 163 pcmk__mark_xml_attr_dirty(xmlAttr *a) 
     /* [previous][next][first][last][top][bottom][index][help] */
 164 {
 165     xmlNode *parent = a->parent;
 166     xml_node_private_t *nodepriv = a->_private;
 167 
 168     pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_modified);
 169     pcmk__clear_xml_flags(nodepriv, pcmk__xf_deleted);
 170     mark_xml_node_dirty(parent);
 171 }
 172 
 173 #define XML_DOC_PRIVATE_MAGIC   0x81726354UL
 174 #define XML_NODE_PRIVATE_MAGIC  0x54637281UL
 175 
 176 // Free an XML object previously marked as deleted
 177 static void
 178 free_deleted_object(void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 179 {
 180     if(data) {
 181         pcmk__deleted_xml_t *deleted_obj = data;
 182 
 183         free(deleted_obj->path);
 184         free(deleted_obj);
 185     }
 186 }
 187 
 188 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
 189 static void
 190 reset_xml_private_data(xml_doc_private_t *docpriv)
     /* [previous][next][first][last][top][bottom][index][help] */
 191 {
 192     if (docpriv != NULL) {
 193         CRM_ASSERT(docpriv->check == XML_DOC_PRIVATE_MAGIC);
 194 
 195         free(docpriv->user);
 196         docpriv->user = NULL;
 197 
 198         if (docpriv->acls != NULL) {
 199             pcmk__free_acls(docpriv->acls);
 200             docpriv->acls = NULL;
 201         }
 202 
 203         if(docpriv->deleted_objs) {
 204             g_list_free_full(docpriv->deleted_objs, free_deleted_object);
 205             docpriv->deleted_objs = NULL;
 206         }
 207     }
 208 }
 209 
 210 // Free all private data associated with an XML node
 211 static void
 212 free_private_data(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 213 {
 214     /* Note:
 215     
 216     This function frees private data assosciated with an XML node,
 217     unless the function is being called as a result of internal
 218     XSLT cleanup.
 219     
 220     That could happen through, for example, the following chain of
 221     function calls:
 222     
 223        xsltApplyStylesheetInternal
 224     -> xsltFreeTransformContext
 225     -> xsltFreeRVTs
 226     -> xmlFreeDoc
 227 
 228     And in that case, the node would fulfill three conditions:
 229     
 230     1. It would be a standalone document (i.e. it wouldn't be 
 231        part of a document)
 232     2. It would have a space-prefixed name (for reference, please
 233        see xsltInternals.h: XSLT_MARK_RES_TREE_FRAG)
 234     3. It would carry its own payload in the _private field.
 235     
 236     We do not free data in this circumstance to avoid a failed
 237     assertion on the XML_*_PRIVATE_MAGIC later.
 238     
 239     */
 240     if (node->name == NULL || node->name[0] != ' ') {
 241         if (node->_private) {
 242             if (node->type == XML_DOCUMENT_NODE) {
 243                 reset_xml_private_data(node->_private);
 244             } else {
 245                 CRM_ASSERT(((xml_node_private_t *) node->_private)->check
 246                                == XML_NODE_PRIVATE_MAGIC);
 247                 /* nothing dynamically allocated nested */
 248             }
 249             free(node->_private);
 250             node->_private = NULL;
 251         }
 252     }
 253 }
 254 
 255 // Allocate and initialize private data for an XML node
 256 static void
 257 new_private_data(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259     switch (node->type) {
 260         case XML_DOCUMENT_NODE: {
 261             xml_doc_private_t *docpriv = NULL;
 262             docpriv = calloc(1, sizeof(xml_doc_private_t));
 263             CRM_ASSERT(docpriv != NULL);
 264             docpriv->check = XML_DOC_PRIVATE_MAGIC;
 265             /* Flags will be reset if necessary when tracking is enabled */
 266             pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
 267             node->_private = docpriv;
 268             break;
 269         }
 270         case XML_ELEMENT_NODE:
 271         case XML_ATTRIBUTE_NODE:
 272         case XML_COMMENT_NODE: {
 273             xml_node_private_t *nodepriv = NULL;
 274             nodepriv = calloc(1, sizeof(xml_node_private_t));
 275             CRM_ASSERT(nodepriv != NULL);
 276             nodepriv->check = XML_NODE_PRIVATE_MAGIC;
 277             /* Flags will be reset if necessary when tracking is enabled */
 278             pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
 279             node->_private = nodepriv;
 280             if (pcmk__tracking_xml_changes(node, FALSE)) {
 281                 /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
 282                  * not hooked up at the point we are called
 283                  */
 284                 mark_xml_node_dirty(node);
 285             }
 286             break;
 287         }
 288         case XML_TEXT_NODE:
 289         case XML_DTD_NODE:
 290         case XML_CDATA_SECTION_NODE:
 291             break;
 292         default:
 293             /* Ignore */
 294             crm_trace("Ignoring %p %d", node, node->type);
 295             CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
 296             break;
 297     }
 298 }
 299 
 300 void
 301 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) 
     /* [previous][next][first][last][top][bottom][index][help] */
 302 {
 303     xml_accept_changes(xml);
 304     crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
 305     pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
 306     if(enforce_acls) {
 307         if(acl_source == NULL) {
 308             acl_source = xml;
 309         }
 310         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
 311         pcmk__unpack_acl(acl_source, xml, user);
 312         pcmk__apply_acl(xml);
 313     }
 314 }
 315 
 316 bool xml_tracking_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 317 {
 318     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 319            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 320                           pcmk__xf_tracking);
 321 }
 322 
 323 bool xml_document_dirty(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 324 {
 325     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 326            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 327                           pcmk__xf_dirty);
 328 }
 329 
 330 /*!
 331  * \internal
 332  * \brief Return ordinal position of an XML node among its siblings
 333  *
 334  * \param[in] xml            XML node to check
 335  * \param[in] ignore_if_set  Don't count siblings with this flag set
 336  *
 337  * \return Ordinal position of \p xml (starting with 0)
 338  */
 339 int
 340 pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 341 {
 342     int position = 0;
 343 
 344     for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
 345         xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
 346 
 347         if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
 348             position++;
 349         }
 350     }
 351 
 352     return position;
 353 }
 354 
 355 // This also clears attribute's flags if not marked as deleted
 356 static bool
 357 marked_as_deleted(xmlAttrPtr a, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 358 {
 359     xml_node_private_t *nodepriv = a->_private;
 360 
 361     if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 362         return true;
 363     }
 364     nodepriv->flags = pcmk__xf_none;
 365     return false;
 366 }
 367 
 368 // Remove all attributes marked as deleted from an XML node
 369 static void
 370 accept_attr_deletions(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 371 {
 372     // Clear XML node's flags
 373     ((xml_node_private_t *) xml->_private)->flags = pcmk__xf_none;
 374 
 375     // Remove this XML node's attributes that were marked as deleted
 376     pcmk__xe_remove_matching_attrs(xml, marked_as_deleted, NULL);
 377 
 378     // Recursively do the same for this XML node's children
 379     for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL;
 380          cIter = pcmk__xml_next(cIter)) {
 381         accept_attr_deletions(cIter);
 382     }
 383 }
 384 
 385 /*!
 386  * \internal
 387  * \brief Find first child XML node matching another given XML node
 388  *
 389  * \param[in] haystack  XML whose children should be checked
 390  * \param[in] needle    XML to match (comment content or element name and ID)
 391  * \param[in] exact     If true and needle is a comment, position must match
 392  */
 393 xmlNode *
 394 pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
 395 {
 396     CRM_CHECK(needle != NULL, return NULL);
 397 
 398     if (needle->type == XML_COMMENT_NODE) {
 399         return pcmk__xc_match(haystack, needle, exact);
 400 
 401     } else {
 402         const char *id = ID(needle);
 403         const char *attr = (id == NULL)? NULL : XML_ATTR_ID;
 404 
 405         return pcmk__xe_match(haystack, crm_element_name(needle), attr, id);
 406     }
 407 }
 408 
 409 void
 410 xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 411 {
 412     GList *gIter = NULL;
 413     xml_doc_private_t *docpriv = NULL;
 414 
 415     if (log_level == LOG_NEVER) {
 416         return;
 417     }
 418 
 419     CRM_ASSERT(xml);
 420     CRM_ASSERT(xml->doc);
 421 
 422     docpriv = xml->doc->_private;
 423     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 424         return;
 425     }
 426 
 427     for(gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
 428         pcmk__deleted_xml_t *deleted_obj = gIter->data;
 429 
 430         if (deleted_obj->position >= 0) {
 431             do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)",
 432                              deleted_obj->path, deleted_obj->position);
 433 
 434         } else {
 435             do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s",
 436                              deleted_obj->path);
 437         }
 438     }
 439 
 440     log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
 441                      xml_log_option_formatted|xml_log_option_dirty_add);
 442 }
 443 
 444 void
 445 xml_accept_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 446 {
 447     xmlNode *top = NULL;
 448     xml_doc_private_t *docpriv = NULL;
 449 
 450     if(xml == NULL) {
 451         return;
 452     }
 453 
 454     crm_trace("Accepting changes to %p", xml);
 455     docpriv = xml->doc->_private;
 456     top = xmlDocGetRootElement(xml->doc);
 457 
 458     reset_xml_private_data(xml->doc->_private);
 459 
 460     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 461         docpriv->flags = pcmk__xf_none;
 462         return;
 463     }
 464 
 465     docpriv->flags = pcmk__xf_none;
 466     accept_attr_deletions(top);
 467 }
 468 
 469 xmlNode *
 470 find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
     /* [previous][next][first][last][top][bottom][index][help] */
 471 {
 472     xmlNode *a_child = NULL;
 473     const char *name = "NULL";
 474 
 475     if (root != NULL) {
 476         name = crm_element_name(root);
 477     }
 478 
 479     if (search_path == NULL) {
 480         crm_warn("Will never find <NULL>");
 481         return NULL;
 482     }
 483 
 484     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
 485          a_child = pcmk__xml_next(a_child)) {
 486         if (strcmp((const char *)a_child->name, search_path) == 0) {
 487 /*              crm_trace("returning node (%s).", crm_element_name(a_child)); */
 488             return a_child;
 489         }
 490     }
 491 
 492     if (must_find) {
 493         crm_warn("Could not find %s in %s.", search_path, name);
 494     } else if (root != NULL) {
 495         crm_trace("Could not find %s in %s.", search_path, name);
 496     } else {
 497         crm_trace("Could not find %s in <NULL>.", search_path);
 498     }
 499 
 500     return NULL;
 501 }
 502 
 503 #define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \
 504                                            (v), pcmk__str_none)
 505 
 506 /*!
 507  * \internal
 508  * \brief Find first XML child element matching given criteria
 509  *
 510  * \param[in] parent     XML element to search
 511  * \param[in] node_name  If not NULL, only match children of this type
 512  * \param[in] attr_n     If not NULL, only match children with an attribute
 513  *                       of this name and a value of \p attr_v
 514  * \param[in] attr_v     If \p attr_n and this are not NULL, only match children
 515  *                       with an attribute named \p attr_n and this value
 516  *
 517  * \return Matching XML child element, or NULL if none found
 518  */
 519 xmlNode *
 520 pcmk__xe_match(const xmlNode *parent, const char *node_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 521                const char *attr_n, const char *attr_v)
 522 {
 523     /* ensure attr_v specified when attr_n is */
 524     CRM_CHECK(attr_n == NULL || attr_v != NULL, return NULL);
 525 
 526     for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
 527          child = pcmk__xml_next(child)) {
 528         if (pcmk__str_eq(node_name, (const char *) (child->name),
 529                          pcmk__str_null_matches)
 530             && ((attr_n == NULL) || attr_matches(child, attr_n, attr_v))) {
 531             return child;
 532         }
 533     }
 534     crm_trace("XML child node <%s%s%s%s%s> not found in %s",
 535               (node_name? node_name : "(any)"),
 536               (attr_n? " " : ""),
 537               (attr_n? attr_n : ""),
 538               (attr_n? "=" : ""),
 539               (attr_n? attr_v : ""),
 540               crm_element_name(parent));
 541     return NULL;
 542 }
 543 
 544 void
 545 copy_in_properties(xmlNode * target, xmlNode * src)
     /* [previous][next][first][last][top][bottom][index][help] */
 546 {
 547     if (src == NULL) {
 548         crm_warn("No node to copy properties from");
 549 
 550     } else if (target == NULL) {
 551         crm_err("No node to copy properties into");
 552 
 553     } else {
 554         for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
 555             const char *p_name = (const char *) a->name;
 556             const char *p_value = pcmk__xml_attr_value(a);
 557 
 558             expand_plus_plus(target, p_name, p_value);
 559             if (xml_acl_denied(target)) {
 560                 crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
 561                 return;
 562             }
 563         }
 564     }
 565 
 566     return;
 567 }
 568 
 569 /*!
 570  * \brief Parse integer assignment statements on this node and all its child
 571  *        nodes
 572  *
 573  * \param[in,out] target  Root XML node to be processed
 574  *
 575  * \note This function is recursive
 576  */
 577 void
 578 fix_plus_plus_recursive(xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 579 {
 580     /* TODO: Remove recursion and use xpath searches for value++ */
 581     xmlNode *child = NULL;
 582 
 583     for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
 584         const char *p_name = (const char *) a->name;
 585         const char *p_value = pcmk__xml_attr_value(a);
 586 
 587         expand_plus_plus(target, p_name, p_value);
 588     }
 589     for (child = pcmk__xml_first_child(target); child != NULL;
 590          child = pcmk__xml_next(child)) {
 591         fix_plus_plus_recursive(child);
 592     }
 593 }
 594 
 595 /*!
 596  * \brief Update current XML attribute value per parsed integer assignment
 597           statement
 598  *
 599  * \param[in,out]   target  an XML node, containing a XML attribute that is
 600  *                          initialized to some numeric value, to be processed
 601  * \param[in]       name    name of the XML attribute, e.g. X, whose value
 602  *                          should be updated
 603  * \param[in]       value   assignment statement, e.g. "X++" or
 604  *                          "X+=5", to be applied to the initialized value.
 605  *
 606  * \note The original XML attribute value is treated as 0 if non-numeric and
 607  *       truncated to be an integer if decimal-point-containing.
 608  * \note The final XML attribute value is truncated to not exceed 1000000.
 609  * \note Undefined behavior if unexpected input.
 610  */
 611 void
 612 expand_plus_plus(xmlNode * target, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 613 {
 614     int offset = 1;
 615     int name_len = 0;
 616     int int_value = 0;
 617     int value_len = 0;
 618 
 619     const char *old_value = NULL;
 620 
 621     if (target == NULL || value == NULL || name == NULL) {
 622         return;
 623     }
 624 
 625     old_value = crm_element_value(target, name);
 626 
 627     if (old_value == NULL) {
 628         /* if no previous value, set unexpanded */
 629         goto set_unexpanded;
 630 
 631     } else if (strstr(value, name) != value) {
 632         goto set_unexpanded;
 633     }
 634 
 635     name_len = strlen(name);
 636     value_len = strlen(value);
 637     if (value_len < (name_len + 2)
 638         || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
 639         goto set_unexpanded;
 640     }
 641 
 642     /* if we are expanding ourselves,
 643      * then no previous value was set and leave int_value as 0
 644      */
 645     if (old_value != value) {
 646         int_value = char2score(old_value);
 647     }
 648 
 649     if (value[name_len + 1] != '+') {
 650         const char *offset_s = value + (name_len + 2);
 651 
 652         offset = char2score(offset_s);
 653     }
 654     int_value += offset;
 655 
 656     if (int_value > INFINITY) {
 657         int_value = (int)INFINITY;
 658     }
 659 
 660     crm_xml_add_int(target, name, int_value);
 661     return;
 662 
 663   set_unexpanded:
 664     if (old_value == value) {
 665         /* the old value is already set, nothing to do */
 666         return;
 667     }
 668     crm_xml_add(target, name, value);
 669     return;
 670 }
 671 
 672 /*!
 673  * \internal
 674  * \brief Remove an XML element's attributes that match some criteria
 675  *
 676  * \param[in,out] element    XML element to modify
 677  * \param[in]     match      If not NULL, only remove attributes for which
 678  *                           this function returns true
 679  * \param[in,out] user_data  Data to pass to \p match
 680  */
 681 void
 682 pcmk__xe_remove_matching_attrs(xmlNode *element,
     /* [previous][next][first][last][top][bottom][index][help] */
 683                                bool (*match)(xmlAttrPtr, void *),
 684                                void *user_data)
 685 {
 686     xmlAttrPtr next = NULL;
 687 
 688     for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
 689         next = a->next; // Grab now because attribute might get removed
 690         if ((match == NULL) || match(a, user_data)) {
 691             if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
 692                 crm_trace("ACLs prevent removal of attributes (%s and "
 693                           "possibly others) from %s element",
 694                           (const char *) a->name, (const char *) element->name);
 695                 return; // ACLs apply to element, not particular attributes
 696             }
 697 
 698             if (pcmk__tracking_xml_changes(element, false)) {
 699                 // Leave (marked for removal) until after diff is calculated
 700                 set_parent_flag(element, pcmk__xf_dirty);
 701                 pcmk__set_xml_flags((xml_node_private_t *) a->_private,
 702                                     pcmk__xf_deleted);
 703             } else {
 704                 xmlRemoveProp(a);
 705             }
 706         }
 707     }
 708 }
 709 
 710 xmlDoc *
 711 getDocPtr(xmlNode * node)
     /* [previous][next][first][last][top][bottom][index][help] */
 712 {
 713     xmlDoc *doc = NULL;
 714 
 715     CRM_CHECK(node != NULL, return NULL);
 716 
 717     doc = node->doc;
 718     if (doc == NULL) {
 719         doc = xmlNewDoc((pcmkXmlStr) "1.0");
 720         xmlDocSetRootElement(doc, node);
 721         xmlSetTreeDoc(node, doc);
 722     }
 723     return doc;
 724 }
 725 
 726 xmlNode *
 727 add_node_copy(xmlNode * parent, xmlNode * src_node)
     /* [previous][next][first][last][top][bottom][index][help] */
 728 {
 729     xmlNode *child = NULL;
 730     xmlDoc *doc = getDocPtr(parent);
 731 
 732     CRM_CHECK(src_node != NULL, return NULL);
 733 
 734     child = xmlDocCopyNode(src_node, doc, 1);
 735     xmlAddChild(parent, child);
 736     pcmk__mark_xml_created(child);
 737     return child;
 738 }
 739 
 740 int
 741 add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
     /* [previous][next][first][last][top][bottom][index][help] */
 742 {
 743     add_node_copy(parent, child);
 744     free_xml(child);
 745     return 1;
 746 }
 747 
 748 xmlNode *
 749 create_xml_node(xmlNode * parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 750 {
 751     xmlDoc *doc = NULL;
 752     xmlNode *node = NULL;
 753 
 754     if (pcmk__str_empty(name)) {
 755         CRM_CHECK(name != NULL && name[0] == 0, return NULL);
 756         return NULL;
 757     }
 758 
 759     if (parent == NULL) {
 760         doc = xmlNewDoc((pcmkXmlStr) "1.0");
 761         node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
 762         xmlDocSetRootElement(doc, node);
 763 
 764     } else {
 765         doc = getDocPtr(parent);
 766         node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
 767         xmlAddChild(parent, node);
 768     }
 769     pcmk__mark_xml_created(node);
 770     return node;
 771 }
 772 
 773 xmlNode *
 774 pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
     /* [previous][next][first][last][top][bottom][index][help] */
 775 {
 776     xmlNode *node = create_xml_node(parent, name);
 777 
 778     if (node != NULL) {
 779         xmlNodeSetContent(node, (pcmkXmlStr) content);
 780     }
 781 
 782     return node;
 783 }
 784 
 785 xmlNode *
 786 pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 787                       const char *class_name, const char *text)
 788 {
 789     xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
 790 
 791     if (class_name != NULL) {
 792         crm_xml_add(node, "class", class_name);
 793     }
 794 
 795     if (id != NULL) {
 796         crm_xml_add(node, "id", id);
 797     }
 798 
 799     return node;
 800 }
 801 
 802 /*!
 803  * Free an XML element and all of its children, removing it from its parent
 804  *
 805  * \param[in,out] xml  XML element to free
 806  */
 807 void
 808 pcmk_free_xml_subtree(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 809 {
 810     xmlUnlinkNode(xml); // Detaches from parent and siblings
 811     xmlFreeNode(xml);   // Frees
 812 }
 813 
 814 static void
 815 free_xml_with_position(xmlNode * child, int position)
     /* [previous][next][first][last][top][bottom][index][help] */
 816 {
 817     if (child != NULL) {
 818         xmlNode *top = NULL;
 819         xmlDoc *doc = child->doc;
 820         xml_node_private_t *nodepriv = child->_private;
 821         xml_doc_private_t *docpriv = NULL;
 822 
 823         if (doc != NULL) {
 824             top = xmlDocGetRootElement(doc);
 825         }
 826 
 827         if (doc != NULL && top == child) {
 828             /* Free everything */
 829             xmlFreeDoc(doc);
 830 
 831         } else if (pcmk__check_acl(child, NULL, pcmk__xf_acl_write) == FALSE) {
 832             GString *xpath = NULL;
 833 
 834             pcmk__log_else(LOG_TRACE, return);
 835             xpath = pcmk__element_xpath(child);
 836             qb_log_from_external_source(__func__, __FILE__,
 837                                         "Cannot remove %s %x", LOG_TRACE,
 838                                         __LINE__, 0, (const char *) xpath->str,
 839                                         nodepriv->flags);
 840             g_string_free(xpath, TRUE);
 841             return;
 842 
 843         } else {
 844             if (doc && pcmk__tracking_xml_changes(child, FALSE)
 845                 && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 846 
 847                 GString *xpath = pcmk__element_xpath(child);
 848 
 849                 if (xpath != NULL) {
 850                     pcmk__deleted_xml_t *deleted_obj = NULL;
 851 
 852                     crm_trace("Deleting %s %p from %p",
 853                               (const char *) xpath->str, child, doc);
 854 
 855                     deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t));
 856                     deleted_obj->path = strdup((const char *) xpath->str);
 857 
 858                     CRM_ASSERT(deleted_obj->path != NULL);
 859                     g_string_free(xpath, TRUE);
 860 
 861                     deleted_obj->position = -1;
 862                     /* Record the "position" only for XML comments for now */
 863                     if (child->type == XML_COMMENT_NODE) {
 864                         if (position >= 0) {
 865                             deleted_obj->position = position;
 866 
 867                         } else {
 868                             deleted_obj->position = pcmk__xml_position(child,
 869                                                                        pcmk__xf_skip);
 870                         }
 871                     }
 872 
 873                     docpriv = doc->_private;
 874                     docpriv->deleted_objs = g_list_append(docpriv->deleted_objs, deleted_obj);
 875                     pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
 876                 }
 877             }
 878             pcmk_free_xml_subtree(child);
 879         }
 880     }
 881 }
 882 
 883 
 884 void
 885 free_xml(xmlNode * child)
     /* [previous][next][first][last][top][bottom][index][help] */
 886 {
 887     free_xml_with_position(child, -1);
 888 }
 889 
 890 xmlNode *
 891 copy_xml(xmlNode * src)
     /* [previous][next][first][last][top][bottom][index][help] */
 892 {
 893     xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
 894     xmlNode *copy = xmlDocCopyNode(src, doc, 1);
 895 
 896     xmlDocSetRootElement(doc, copy);
 897     xmlSetTreeDoc(copy, doc);
 898     return copy;
 899 }
 900 
 901 static void
 902 log_xmllib_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
 903 G_GNUC_PRINTF(2, 3);
 904 
 905 // Log an XML library error
 906 static void
 907 log_xmllib_err(void *ctx, const char *fmt, ...)
 908 {
 909     va_list ap;
 910     static struct qb_log_callsite *xml_error_cs = NULL;
 911 
 912     if (xml_error_cs == NULL) {
 913         xml_error_cs = qb_log_callsite_get(
 914             __func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
 915     }
 916 
 917     va_start(ap, fmt);
 918     if (xml_error_cs && xml_error_cs->targets) {
 919         PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
 920                            crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error",
 921                                      TRUE, TRUE),
 922                            "XML Error: ", fmt, ap);
 923     } else {
 924         PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
 925     }
 926     va_end(ap);
 927 }
 928 
 929 xmlNode *
 930 string2xml(const char *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 931 {
 932     xmlNode *xml = NULL;
 933     xmlDocPtr output = NULL;
 934     xmlParserCtxtPtr ctxt = NULL;
 935     xmlErrorPtr last_error = NULL;
 936 
 937     if (input == NULL) {
 938         crm_err("Can't parse NULL input");
 939         return NULL;
 940     }
 941 
 942     /* create a parser context */
 943     ctxt = xmlNewParserCtxt();
 944     CRM_CHECK(ctxt != NULL, return NULL);
 945 
 946     xmlCtxtResetLastError(ctxt);
 947     xmlSetGenericErrorFunc(ctxt, log_xmllib_err);
 948     output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
 949                             PCMK__XML_PARSE_OPTS);
 950     if (output) {
 951         xml = xmlDocGetRootElement(output);
 952     }
 953     last_error = xmlCtxtGetLastError(ctxt);
 954     if (last_error && last_error->code != XML_ERR_OK) {
 955         /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
 956         /*
 957          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
 958          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
 959          */
 960         crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
 961                  last_error->domain, last_error->level, last_error->code, last_error->message);
 962 
 963         if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
 964             CRM_LOG_ASSERT("Cannot parse an empty string");
 965 
 966         } else if (last_error->code != XML_ERR_DOCUMENT_END) {
 967             crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
 968                     input);
 969             if (xml != NULL) {
 970                 crm_log_xml_err(xml, "Partial");
 971             }
 972 
 973         } else {
 974             int len = strlen(input);
 975             int lpc = 0;
 976 
 977             while(lpc < len) {
 978                 crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
 979                 lpc += 80;
 980             }
 981 
 982             CRM_LOG_ASSERT("String parsing error");
 983         }
 984     }
 985 
 986     xmlFreeParserCtxt(ctxt);
 987     return xml;
 988 }
 989 
 990 xmlNode *
 991 stdin2xml(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 992 {
 993     size_t data_length = 0;
 994     size_t read_chars = 0;
 995 
 996     char *xml_buffer = NULL;
 997     xmlNode *xml_obj = NULL;
 998 
 999     do {
1000         xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE);
1001         read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE,
1002                            stdin);
1003         data_length += read_chars;
1004     } while (read_chars == PCMK__BUFFER_SIZE);
1005 
1006     if (data_length == 0) {
1007         crm_warn("No XML supplied on stdin");
1008         free(xml_buffer);
1009         return NULL;
1010     }
1011 
1012     xml_buffer[data_length] = '\0';
1013     xml_obj = string2xml(xml_buffer);
1014     free(xml_buffer);
1015 
1016     crm_log_xml_trace(xml_obj, "Created fragment");
1017     return xml_obj;
1018 }
1019 
1020 static char *
1021 decompress_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1022 {
1023     char *buffer = NULL;
1024     int rc = 0;
1025     size_t length = 0, read_len = 0;
1026     BZFILE *bz_file = NULL;
1027     FILE *input = fopen(filename, "r");
1028 
1029     if (input == NULL) {
1030         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
1031         return NULL;
1032     }
1033 
1034     bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
1035     if (rc != BZ_OK) {
1036         crm_err("Could not prepare to read compressed %s: %s "
1037                 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1038         BZ2_bzReadClose(&rc, bz_file);
1039         fclose(input);
1040         return NULL;
1041     }
1042 
1043     rc = BZ_OK;
1044     // cppcheck seems not to understand the abort-logic in pcmk__realloc
1045     // cppcheck-suppress memleak
1046     while (rc == BZ_OK) {
1047         buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
1048         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
1049 
1050         crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
1051 
1052         if (rc == BZ_OK || rc == BZ_STREAM_END) {
1053             length += read_len;
1054         }
1055     }
1056 
1057     buffer[length] = '\0';
1058 
1059     if (rc != BZ_STREAM_END) {
1060         crm_err("Could not read compressed %s: %s "
1061                 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1062         free(buffer);
1063         buffer = NULL;
1064     }
1065 
1066     BZ2_bzReadClose(&rc, bz_file);
1067     fclose(input);
1068     return buffer;
1069 }
1070 
1071 /*!
1072  * \internal
1073  * \brief Remove XML text nodes from specified XML and all its children
1074  *
1075  * \param[in,out] xml  XML to strip text from
1076  */
1077 void
1078 pcmk__strip_xml_text(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1079 {
1080     xmlNode *iter = xml->children;
1081 
1082     while (iter) {
1083         xmlNode *next = iter->next;
1084 
1085         switch (iter->type) {
1086             case XML_TEXT_NODE:
1087                 /* Remove it */
1088                 pcmk_free_xml_subtree(iter);
1089                 break;
1090 
1091             case XML_ELEMENT_NODE:
1092                 /* Search it */
1093                 pcmk__strip_xml_text(iter);
1094                 break;
1095 
1096             default:
1097                 /* Leave it */
1098                 break;
1099         }
1100 
1101         iter = next;
1102     }
1103 }
1104 
1105 xmlNode *
1106 filename2xml(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1107 {
1108     xmlNode *xml = NULL;
1109     xmlDocPtr output = NULL;
1110     bool uncompressed = true;
1111     xmlParserCtxtPtr ctxt = NULL;
1112     xmlErrorPtr last_error = NULL;
1113 
1114     /* create a parser context */
1115     ctxt = xmlNewParserCtxt();
1116     CRM_CHECK(ctxt != NULL, return NULL);
1117 
1118     xmlCtxtResetLastError(ctxt);
1119     xmlSetGenericErrorFunc(ctxt, log_xmllib_err);
1120 
1121     if (filename) {
1122         uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
1123     }
1124 
1125     if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
1126         /* STDIN_FILENO == fileno(stdin) */
1127         output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
1128                                PCMK__XML_PARSE_OPTS);
1129 
1130     } else if (uncompressed) {
1131         output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS);
1132 
1133     } else {
1134         char *input = decompress_file(filename);
1135 
1136         output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
1137                                 PCMK__XML_PARSE_OPTS);
1138         free(input);
1139     }
1140 
1141     if (output && (xml = xmlDocGetRootElement(output))) {
1142         pcmk__strip_xml_text(xml);
1143     }
1144 
1145     last_error = xmlCtxtGetLastError(ctxt);
1146     if (last_error && last_error->code != XML_ERR_OK) {
1147         /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
1148         /*
1149          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
1150          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
1151          */
1152         crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
1153                 last_error->domain, last_error->level, last_error->code, last_error->message);
1154 
1155         if (last_error && last_error->code != XML_ERR_OK) {
1156             crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
1157             if (xml != NULL) {
1158                 crm_log_xml_err(xml, "Partial");
1159             }
1160         }
1161     }
1162 
1163     xmlFreeParserCtxt(ctxt);
1164     return xml;
1165 }
1166 
1167 /*!
1168  * \internal
1169  * \brief Add a "last written" attribute to an XML element, set to current time
1170  *
1171  * \param[in,out] xe  XML element to add attribute to
1172  *
1173  * \return Value that was set, or NULL on error
1174  */
1175 const char *
1176 pcmk__xe_add_last_written(xmlNode *xe)
     /* [previous][next][first][last][top][bottom][index][help] */
1177 {
1178     const char *now_str = pcmk__epoch2str(NULL);
1179 
1180     return crm_xml_add(xe, XML_CIB_ATTR_WRITTEN,
1181                        now_str ? now_str : "Could not determine current time");
1182 }
1183 
1184 /*!
1185  * \brief Sanitize a string so it is usable as an XML ID
1186  *
1187  * \param[in,out] id  String to sanitize
1188  */
1189 void
1190 crm_xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
1191 {
1192     char *c;
1193 
1194     for (c = id; *c; ++c) {
1195         /* @TODO Sanitize more comprehensively */
1196         switch (*c) {
1197             case ':':
1198             case '#':
1199                 *c = '.';
1200         }
1201     }
1202 }
1203 
1204 /*!
1205  * \brief Set the ID of an XML element using a format
1206  *
1207  * \param[in,out] xml  XML element
1208  * \param[in]     fmt  printf-style format
1209  * \param[in]     ...  any arguments required by format
1210  */
1211 void
1212 crm_xml_set_id(xmlNode *xml, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
1213 {
1214     va_list ap;
1215     int len = 0;
1216     char *id = NULL;
1217 
1218     /* equivalent to crm_strdup_printf() */
1219     va_start(ap, format);
1220     len = vasprintf(&id, format, ap);
1221     va_end(ap);
1222     CRM_ASSERT(len > 0);
1223 
1224     crm_xml_sanitize_id(id);
1225     crm_xml_add(xml, XML_ATTR_ID, id);
1226     free(id);
1227 }
1228 
1229 /*!
1230  * \internal
1231  * \brief Write XML to a file stream
1232  *
1233  * \param[in]     xml_node  XML to write
1234  * \param[in]     filename  Name of file being written (for logging only)
1235  * \param[in,out] stream    Open file stream corresponding to filename
1236  * \param[in]     compress  Whether to compress XML before writing
1237  * \param[out]    nbytes    Number of bytes written
1238  *
1239  * \return Standard Pacemaker return code
1240  */
1241 static int
1242 write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream,
     /* [previous][next][first][last][top][bottom][index][help] */
1243                  bool compress, unsigned int *nbytes)
1244 {
1245     int rc = pcmk_rc_ok;
1246     char *buffer = NULL;
1247 
1248     *nbytes = 0;
1249     crm_log_xml_trace(xml_node, "writing");
1250 
1251     buffer = dump_xml_formatted(xml_node);
1252     CRM_CHECK(buffer && strlen(buffer),
1253               crm_log_xml_warn(xml_node, "formatting failed");
1254               rc = pcmk_rc_error;
1255               goto bail);
1256 
1257     if (compress) {
1258         unsigned int in = 0;
1259         BZFILE *bz_file = NULL;
1260 
1261         rc = BZ_OK;
1262         bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
1263         if (rc != BZ_OK) {
1264             crm_warn("Not compressing %s: could not prepare file stream: %s "
1265                      CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1266         } else {
1267             BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
1268             if (rc != BZ_OK) {
1269                 crm_warn("Not compressing %s: could not compress data: %s "
1270                          CRM_XS " bzerror=%d errno=%d",
1271                          filename, bz2_strerror(rc), rc, errno);
1272             }
1273         }
1274 
1275         if (rc == BZ_OK) {
1276             BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
1277             if (rc != BZ_OK) {
1278                 crm_warn("Not compressing %s: could not write compressed data: %s "
1279                          CRM_XS " bzerror=%d errno=%d",
1280                          filename, bz2_strerror(rc), rc, errno);
1281                 *nbytes = 0; // retry without compression
1282             } else {
1283                 crm_trace("Compressed XML for %s from %u bytes to %u",
1284                           filename, in, *nbytes);
1285             }
1286         }
1287         rc = pcmk_rc_ok; // Either true, or we'll retry without compression
1288     }
1289 
1290     if (*nbytes == 0) {
1291         rc = fprintf(stream, "%s", buffer);
1292         if (rc < 0) {
1293             rc = errno;
1294             crm_perror(LOG_ERR, "writing %s", filename);
1295         } else {
1296             *nbytes = (unsigned int) rc;
1297             rc = pcmk_rc_ok;
1298         }
1299     }
1300 
1301   bail:
1302 
1303     if (fflush(stream) != 0) {
1304         rc = errno;
1305         crm_perror(LOG_ERR, "flushing %s", filename);
1306     }
1307 
1308     /* Don't report error if the file does not support synchronization */
1309     if (fsync(fileno(stream)) < 0 && errno != EROFS  && errno != EINVAL) {
1310         rc = errno;
1311         crm_perror(LOG_ERR, "synchronizing %s", filename);
1312     }
1313 
1314     fclose(stream);
1315 
1316     crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
1317     free(buffer);
1318 
1319     return rc;
1320 }
1321 
1322 /*!
1323  * \brief Write XML to a file descriptor
1324  *
1325  * \param[in] xml_node  XML to write
1326  * \param[in] filename  Name of file being written (for logging only)
1327  * \param[in] fd        Open file descriptor corresponding to filename
1328  * \param[in] compress  Whether to compress XML before writing
1329  *
1330  * \return Number of bytes written on success, -errno otherwise
1331  */
1332 int
1333 write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
1334 {
1335     FILE *stream = NULL;
1336     unsigned int nbytes = 0;
1337     int rc = pcmk_rc_ok;
1338 
1339     CRM_CHECK(xml_node && (fd > 0), return -EINVAL);
1340     stream = fdopen(fd, "w");
1341     if (stream == NULL) {
1342         return -errno;
1343     }
1344     rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1345     if (rc != pcmk_rc_ok) {
1346         return pcmk_rc2legacy(rc);
1347     }
1348     return (int) nbytes;
1349 }
1350 
1351 /*!
1352  * \brief Write XML to a file
1353  *
1354  * \param[in] xml_node  XML to write
1355  * \param[in] filename  Name of file to write
1356  * \param[in] compress  Whether to compress XML before writing
1357  *
1358  * \return Number of bytes written on success, -errno otherwise
1359  */
1360 int
1361 write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
1362 {
1363     FILE *stream = NULL;
1364     unsigned int nbytes = 0;
1365     int rc = pcmk_rc_ok;
1366 
1367     CRM_CHECK(xml_node && filename, return -EINVAL);
1368     stream = fopen(filename, "w");
1369     if (stream == NULL) {
1370         return -errno;
1371     }
1372     rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1373     if (rc != pcmk_rc_ok) {
1374         return pcmk_rc2legacy(rc);
1375     }
1376     return (int) nbytes;
1377 }
1378 
1379 // Replace a portion of a dynamically allocated string (reallocating memory)
1380 static char *
1381 replace_text(char *text, int start, size_t *length, const char *replace)
     /* [previous][next][first][last][top][bottom][index][help] */
1382 {
1383     size_t offset = strlen(replace) - 1; // We have space for 1 char already
1384 
1385     *length += offset;
1386     text = pcmk__realloc(text, *length);
1387 
1388     for (size_t lpc = (*length) - 1; lpc > (start + offset); lpc--) {
1389         text[lpc] = text[lpc - offset];
1390     }
1391 
1392     memcpy(text + start, replace, offset + 1);
1393     return text;
1394 }
1395 
1396 /*!
1397  * \brief Replace special characters with their XML escape sequences
1398  *
1399  * \param[in] text  Text to escape
1400  *
1401  * \return Newly allocated string equivalent to \p text but with special
1402  *         characters replaced with XML escape sequences (or NULL if \p text
1403  *         is NULL)
1404  */
1405 char *
1406 crm_xml_escape(const char *text)
     /* [previous][next][first][last][top][bottom][index][help] */
1407 {
1408     size_t length;
1409     char *copy;
1410 
1411     /*
1412      * When xmlCtxtReadDoc() parses &lt; and friends in a
1413      * value, it converts them to their human readable
1414      * form.
1415      *
1416      * If one uses xmlNodeDump() to convert it back to a
1417      * string, all is well, because special characters are
1418      * converted back to their escape sequences.
1419      *
1420      * However xmlNodeDump() is randomly dog slow, even with the same
1421      * input. So we need to replicate the escaping in our custom
1422      * version so that the result can be re-parsed by xmlCtxtReadDoc()
1423      * when necessary.
1424      */
1425 
1426     if (text == NULL) {
1427         return NULL;
1428     }
1429 
1430     length = 1 + strlen(text);
1431     copy = strdup(text);
1432     CRM_ASSERT(copy != NULL);
1433     for (size_t index = 0; index < length; index++) {
1434         if(copy[index] & 0x80 && copy[index+1] & 0x80){
1435             index++;
1436             break;
1437         }
1438         switch (copy[index]) {
1439             case 0:
1440                 break;
1441             case '<':
1442                 copy = replace_text(copy, index, &length, "&lt;");
1443                 break;
1444             case '>':
1445                 copy = replace_text(copy, index, &length, "&gt;");
1446                 break;
1447             case '"':
1448                 copy = replace_text(copy, index, &length, "&quot;");
1449                 break;
1450             case '\'':
1451                 copy = replace_text(copy, index, &length, "&apos;");
1452                 break;
1453             case '&':
1454                 copy = replace_text(copy, index, &length, "&amp;");
1455                 break;
1456             case '\t':
1457                 /* Might as well just expand to a few spaces... */
1458                 copy = replace_text(copy, index, &length, "    ");
1459                 break;
1460             case '\n':
1461                 copy = replace_text(copy, index, &length, "\\n");
1462                 break;
1463             case '\r':
1464                 copy = replace_text(copy, index, &length, "\\r");
1465                 break;
1466             default:
1467                 /* Check for and replace non-printing characters with their octal equivalent */
1468                 if(copy[index] < ' ' || copy[index] > '~') {
1469                     char *replace = crm_strdup_printf("\\%.3o", copy[index]);
1470 
1471                     copy = replace_text(copy, index, &length, replace);
1472                     free(replace);
1473                 }
1474         }
1475     }
1476     return copy;
1477 }
1478 
1479 /* Keep this inline. De-inlining resulted in a 0.6% average slowdown in
1480  * crm_simulate on cts/scheduler/xml during testing.
1481  */
1482 
1483 /*!
1484  * \internal
1485  * \brief Append an XML attribute to a buffer
1486  *
1487  * \param[in]     attr     Attribute to append
1488  * \param[in]     options  Group of \p xml_log_options flags
1489  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1490  */
1491 static inline void
1492 dump_xml_attr(const xmlAttr *attr, int options, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
1493 {
1494     char *p_value = NULL;
1495     const char *p_name = NULL;
1496     xml_node_private_t *nodepriv = NULL;
1497 
1498     CRM_ASSERT(buffer != NULL);
1499     if (attr == NULL || attr->children == NULL) {
1500         return;
1501     }
1502 
1503     nodepriv = attr->_private;
1504     if (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
1505         return;
1506     }
1507 
1508     p_name = (const char *) attr->name;
1509     p_value = crm_xml_escape((const char *)attr->children->content);
1510     pcmk__g_strcat(buffer, " ", p_name, "=\"", pcmk__s(p_value, "<null>"), "\"",
1511                    NULL);
1512 
1513     free(p_value);
1514 }
1515 
1516 // Log an XML element or comment (and any children) in a formatted way
1517 static void
1518 log_xml_node_and_children(GString *buffer, int log_level, const char *file,
     /* [previous][next][first][last][top][bottom][index][help] */
1519                           const char *function, int line, const char *prefix,
1520                           const xmlNode *data, int depth, int options)
1521 {
1522     const char *name = NULL;
1523     const char *hidden = NULL;
1524 
1525     xmlNode *child = NULL;
1526 
1527     CRM_ASSERT(buffer != NULL);
1528 
1529     if ((data == NULL) || (log_level == LOG_NEVER)
1530         || ((data->type != XML_COMMENT_NODE)
1531             && (data->type != XML_ELEMENT_NODE))) {
1532         return;
1533     }
1534 
1535     name = crm_element_name(data);
1536 
1537     g_string_truncate(buffer, 0);
1538 
1539     if (pcmk_is_set(options, xml_log_option_open)) {
1540         insert_prefix(options, buffer, depth);
1541 
1542         if (data->type == XML_COMMENT_NODE) {
1543             pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->",
1544                            NULL);
1545 
1546         } else {
1547             pcmk__g_strcat(buffer, "<", name, NULL);
1548 
1549             hidden = crm_element_value(data, "hidden");
1550             for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL;
1551                  a = a->next) {
1552 
1553                 xml_node_private_t *nodepriv = a->_private;
1554                 const char *p_name = (const char *) a->name;
1555                 const char *p_value = pcmk__xml_attr_value(a);
1556                 char *p_copy = NULL;
1557 
1558                 if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
1559                     continue;
1560                 } else if (pcmk_any_flags_set(options,
1561                                               xml_log_option_diff_plus
1562                                               |xml_log_option_diff_minus)
1563                            && (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
1564                     continue;
1565 
1566                 } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
1567                     p_copy = strdup("*****");
1568 
1569                 } else {
1570                     p_copy = crm_xml_escape(p_value);
1571                 }
1572 
1573                 pcmk__g_strcat(buffer, " ", p_name, "=\"",
1574                                pcmk__s(p_copy, "<null>"), "\"", NULL);
1575                 free(p_copy);
1576             }
1577 
1578             if(xml_has_children(data) == FALSE) {
1579                 g_string_append(buffer, "/>");
1580 
1581             } else if (pcmk_is_set(options, xml_log_option_children)) {
1582                 g_string_append_c(buffer, '>');
1583 
1584             } else {
1585                 g_string_append(buffer, "/>");
1586             }
1587         }
1588 
1589         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix,
1590                          (const char *) buffer->str);
1591     }
1592 
1593     if(data->type == XML_COMMENT_NODE) {
1594         return;
1595 
1596     } else if(xml_has_children(data) == FALSE) {
1597         return;
1598 
1599     } else if (pcmk_is_set(options, xml_log_option_children)) {
1600         for (child = pcmk__xml_first_child(data); child != NULL;
1601              child = pcmk__xml_next(child)) {
1602 
1603             log_xml_node_and_children(buffer, log_level, file, function, line,
1604                                       prefix, child, depth + 1,
1605                                       options|xml_log_option_open
1606                                           |xml_log_option_close);
1607         }
1608     }
1609 
1610     if (pcmk_is_set(options, xml_log_option_close)) {
1611         g_string_truncate(buffer, 0);
1612 
1613         insert_prefix(options, buffer, depth);
1614         pcmk__g_strcat(buffer, "</", name, ">", NULL);
1615 
1616         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix,
1617                          (const char *) buffer->str);
1618     }
1619 }
1620 
1621 // Log an XML element or comment (and any children) in a formatted way
1622 void
1623 pcmk__xml_log(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
1624               const char *prefix, const xmlNode *data, int depth, int options)
1625 {
1626     /* Allocate a buffer once, for log_xml_node_and_children() to truncate and
1627      * reuse in recursive calls
1628      */
1629     GString *buffer = g_string_sized_new(1024);
1630 
1631     log_xml_node_and_children(buffer, log_level, file, function, line, prefix,
1632                               data, depth, options);
1633 
1634     g_string_free(buffer, TRUE);
1635 }
1636 
1637 // Log XML portions that have been marked as changed
1638 static void
1639 log_xml_changes(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
1640                 const char *prefix, const xmlNode *data, int depth, int options)
1641 {
1642     xml_node_private_t *nodepriv;
1643     char *prefix_m = NULL;
1644     xmlNode *child = NULL;
1645 
1646     if ((data == NULL) || (log_level == LOG_NEVER)) {
1647         return;
1648     }
1649 
1650     nodepriv = data->_private;
1651 
1652     prefix_m = strdup(prefix);
1653     prefix_m[1] = '+';
1654 
1655     if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
1656         /* Continue and log full subtree */
1657         pcmk__xml_log(log_level, file, function, line, prefix_m, data, depth,
1658                       options|xml_log_option_open|xml_log_option_close
1659                           |xml_log_option_children);
1660 
1661     } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
1662         int spaces = 0;
1663         char *prefix_del = NULL;
1664         char *prefix_moved = NULL;
1665         const char *flags = prefix;
1666 
1667         if (pcmk_is_set(options, xml_log_option_formatted)) {
1668             CRM_CHECK(depth >= 0, depth = 0);
1669             spaces = 2 * depth;
1670         }
1671 
1672         prefix_del = strdup(prefix);
1673         prefix_del[0] = '-';
1674         prefix_del[1] = '-';
1675         prefix_moved = strdup(prefix);
1676         prefix_moved[1] = '~';
1677 
1678         if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
1679             flags = prefix_moved;
1680         } else {
1681             flags = prefix;
1682         }
1683 
1684         pcmk__xml_log(log_level, file, function, line, flags, data, depth,
1685                       options|xml_log_option_open);
1686 
1687         for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1688             const char *aname = (const char*) a->name;
1689 
1690             nodepriv = a->_private;
1691             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
1692                 const char *value = crm_element_value(data, aname);
1693                 flags = prefix_del;
1694                 do_crm_log_alias(log_level, file, function, line,
1695                                  "%s %*s @%s=%s", flags, spaces, "", aname,
1696                                  value);
1697 
1698             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
1699                 const char *value = crm_element_value(data, aname);
1700 
1701                 if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
1702                     flags = prefix_m;
1703 
1704                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
1705                     flags = prefix;
1706 
1707                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
1708                     flags = prefix_moved;
1709 
1710                 } else {
1711                     flags = prefix;
1712                 }
1713                 do_crm_log_alias(log_level, file, function, line,
1714                                  "%s %*s @%s=%s", flags, spaces, "", aname,
1715                                  value);
1716             }
1717         }
1718         free(prefix_moved);
1719         free(prefix_del);
1720 
1721         for (child = pcmk__xml_first_child(data); child != NULL;
1722              child = pcmk__xml_next(child)) {
1723             log_xml_changes(log_level, file, function, line, prefix, child,
1724                             depth + 1, options);
1725         }
1726 
1727         pcmk__xml_log(log_level, file, function, line, prefix, data, depth,
1728                       options|xml_log_option_close);
1729 
1730     } else {
1731         for (child = pcmk__xml_first_child(data); child != NULL;
1732              child = pcmk__xml_next(child)) {
1733             log_xml_changes(log_level, file, function, line, prefix, child,
1734                             depth + 1, options);
1735         }
1736     }
1737 
1738     free(prefix_m);
1739 
1740 }
1741 
1742 void
1743 log_data_element(int log_level, const char *file, const char *function,
     /* [previous][next][first][last][top][bottom][index][help] */
1744                  int line, const char *prefix, const xmlNode *data, int depth,
1745                  int options)
1746 {
1747     xmlNode *a_child = NULL;
1748 
1749     char *prefix_m = NULL;
1750 
1751     if (log_level == LOG_NEVER) {
1752         return;
1753     }
1754 
1755     if (prefix == NULL) {
1756         prefix = "";
1757     }
1758 
1759     /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
1760     if (data == NULL) {
1761         do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
1762                          "No data to dump as XML");
1763         return;
1764     }
1765 
1766     if (pcmk_is_set(options, xml_log_option_dirty_add)) {
1767         log_xml_changes(log_level, file, function, line, prefix, data, depth,
1768                         options);
1769         return;
1770     }
1771 
1772     if (pcmk_is_set(options, xml_log_option_formatted)) {
1773         if (pcmk_is_set(options, xml_log_option_diff_plus)
1774             && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1775             options |= xml_log_option_diff_all;
1776             prefix_m = strdup(prefix);
1777             prefix_m[1] = '+';
1778             prefix = prefix_m;
1779 
1780         } else if (pcmk_is_set(options, xml_log_option_diff_minus)
1781                    && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1782             options |= xml_log_option_diff_all;
1783             prefix_m = strdup(prefix);
1784             prefix_m[1] = '-';
1785             prefix = prefix_m;
1786         }
1787     }
1788 
1789     if (pcmk_is_set(options, xml_log_option_diff_short)
1790                && !pcmk_is_set(options, xml_log_option_diff_all)) {
1791         /* Still searching for the actual change */
1792         for (a_child = pcmk__xml_first_child(data); a_child != NULL;
1793              a_child = pcmk__xml_next(a_child)) {
1794             log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
1795         }
1796     } else {
1797         pcmk__xml_log(log_level, file, function, line, prefix, data, depth,
1798                       options|xml_log_option_open|xml_log_option_close
1799                           |xml_log_option_children);
1800     }
1801     free(prefix_m);
1802 }
1803 
1804 /*!
1805  * \internal
1806  * \brief Append filtered XML attributes to a buffer
1807  *
1808  * \param[in]     data     XML element whose attributes to append
1809  * \param[in]     options  Group of \p xml_log_options flags
1810  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1811  */
1812 static void
1813 dump_filtered_xml(const xmlNode *data, int options, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
1814 {
1815     CRM_ASSERT(buffer != NULL);
1816 
1817     for (const xmlAttr *a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1818         if (!pcmk__xa_filterable((const char *) (a->name))) {
1819             dump_xml_attr(a, options, buffer);
1820         }
1821     }
1822 }
1823 
1824 /*!
1825  * \internal
1826  * \brief Append a string representation of an XML element to a buffer
1827  *
1828  * \param[in]     data     XML whose representation to append
1829  * \param[in]     options  Group of \p xml_log_options flags
1830  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1831  * \param[in]     depth    Current indentation level
1832  */
1833 static void
1834 dump_xml_element(const xmlNode *data, int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1835 {
1836     const char *name = NULL;
1837 
1838     CRM_ASSERT(buffer != NULL);
1839 
1840     if (data == NULL) {
1841         crm_trace("Nothing to dump");
1842         return;
1843     }
1844 
1845     name = crm_element_name(data);
1846     CRM_ASSERT(name != NULL);
1847 
1848     insert_prefix(options, buffer, depth);
1849     pcmk__g_strcat(buffer, "<", name, NULL);
1850 
1851     if (options & xml_log_option_filtered) {
1852         dump_filtered_xml(data, options, buffer);
1853 
1854     } else {
1855         for (const xmlAttr *a = pcmk__xe_first_attr(data); a != NULL;
1856              a = a->next) {
1857 
1858             dump_xml_attr(a, options, buffer);
1859         }
1860     }
1861 
1862     if (data->children == NULL) {
1863         g_string_append(buffer, "/>");
1864 
1865     } else {
1866         g_string_append_c(buffer, '>');
1867     }
1868 
1869     if (pcmk_is_set(options, xml_log_option_formatted)) {
1870         g_string_append_c(buffer, '\n');
1871     }
1872 
1873     if (data->children) {
1874         xmlNode *xChild = NULL;
1875         for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
1876             pcmk__xml2text(xChild, options, buffer, depth + 1);
1877         }
1878 
1879         insert_prefix(options, buffer, depth);
1880         pcmk__g_strcat(buffer, "</", name, ">", NULL);
1881 
1882         if (pcmk_is_set(options, xml_log_option_formatted)) {
1883             g_string_append_c(buffer, '\n');
1884         }
1885     }
1886 }
1887 
1888 /*!
1889  * \internal
1890  * \brief Append XML text content to a buffer
1891  *
1892  * \param[in]     data     XML whose content to append
1893  * \param[in]     options  Group of \p xml_log_options flags
1894  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1895  * \param[in]     depth    Current indentation level
1896  */
1897 static void
1898 dump_xml_text(const xmlNode *data, int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1899 {
1900     CRM_ASSERT(buffer != NULL);
1901 
1902     if (data == NULL) {
1903         crm_trace("Nothing to dump");
1904         return;
1905     }
1906 
1907     insert_prefix(options, buffer, depth);
1908     g_string_append(buffer, (const gchar *) data->content);
1909 
1910     if (pcmk_is_set(options, xml_log_option_formatted)) {
1911         g_string_append_c(buffer, '\n');
1912     }
1913 }
1914 
1915 /*!
1916  * \internal
1917  * \brief Append XML CDATA content to a buffer
1918  *
1919  * \param[in]     data     XML whose content to append
1920  * \param[in]     options  Group of \p xml_log_options flags
1921  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1922  * \param[in]     depth    Current indentation level
1923  */
1924 static void
1925 dump_xml_cdata(const xmlNode *data, int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1926 {
1927     CRM_ASSERT(buffer != NULL);
1928 
1929     if (data == NULL) {
1930         crm_trace("Nothing to dump");
1931         return;
1932     }
1933 
1934     insert_prefix(options, buffer, depth);
1935     pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
1936                    NULL);
1937 
1938     if (pcmk_is_set(options, xml_log_option_formatted)) {
1939         g_string_append_c(buffer, '\n');
1940     }
1941 }
1942 
1943 /*!
1944  * \internal
1945  * \brief Append an XML comment to a buffer
1946  *
1947  * \param[in]     data     XML whose content to append
1948  * \param[in]     options  Group of \p xml_log_options flags
1949  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
1950  * \param[in]     depth    Current indentation level
1951  */
1952 static void
1953 dump_xml_comment(const xmlNode *data, int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1954 {
1955     CRM_ASSERT(buffer != NULL);
1956 
1957     if (data == NULL) {
1958         crm_trace("Nothing to dump");
1959         return;
1960     }
1961 
1962     insert_prefix(options, buffer, depth);
1963     pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
1964 
1965     if (pcmk_is_set(options, xml_log_option_formatted)) {
1966         g_string_append_c(buffer, '\n');
1967     }
1968 }
1969 
1970 #define PCMK__XMLDUMP_STATS 0
1971 
1972 /*!
1973  * \internal
1974  * \brief Create a text representation of an XML object
1975  *
1976  * \param[in]     data     XML to convert
1977  * \param[in]     options  Group of \p xml_log_options flags
1978  * \param[in,out] buffer   Where to store the text (must not be \p NULL)
1979  * \param[in]     depth    Current indentation level
1980  */
1981 void
1982 pcmk__xml2text(xmlNodePtr data, int options, GString *buffer, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1983 {
1984     CRM_ASSERT(buffer != NULL);
1985 
1986     if (data == NULL) {
1987         return;
1988     }
1989 
1990     if (!pcmk_is_set(options, xml_log_option_filtered)
1991         && pcmk_is_set(options, xml_log_option_full_fledged)) {
1992         /* libxml's serialization reuse is a good idea, sadly we cannot
1993            apply it for the filtered cases (preceding filtering pass
1994            would preclude further reuse of such in-situ modified XML
1995            in generic context and is likely not a win performance-wise),
1996            and there's also a historically unstable throughput argument
1997            (likely stemming from memory allocation overhead, eventhough
1998            that shall be minimized with defaults preset in crm_xml_init) */
1999 #if (PCMK__XMLDUMP_STATS - 0)
2000         time_t next, new = time(NULL);
2001 #endif
2002         xmlDoc *doc;
2003         xmlOutputBuffer *xml_buffer;
2004 
2005         doc = getDocPtr(data);
2006         /* doc will only be NULL if data is */
2007         CRM_CHECK(doc != NULL, return);
2008 
2009         xml_buffer = xmlAllocOutputBuffer(NULL);
2010         CRM_ASSERT(xml_buffer != NULL);
2011 
2012         /* XXX we could setup custom allocation scheme for the particular
2013                buffer, but it's subsumed with crm_xml_init that needs to
2014                be invoked prior to entering this function as such, since
2015                its other branch vitally depends on it -- what can be done
2016                about this all is to have a facade parsing functions that
2017                would 100% mark entering libxml code for us, since we don't
2018                do anything as crazy as swapping out the binary form of the
2019                parsed tree (but those would need to be strictly used as
2020                opposed to libxml's raw functions) */
2021 
2022         xmlNodeDumpOutput(xml_buffer, doc, data, 0,
2023                           (options & xml_log_option_formatted), NULL);
2024         /* attempt adding final NL - failing shouldn't be fatal here */
2025         (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n");
2026         if (xml_buffer->buffer != NULL) {
2027             g_string_append(buffer,
2028                             (const gchar *) xmlBufContent(xml_buffer->buffer));
2029         }
2030 
2031 #if (PCMK__XMLDUMP_STATS - 0)
2032         next = time(NULL);
2033         if ((now + 1) < next) {
2034             crm_log_xml_trace(data, "Long time");
2035             crm_err("xmlNodeDumpOutput() -> %lld bytes took %ds",
2036                     (long long) buffer->len, next - now);
2037         }
2038 #endif
2039 
2040         /* asserted allocation before so there should be something to remove */
2041         (void) xmlOutputBufferClose(xml_buffer);
2042         return;
2043     }
2044 
2045     switch(data->type) {
2046         case XML_ELEMENT_NODE:
2047             /* Handle below */
2048             dump_xml_element(data, options, buffer, depth);
2049             break;
2050         case XML_TEXT_NODE:
2051             /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
2052             if (options & xml_log_option_text) {
2053                 dump_xml_text(data, options, buffer, depth);
2054             }
2055             break;
2056         case XML_COMMENT_NODE:
2057             dump_xml_comment(data, options, buffer, depth);
2058             break;
2059         case XML_CDATA_SECTION_NODE:
2060             dump_xml_cdata(data, options, buffer, depth);
2061             break;
2062         default:
2063             crm_warn("Unhandled type: %d", data->type);
2064             break;
2065 
2066             /*
2067             XML_ATTRIBUTE_NODE = 2
2068             XML_ENTITY_REF_NODE = 5
2069             XML_ENTITY_NODE = 6
2070             XML_PI_NODE = 7
2071             XML_DOCUMENT_NODE = 9
2072             XML_DOCUMENT_TYPE_NODE = 10
2073             XML_DOCUMENT_FRAG_NODE = 11
2074             XML_NOTATION_NODE = 12
2075             XML_HTML_DOCUMENT_NODE = 13
2076             XML_DTD_NODE = 14
2077             XML_ELEMENT_DECL = 15
2078             XML_ATTRIBUTE_DECL = 16
2079             XML_ENTITY_DECL = 17
2080             XML_NAMESPACE_DECL = 18
2081             XML_XINCLUDE_START = 19
2082             XML_XINCLUDE_END = 20
2083             XML_DOCB_DOCUMENT_NODE = 21
2084             */
2085     }
2086 }
2087 
2088 char *
2089 dump_xml_formatted_with_text(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2090 {
2091     char *buffer = NULL;
2092     GString *g_buffer = g_string_sized_new(1024);
2093 
2094     pcmk__xml2text(an_xml_node,
2095                    xml_log_option_formatted|xml_log_option_full_fledged,
2096                    g_buffer, 0);
2097 
2098     if (g_buffer != NULL) {
2099         buffer = strdup((const char *) g_buffer->str);
2100         g_string_free(g_buffer, TRUE);
2101     }
2102     return buffer;
2103 }
2104 
2105 char *
2106 dump_xml_formatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2107 {
2108     char *buffer = NULL;
2109     GString *g_buffer = g_string_sized_new(1024);
2110 
2111     pcmk__xml2text(an_xml_node, xml_log_option_formatted, g_buffer, 0);
2112 
2113     if (g_buffer != NULL) {
2114         buffer = strdup((const char *) g_buffer->str);
2115         g_string_free(g_buffer, TRUE);
2116     }
2117     return buffer;
2118 }
2119 
2120 char *
2121 dump_xml_unformatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2122 {
2123     char *buffer = NULL;
2124     GString *g_buffer = g_string_sized_new(1024);
2125 
2126     pcmk__xml2text(an_xml_node, 0, g_buffer, 0);
2127 
2128     if (g_buffer != NULL) {
2129         buffer = strdup((const char *) g_buffer->str);
2130         g_string_free(g_buffer, TRUE);
2131     }
2132     return buffer;
2133 }
2134 
2135 gboolean
2136 xml_has_children(const xmlNode * xml_root)
     /* [previous][next][first][last][top][bottom][index][help] */
2137 {
2138     if (xml_root != NULL && xml_root->children != NULL) {
2139         return TRUE;
2140     }
2141     return FALSE;
2142 }
2143 
2144 void
2145 xml_remove_prop(xmlNode * obj, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2146 {
2147     if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) {
2148         crm_trace("Cannot remove %s from %s", name, obj->name);
2149 
2150     } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
2151         /* Leave in place (marked for removal) until after the diff is calculated */
2152         xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
2153         xml_node_private_t *nodepriv = attr->_private;
2154 
2155         set_parent_flag(obj, pcmk__xf_dirty);
2156         pcmk__set_xml_flags(nodepriv, pcmk__xf_deleted);
2157     } else {
2158         xmlUnsetProp(obj, (pcmkXmlStr) name);
2159     }
2160 }
2161 
2162 void
2163 save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
2164 {
2165     char *f = NULL;
2166 
2167     if (filename == NULL) {
2168         char *uuid = crm_generate_uuid();
2169 
2170         f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
2171         filename = f;
2172         free(uuid);
2173     }
2174 
2175     crm_info("Saving %s to %s", desc, filename);
2176     write_xml_file(xml, filename, FALSE);
2177     free(f);
2178 }
2179 
2180 /*!
2181  * \internal
2182  * \brief Set a flag on all attributes of an XML element
2183  *
2184  * \param[in,out] xml   XML node to set flags on
2185  * \param[in]     flag  XML private flag to set
2186  */
2187 static void
2188 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
2189 {
2190     for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
2191         pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
2192     }
2193 }
2194 
2195 /*!
2196  * \internal
2197  * \brief Add an XML attribute to a node, marked as deleted
2198  *
2199  * When calculating XML changes, we need to know when an attribute has been
2200  * deleted. Add the attribute back to the new XML, so that we can check the
2201  * removal against ACLs, and mark it as deleted for later removal after
2202  * differences have been calculated.
2203  *
2204  * \param[in,out] new_xml     XML to modify
2205  * \param[in]     element     Name of XML element that changed (for logging)
2206  * \param[in]     attr_name   Name of attribute that was deleted
2207  * \param[in]     old_value   Value of attribute that was deleted
2208  */
2209 static void
2210 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
2211                   const char *old_value)
2212 {
2213     xml_doc_private_t *docpriv = new_xml->doc->_private;
2214     xmlAttr *attr = NULL;
2215     xml_node_private_t *nodepriv;
2216 
2217     // Prevent the dirty flag being set recursively upwards
2218     pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
2219 
2220     // Restore the old value (and the tracking flag)
2221     attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2222     pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
2223 
2224     // Reset flags (so the attribute doesn't appear as newly created)
2225     nodepriv = attr->_private;
2226     nodepriv->flags = 0;
2227 
2228     // Check ACLs and mark restored value for later removal
2229     xml_remove_prop(new_xml, attr_name);
2230 
2231     crm_trace("XML attribute %s=%s was removed from %s",
2232               attr_name, old_value, element);
2233 }
2234 
2235 /*
2236  * \internal
2237  * \brief Check ACLs for a changed XML attribute
2238  */
2239 static void
2240 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
2241                   const char *old_value)
2242 {
2243     char *vcopy = crm_element_value_copy(new_xml, attr_name);
2244 
2245     crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
2246               attr_name, old_value, vcopy, element);
2247 
2248     // Restore the original value
2249     xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2250 
2251     // Change it back to the new value, to check ACLs
2252     crm_xml_add(new_xml, attr_name, vcopy);
2253     free(vcopy);
2254 }
2255 
2256 /*!
2257  * \internal
2258  * \brief Mark an XML attribute as having changed position
2259  *
2260  * \param[in,out] new_xml     XML to modify
2261  * \param[in]     element     Name of XML element that changed (for logging)
2262  * \param[in,out] old_attr    Attribute that moved, in original XML
2263  * \param[in,out] new_attr    Attribute that moved, in \p new_xml
2264  * \param[in]     p_old       Ordinal position of \p old_attr in original XML
2265  * \param[in]     p_new       Ordinal position of \p new_attr in \p new_xml
2266  */
2267 static void
2268 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
     /* [previous][next][first][last][top][bottom][index][help] */
2269                 xmlAttr *new_attr, int p_old, int p_new)
2270 {
2271     xml_node_private_t *nodepriv = new_attr->_private;
2272 
2273     crm_trace("XML attribute %s moved from position %d to %d in %s",
2274               old_attr->name, p_old, p_new, element);
2275 
2276     // Mark document, element, and all element's parents as changed
2277     mark_xml_node_dirty(new_xml);
2278 
2279     // Mark attribute as changed
2280     pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
2281 
2282     nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
2283     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
2284 }
2285 
2286 /*!
2287  * \internal
2288  * \brief Calculate differences in all previously existing XML attributes
2289  *
2290  * \param[in,out] old_xml  Original XML to compare
2291  * \param[in,out] new_xml  New XML to compare
2292  */
2293 static void
2294 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2295 {
2296     xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
2297 
2298     while (attr_iter != NULL) {
2299         xmlAttr *old_attr = attr_iter;
2300         xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
2301         const char *name = (const char *) attr_iter->name;
2302         const char *old_value = crm_element_value(old_xml, name);
2303 
2304         attr_iter = attr_iter->next;
2305         if (new_attr == NULL) {
2306             mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
2307                               old_value);
2308 
2309         } else {
2310             xml_node_private_t *nodepriv = new_attr->_private;
2311             int new_pos = pcmk__xml_position((xmlNode*) new_attr,
2312                                              pcmk__xf_skip);
2313             int old_pos = pcmk__xml_position((xmlNode*) old_attr,
2314                                              pcmk__xf_skip);
2315             const char *new_value = crm_element_value(new_xml, name);
2316 
2317             // This attribute isn't new
2318             pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
2319 
2320             if (strcmp(new_value, old_value) != 0) {
2321                 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
2322                                   old_value);
2323 
2324             } else if ((old_pos != new_pos)
2325                        && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
2326                 mark_attr_moved(new_xml, (const char *) old_xml->name,
2327                                 old_attr, new_attr, old_pos, new_pos);
2328             }
2329         }
2330     }
2331 }
2332 
2333 /*!
2334  * \internal
2335  * \brief Check all attributes in new XML for creation
2336  *
2337  * For each of a given XML element's attributes marked as newly created, accept
2338  * (and mark as dirty) or reject the creation according to ACLs.
2339  *
2340  * \param[in,out] new_xml  XML to check
2341  */
2342 static void
2343 mark_created_attrs(xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2344 {
2345     xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
2346 
2347     while (attr_iter != NULL) {
2348         xmlAttr *new_attr = attr_iter;
2349         xml_node_private_t *nodepriv = attr_iter->_private;
2350 
2351         attr_iter = attr_iter->next;
2352         if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
2353             const char *attr_name = (const char *) new_attr->name;
2354 
2355             crm_trace("Created new attribute %s=%s in %s",
2356                       attr_name, crm_element_value(new_xml, attr_name),
2357                       new_xml->name);
2358 
2359             /* Check ACLs (we can't use the remove-then-create trick because it
2360              * would modify the attribute position).
2361              */
2362             if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
2363                 pcmk__mark_xml_attr_dirty(new_attr);
2364             } else {
2365                 // Creation was not allowed, so remove the attribute
2366                 xmlUnsetProp(new_xml, new_attr->name);
2367             }
2368         }
2369     }
2370 }
2371 
2372 /*!
2373  * \internal
2374  * \brief Calculate differences in attributes between two XML nodes
2375  *
2376  * \param[in,out] old_xml  Original XML to compare
2377  * \param[in,out] new_xml  New XML to compare
2378  */
2379 static void
2380 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2381 {
2382     set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
2383     xml_diff_old_attrs(old_xml, new_xml);
2384     mark_created_attrs(new_xml);
2385 }
2386 
2387 /*!
2388  * \internal
2389  * \brief Add an XML child element to a node, marked as deleted
2390  *
2391  * When calculating XML changes, we need to know when a child element has been
2392  * deleted. Add the child back to the new XML, so that we can check the removal
2393  * against ACLs, and mark it as deleted for later removal after differences have
2394  * been calculated.
2395  *
2396  * \param[in,out] old_child    Child element from original XML
2397  * \param[in,out] new_parent   New XML to add marked copy to
2398  */
2399 static void
2400 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
     /* [previous][next][first][last][top][bottom][index][help] */
2401 {
2402     // Re-create the child element so we can check ACLs
2403     xmlNode *candidate = add_node_copy(new_parent, old_child);
2404 
2405     // Clear flags on new child and its children
2406     reset_xml_node_flags(candidate);
2407 
2408     // Check whether ACLs allow the deletion
2409     pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
2410 
2411     // Remove the child again (which will track it in document's deleted_objs)
2412     free_xml_with_position(candidate,
2413                            pcmk__xml_position(old_child, pcmk__xf_skip));
2414 
2415     if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
2416         pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
2417                             pcmk__xf_skip);
2418     }
2419 }
2420 
2421 static void
2422 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
     /* [previous][next][first][last][top][bottom][index][help] */
2423                  int p_old, int p_new)
2424 {
2425     xml_node_private_t *nodepriv = new_child->_private;
2426 
2427     crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
2428               new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
2429               p_old, p_new, new_parent->name);
2430     mark_xml_node_dirty(new_parent);
2431     pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
2432 
2433     if (p_old > p_new) {
2434         nodepriv = old_child->_private;
2435     } else {
2436         nodepriv = new_child->_private;
2437     }
2438     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
2439 }
2440 
2441 // Given original and new XML, mark new XML portions that have changed
2442 static void
2443 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
     /* [previous][next][first][last][top][bottom][index][help] */
2444 {
2445     xmlNode *cIter = NULL;
2446     xml_node_private_t *nodepriv = NULL;
2447 
2448     CRM_CHECK(new_xml != NULL, return);
2449     if (old_xml == NULL) {
2450         pcmk__mark_xml_created(new_xml);
2451         pcmk__apply_creation_acl(new_xml, check_top);
2452         return;
2453     }
2454 
2455     nodepriv = new_xml->_private;
2456     CRM_CHECK(nodepriv != NULL, return);
2457 
2458     if(nodepriv->flags & pcmk__xf_processed) {
2459         /* Avoid re-comparing nodes */
2460         return;
2461     }
2462     pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
2463 
2464     xml_diff_attrs(old_xml, new_xml);
2465 
2466     // Check for differences in the original children
2467     for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
2468         xmlNode *old_child = cIter;
2469         xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
2470 
2471         cIter = pcmk__xml_next(cIter);
2472         if(new_child) {
2473             mark_xml_changes(old_child, new_child, TRUE);
2474 
2475         } else {
2476             mark_child_deleted(old_child, new_xml);
2477         }
2478     }
2479 
2480     // Check for moved or created children
2481     for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
2482         xmlNode *new_child = cIter;
2483         xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
2484 
2485         cIter = pcmk__xml_next(cIter);
2486         if(old_child == NULL) {
2487             // This is a newly created child
2488             nodepriv = new_child->_private;
2489             pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
2490             mark_xml_changes(old_child, new_child, TRUE);
2491 
2492         } else {
2493             /* Check for movement, we already checked for differences */
2494             int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
2495             int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
2496 
2497             if(p_old != p_new) {
2498                 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
2499             }
2500         }
2501     }
2502 }
2503 
2504 void
2505 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2506 {
2507     pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
2508     xml_calculate_changes(old_xml, new_xml);
2509 }
2510 
2511 void
2512 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2513 {
2514     CRM_CHECK(pcmk__str_eq(crm_element_name(old_xml), crm_element_name(new_xml), pcmk__str_casei),
2515               return);
2516     CRM_CHECK(pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_casei), return);
2517 
2518     if(xml_tracking_changes(new_xml) == FALSE) {
2519         xml_track_changes(new_xml, NULL, NULL, FALSE);
2520     }
2521 
2522     mark_xml_changes(old_xml, new_xml, FALSE);
2523 }
2524 
2525 gboolean
2526 can_prune_leaf(xmlNode * xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2527 {
2528     xmlNode *cIter = NULL;
2529     gboolean can_prune = TRUE;
2530     const char *name = crm_element_name(xml_node);
2531 
2532     if (pcmk__strcase_any_of(name, XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF,
2533                              XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, NULL)) {
2534         return FALSE;
2535     }
2536 
2537     for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
2538         const char *p_name = (const char *) a->name;
2539 
2540         if (strcmp(p_name, XML_ATTR_ID) == 0) {
2541             continue;
2542         }
2543         can_prune = FALSE;
2544     }
2545 
2546     cIter = pcmk__xml_first_child(xml_node);
2547     while (cIter) {
2548         xmlNode *child = cIter;
2549 
2550         cIter = pcmk__xml_next(cIter);
2551         if (can_prune_leaf(child)) {
2552             free_xml(child);
2553         } else {
2554             can_prune = FALSE;
2555         }
2556     }
2557     return can_prune;
2558 }
2559 
2560 /*!
2561  * \internal
2562  * \brief Find a comment with matching content in specified XML
2563  *
2564  * \param[in] root            XML to search
2565  * \param[in] search_comment  Comment whose content should be searched for
2566  * \param[in] exact           If true, comment must also be at same position
2567  */
2568 xmlNode *
2569 pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
2570 {
2571     xmlNode *a_child = NULL;
2572     int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
2573 
2574     CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
2575 
2576     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
2577          a_child = pcmk__xml_next(a_child)) {
2578         if (exact) {
2579             int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
2580             xml_node_private_t *nodepriv = a_child->_private;
2581 
2582             if (offset < search_offset) {
2583                 continue;
2584 
2585             } else if (offset > search_offset) {
2586                 return NULL;
2587             }
2588 
2589             if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
2590                 continue;
2591             }
2592         }
2593 
2594         if (a_child->type == XML_COMMENT_NODE
2595             && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
2596             return a_child;
2597 
2598         } else if (exact) {
2599             return NULL;
2600         }
2601     }
2602 
2603     return NULL;
2604 }
2605 
2606 /*!
2607  * \internal
2608  * \brief Make one XML comment match another (in content)
2609  *
2610  * \param[in,out] parent   If \p target is NULL and this is not, add or update
2611  *                         comment child of this XML node that matches \p update
2612  * \param[in,out] target   If not NULL, update this XML comment node
2613  * \param[in]     update   Make comment content match this (must not be NULL)
2614  *
2615  * \note At least one of \parent and \target must be non-NULL
2616  */
2617 void
2618 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
     /* [previous][next][first][last][top][bottom][index][help] */
2619 {
2620     CRM_CHECK(update != NULL, return);
2621     CRM_CHECK(update->type == XML_COMMENT_NODE, return);
2622 
2623     if (target == NULL) {
2624         target = pcmk__xc_match(parent, update, false);
2625     }
2626 
2627     if (target == NULL) {
2628         add_node_copy(parent, update);
2629 
2630     } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
2631         xmlFree(target->content);
2632         target->content = xmlStrdup(update->content);
2633     }
2634 }
2635 
2636 /*!
2637  * \internal
2638  * \brief Make one XML tree match another (in children and attributes)
2639  *
2640  * \param[in,out] parent   If \p target is NULL and this is not, add or update
2641  *                         child of this XML node that matches \p update
2642  * \param[in,out] target   If not NULL, update this XML
2643  * \param[in]     update   Make the desired XML match this (must not be NULL)
2644  * \param[in]     as_diff  If false, expand "++" when making attributes match
2645  *
2646  * \note At least one of \parent and \target must be non-NULL
2647  */
2648 void
2649 pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
     /* [previous][next][first][last][top][bottom][index][help] */
2650                  bool as_diff)
2651 {
2652     xmlNode *a_child = NULL;
2653     const char *object_name = NULL,
2654                *object_href = NULL,
2655                *object_href_val = NULL;
2656 
2657 #if XML_PARSER_DEBUG
2658     crm_log_xml_trace(update, "update:");
2659     crm_log_xml_trace(target, "target:");
2660 #endif
2661 
2662     CRM_CHECK(update != NULL, return);
2663 
2664     if (update->type == XML_COMMENT_NODE) {
2665         pcmk__xc_update(parent, target, update);
2666         return;
2667     }
2668 
2669     object_name = crm_element_name(update);
2670     object_href_val = ID(update);
2671     if (object_href_val != NULL) {
2672         object_href = XML_ATTR_ID;
2673     } else {
2674         object_href_val = crm_element_value(update, XML_ATTR_IDREF);
2675         object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
2676     }
2677 
2678     CRM_CHECK(object_name != NULL, return);
2679     CRM_CHECK(target != NULL || parent != NULL, return);
2680 
2681     if (target == NULL) {
2682         target = pcmk__xe_match(parent, object_name,
2683                                 object_href, object_href_val);
2684     }
2685 
2686     if (target == NULL) {
2687         target = create_xml_node(parent, object_name);
2688         CRM_CHECK(target != NULL, return);
2689 #if XML_PARSER_DEBUG
2690         crm_trace("Added  <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
2691                   object_href ? " " : "",
2692                   object_href ? object_href : "",
2693                   object_href ? "=" : "",
2694                   object_href ? object_href_val : "");
2695 
2696     } else {
2697         crm_trace("Found node <%s%s%s%s%s/> to update",
2698                   pcmk__s(object_name, "<null>"),
2699                   object_href ? " " : "",
2700                   object_href ? object_href : "",
2701                   object_href ? "=" : "",
2702                   object_href ? object_href_val : "");
2703 #endif
2704     }
2705 
2706     CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(update),
2707                            pcmk__str_casei),
2708               return);
2709 
2710     if (as_diff == FALSE) {
2711         /* So that expand_plus_plus() gets called */
2712         copy_in_properties(target, update);
2713 
2714     } else {
2715         /* No need for expand_plus_plus(), just raw speed */
2716         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2717              a = a->next) {
2718             const char *p_value = pcmk__xml_attr_value(a);
2719 
2720             /* Remove it first so the ordering of the update is preserved */
2721             xmlUnsetProp(target, a->name);
2722             xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
2723         }
2724     }
2725 
2726     for (a_child = pcmk__xml_first_child(update); a_child != NULL;
2727          a_child = pcmk__xml_next(a_child)) {
2728 #if XML_PARSER_DEBUG
2729         crm_trace("Updating child <%s%s%s%s%s/>",
2730                   pcmk__s(object_name, "<null>"),
2731                   object_href ? " " : "",
2732                   object_href ? object_href : "",
2733                   object_href ? "=" : "",
2734                   object_href ? object_href_val : "");
2735 #endif
2736         pcmk__xml_update(target, NULL, a_child, as_diff);
2737     }
2738 
2739 #if XML_PARSER_DEBUG
2740     crm_trace("Finished with <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
2741               object_href ? " " : "",
2742               object_href ? object_href : "",
2743               object_href ? "=" : "",
2744               object_href ? object_href_val : "");
2745 #endif
2746 }
2747 
2748 gboolean
2749 update_xml_child(xmlNode * child, xmlNode * to_update)
     /* [previous][next][first][last][top][bottom][index][help] */
2750 {
2751     gboolean can_update = TRUE;
2752     xmlNode *child_of_child = NULL;
2753 
2754     CRM_CHECK(child != NULL, return FALSE);
2755     CRM_CHECK(to_update != NULL, return FALSE);
2756 
2757     if (!pcmk__str_eq(crm_element_name(to_update), crm_element_name(child), pcmk__str_none)) {
2758         can_update = FALSE;
2759 
2760     } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) {
2761         can_update = FALSE;
2762 
2763     } else if (can_update) {
2764 #if XML_PARSER_DEBUG
2765         crm_log_xml_trace(child, "Update match found...");
2766 #endif
2767         pcmk__xml_update(NULL, child, to_update, false);
2768     }
2769 
2770     for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
2771          child_of_child = pcmk__xml_next(child_of_child)) {
2772         /* only update the first one */
2773         if (can_update) {
2774             break;
2775         }
2776         can_update = update_xml_child(child_of_child, to_update);
2777     }
2778 
2779     return can_update;
2780 }
2781 
2782 int
2783 find_xml_children(xmlNode ** children, xmlNode * root,
     /* [previous][next][first][last][top][bottom][index][help] */
2784                   const char *tag, const char *field, const char *value, gboolean search_matches)
2785 {
2786     int match_found = 0;
2787 
2788     CRM_CHECK(root != NULL, return FALSE);
2789     CRM_CHECK(children != NULL, return FALSE);
2790 
2791     if (tag != NULL && !pcmk__str_eq(tag, crm_element_name(root), pcmk__str_casei)) {
2792 
2793     } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
2794 
2795     } else {
2796         if (*children == NULL) {
2797             *children = create_xml_node(NULL, __func__);
2798         }
2799         add_node_copy(*children, root);
2800         match_found = 1;
2801     }
2802 
2803     if (search_matches || match_found == 0) {
2804         xmlNode *child = NULL;
2805 
2806         for (child = pcmk__xml_first_child(root); child != NULL;
2807              child = pcmk__xml_next(child)) {
2808             match_found += find_xml_children(children, child, tag, field, value, search_matches);
2809         }
2810     }
2811 
2812     return match_found;
2813 }
2814 
2815 gboolean
2816 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
     /* [previous][next][first][last][top][bottom][index][help] */
2817 {
2818     gboolean can_delete = FALSE;
2819     xmlNode *child_of_child = NULL;
2820 
2821     const char *up_id = NULL;
2822     const char *child_id = NULL;
2823     const char *right_val = NULL;
2824 
2825     CRM_CHECK(child != NULL, return FALSE);
2826     CRM_CHECK(update != NULL, return FALSE);
2827 
2828     up_id = ID(update);
2829     child_id = ID(child);
2830 
2831     if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
2832         can_delete = TRUE;
2833     }
2834     if (!pcmk__str_eq(crm_element_name(update), crm_element_name(child), pcmk__str_casei)) {
2835         can_delete = FALSE;
2836     }
2837     if (can_delete && delete_only) {
2838         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2839              a = a->next) {
2840             const char *p_name = (const char *) a->name;
2841             const char *p_value = pcmk__xml_attr_value(a);
2842 
2843             right_val = crm_element_value(child, p_name);
2844             if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
2845                 can_delete = FALSE;
2846             }
2847         }
2848     }
2849 
2850     if (can_delete && parent != NULL) {
2851         crm_log_xml_trace(child, "Delete match found...");
2852         if (delete_only || update == NULL) {
2853             free_xml(child);
2854 
2855         } else {
2856             xmlNode *tmp = copy_xml(update);
2857             xmlDoc *doc = tmp->doc;
2858             xmlNode *old = NULL;
2859 
2860             xml_accept_changes(tmp);
2861             old = xmlReplaceNode(child, tmp);
2862 
2863             if(xml_tracking_changes(tmp)) {
2864                 /* Replaced sections may have included relevant ACLs */
2865                 pcmk__apply_acl(tmp);
2866             }
2867 
2868             xml_calculate_changes(old, tmp);
2869             xmlDocSetRootElement(doc, old);
2870             free_xml(old);
2871         }
2872         child = NULL;
2873         return TRUE;
2874 
2875     } else if (can_delete) {
2876         crm_log_xml_debug(child, "Cannot delete the search root");
2877         can_delete = FALSE;
2878     }
2879 
2880     child_of_child = pcmk__xml_first_child(child);
2881     while (child_of_child) {
2882         xmlNode *next = pcmk__xml_next(child_of_child);
2883 
2884         can_delete = replace_xml_child(child, child_of_child, update, delete_only);
2885 
2886         /* only delete the first one */
2887         if (can_delete) {
2888             child_of_child = NULL;
2889         } else {
2890             child_of_child = next;
2891         }
2892     }
2893 
2894     return can_delete;
2895 }
2896 
2897 xmlNode *
2898 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
     /* [previous][next][first][last][top][bottom][index][help] */
2899 {
2900     xmlNode *child = NULL;
2901     GSList *nvpairs = NULL;
2902     xmlNode *result = NULL;
2903     const char *name = NULL;
2904 
2905     CRM_CHECK(input != NULL, return NULL);
2906 
2907     name = crm_element_name(input);
2908     CRM_CHECK(name != NULL, return NULL);
2909 
2910     result = create_xml_node(parent, name);
2911     nvpairs = pcmk_xml_attrs2nvpairs(input);
2912     nvpairs = pcmk_sort_nvpairs(nvpairs);
2913     pcmk_nvpairs2xml_attrs(nvpairs, result);
2914     pcmk_free_nvpairs(nvpairs);
2915 
2916     for (child = pcmk__xml_first_child(input); child != NULL;
2917          child = pcmk__xml_next(child)) {
2918 
2919         if (recursive) {
2920             sorted_xml(child, result, recursive);
2921         } else {
2922             add_node_copy(result, child);
2923         }
2924     }
2925 
2926     return result;
2927 }
2928 
2929 xmlNode *
2930 first_named_child(const xmlNode *parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2931 {
2932     xmlNode *match = NULL;
2933 
2934     for (match = pcmk__xe_first_child(parent); match != NULL;
2935          match = pcmk__xe_next(match)) {
2936         /*
2937          * name == NULL gives first child regardless of name; this is
2938          * semantically incorrect in this function, but may be necessary
2939          * due to prior use of xml_child_iter_filter
2940          */
2941         if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) {
2942             return match;
2943         }
2944     }
2945     return NULL;
2946 }
2947 
2948 /*!
2949  * \brief Get next instance of same XML tag
2950  *
2951  * \param[in] sibling  XML tag to start from
2952  *
2953  * \return Next sibling XML tag with same name
2954  */
2955 xmlNode *
2956 crm_next_same_xml(const xmlNode *sibling)
     /* [previous][next][first][last][top][bottom][index][help] */
2957 {
2958     xmlNode *match = pcmk__xe_next(sibling);
2959     const char *name = crm_element_name(sibling);
2960 
2961     while (match != NULL) {
2962         if (!strcmp(crm_element_name(match), name)) {
2963             return match;
2964         }
2965         match = pcmk__xe_next(match);
2966     }
2967     return NULL;
2968 }
2969 
2970 void
2971 crm_xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2972 {
2973     static bool init = true;
2974 
2975     if(init) {
2976         init = false;
2977         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
2978          * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
2979          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
2980          * less than 1 second.
2981          */
2982         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
2983 
2984         /* Populate and free the _private field when nodes are created and destroyed */
2985         xmlDeregisterNodeDefault(free_private_data);
2986         xmlRegisterNodeDefault(new_private_data);
2987 
2988         crm_schema_init();
2989     }
2990 }
2991 
2992 void
2993 crm_xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2994 {
2995     crm_schema_cleanup();
2996     xmlCleanupParser();
2997 }
2998 
2999 #define XPATH_MAX 512
3000 
3001 xmlNode *
3002 expand_idref(xmlNode * input, xmlNode * top)
     /* [previous][next][first][last][top][bottom][index][help] */
3003 {
3004     const char *tag = NULL;
3005     const char *ref = NULL;
3006     xmlNode *result = input;
3007 
3008     if (result == NULL) {
3009         return NULL;
3010 
3011     } else if (top == NULL) {
3012         top = input;
3013     }
3014 
3015     tag = crm_element_name(result);
3016     ref = crm_element_value(result, XML_ATTR_IDREF);
3017 
3018     if (ref != NULL) {
3019         char *xpath_string = crm_strdup_printf("//%s[@id='%s']", tag, ref);
3020 
3021         result = get_xpath_object(xpath_string, top, LOG_ERR);
3022         if (result == NULL) {
3023             char *nodePath = (char *)xmlGetNodePath(top);
3024 
3025             crm_err("No match for %s found in %s: Invalid configuration",
3026                     xpath_string, pcmk__s(nodePath, "unrecognizable path"));
3027             free(nodePath);
3028         }
3029         free(xpath_string);
3030     }
3031     return result;
3032 }
3033 
3034 char *
3035 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
     /* [previous][next][first][last][top][bottom][index][help] */
3036 {
3037     static const char *base = NULL;
3038     char *ret = NULL;
3039 
3040     if (base == NULL) {
3041         base = getenv("PCMK_schema_directory");
3042     }
3043     if (pcmk__str_empty(base)) {
3044         base = CRM_SCHEMA_DIRECTORY;
3045     }
3046 
3047     switch (ns) {
3048         case pcmk__xml_artefact_ns_legacy_rng:
3049         case pcmk__xml_artefact_ns_legacy_xslt:
3050             ret = strdup(base);
3051             break;
3052         case pcmk__xml_artefact_ns_base_rng:
3053         case pcmk__xml_artefact_ns_base_xslt:
3054             ret = crm_strdup_printf("%s/base", base);
3055             break;
3056         default:
3057             crm_err("XML artefact family specified as %u not recognized", ns);
3058     }
3059     return ret;
3060 }
3061 
3062 char *
3063 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
3064 {
3065     char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
3066 
3067     switch (ns) {
3068         case pcmk__xml_artefact_ns_legacy_rng:
3069         case pcmk__xml_artefact_ns_base_rng:
3070             ret = crm_strdup_printf("%s/%s.rng", base, filespec);
3071             break;
3072         case pcmk__xml_artefact_ns_legacy_xslt:
3073         case pcmk__xml_artefact_ns_base_xslt:
3074             ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
3075             break;
3076         default:
3077             crm_err("XML artefact family specified as %u not recognized", ns);
3078     }
3079     free(base);
3080 
3081     return ret;
3082 }
3083 
3084 void
3085 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
     /* [previous][next][first][last][top][bottom][index][help] */
3086 {
3087     while (true) {
3088         const char *name, *value;
3089 
3090         name = va_arg(pairs, const char *);
3091         if (name == NULL) {
3092             return;
3093         }
3094 
3095         value = va_arg(pairs, const char *);
3096         if (value != NULL) {
3097             crm_xml_add(node, name, value);
3098         }
3099     }
3100 }
3101 
3102 void
3103 pcmk__xe_set_props(xmlNodePtr node, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
3104 {
3105     va_list pairs;
3106     va_start(pairs, node);
3107     pcmk__xe_set_propv(node, pairs);
3108     va_end(pairs);
3109 }
3110 
3111 int
3112 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
     /* [previous][next][first][last][top][bottom][index][help] */
3113                        int (*handler)(xmlNode *xml, void *userdata),
3114                        void *userdata)
3115 {
3116     xmlNode *children = (xml? xml->children : NULL);
3117 
3118     CRM_ASSERT(handler != NULL);
3119 
3120     for (xmlNode *node = children; node != NULL; node = node->next) {
3121         if (node->type == XML_ELEMENT_NODE &&
3122             pcmk__str_eq(child_element_name, (const char *) node->name, pcmk__str_null_matches)) {
3123             int rc = handler(node, userdata);
3124 
3125             if (rc != pcmk_rc_ok) {
3126                 return rc;
3127             }
3128         }
3129     }
3130 
3131     return pcmk_rc_ok;
3132 }
3133 
3134 // Deprecated functions kept only for backward API compatibility
3135 // LCOV_EXCL_START
3136 
3137 #include <crm/common/xml_compat.h>
3138 
3139 xmlNode *
3140 find_entity(xmlNode *parent, const char *node_name, const char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
3141 {
3142     return pcmk__xe_match(parent, node_name,
3143                           ((id == NULL)? id : XML_ATTR_ID), id);
3144 }
3145 
3146 void
3147 crm_destroy_xml(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
3148 {
3149     free_xml(data);
3150 }
3151 
3152 // LCOV_EXCL_STOP
3153 // End deprecated API

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