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. pcmk__xe_log
  54. log_xml_changes
  55. log_data_element
  56. dump_filtered_xml
  57. dump_xml_element
  58. dump_xml_text
  59. dump_xml_cdata
  60. dump_xml_comment
  61. pcmk__xml2text
  62. pcmk__buffer_add_char
  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. crm_destroy_xml
  95. pcmk__xml_artefact_root
  96. pcmk__xml_artefact_path
  97. pcmk__xe_set_propv
  98. pcmk__xe_set_props
  99. find_entity

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

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