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. accept_attr_deletions
  18. pcmk__xml_match
  19. xml_log_changes
  20. xml_accept_changes
  21. find_xml_node
  22. pcmk__xe_match
  23. copy_in_properties
  24. fix_plus_plus_recursive
  25. expand_plus_plus
  26. getDocPtr
  27. add_node_copy
  28. add_node_nocopy
  29. create_xml_node
  30. pcmk_create_xml_text_node
  31. pcmk_create_html_node
  32. pcmk_free_xml_subtree
  33. free_xml_with_position
  34. free_xml
  35. copy_xml
  36. log_xmllib_err
  37. string2xml
  38. stdin2xml
  39. decompress_file
  40. pcmk__strip_xml_text
  41. filename2xml
  42. pcmk__xe_add_last_written
  43. crm_xml_sanitize_id
  44. crm_xml_set_id
  45. write_xml_stream
  46. write_xml_fd
  47. write_xml_file
  48. replace_text
  49. crm_xml_escape
  50. dump_xml_attr
  51. pcmk__xe_log
  52. log_xml_changes
  53. log_data_element
  54. dump_filtered_xml
  55. dump_xml_element
  56. dump_xml_text
  57. dump_xml_cdata
  58. dump_xml_comment
  59. pcmk__xml2text
  60. pcmk__buffer_add_char
  61. dump_xml_formatted_with_text
  62. dump_xml_formatted
  63. dump_xml_unformatted
  64. xml_has_children
  65. xml_remove_prop
  66. save_xml_to_file
  67. set_attrs_flag
  68. mark_attr_deleted
  69. mark_attr_changed
  70. mark_attr_moved
  71. xml_diff_old_attrs
  72. mark_created_attrs
  73. xml_diff_attrs
  74. mark_child_deleted
  75. mark_child_moved
  76. mark_xml_changes
  77. xml_calculate_significant_changes
  78. xml_calculate_changes
  79. can_prune_leaf
  80. pcmk__xc_match
  81. pcmk__xc_update
  82. pcmk__xml_update
  83. update_xml_child
  84. find_xml_children
  85. replace_xml_child
  86. sorted_xml
  87. first_named_child
  88. crm_next_same_xml
  89. crm_xml_init
  90. crm_xml_cleanup
  91. expand_idref
  92. crm_destroy_xml
  93. pcmk__xml_artefact_root
  94. pcmk__xml_artefact_path
  95. find_entity

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

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