root/lib/common/xml.c

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

DEFINITIONS

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

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

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