root/lib/common/xml.c

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

DEFINITIONS

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

   1 /*
   2  * Copyright 2004-2021 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <sys/types.h>
  14 #include <unistd.h>
  15 #include <time.h>
  16 #include <string.h>
  17 #include <stdlib.h>
  18 #include <stdarg.h>
  19 #include <bzlib.h>
  20 
  21 #include <libxml/parser.h>
  22 #include <libxml/tree.h>
  23 #include <libxml/xmlIO.h>  /* xmlAllocOutputBuffer */
  24 
  25 #include <crm/crm.h>
  26 #include <crm/msg_xml.h>
  27 #include <crm/common/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         return NULL;
 979     }
 980 
 981     rc = BZ_OK;
 982     // cppcheck seems not to understand the abort-logic in pcmk__realloc
 983     // cppcheck-suppress memleak
 984     while (rc == BZ_OK) {
 985         buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
 986         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
 987 
 988         crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
 989 
 990         if (rc == BZ_OK || rc == BZ_STREAM_END) {
 991             length += read_len;
 992         }
 993     }
 994 
 995     buffer[length] = '\0';
 996 
 997     if (rc != BZ_STREAM_END) {
 998         crm_err("Could not read compressed %s: %s "
 999                 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1000         free(buffer);
1001         buffer = NULL;
1002     }
1003 
1004     BZ2_bzReadClose(&rc, bz_file);
1005     fclose(input);
1006     return buffer;
1007 }
1008 
1009 /*!
1010  * \internal
1011  * \brief Remove XML text nodes from specified XML and all its children
1012  *
1013  * \param[in,out] xml  XML to strip text from
1014  */
1015 void
1016 pcmk__strip_xml_text(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1017 {
1018     xmlNode *iter = xml->children;
1019 
1020     while (iter) {
1021         xmlNode *next = iter->next;
1022 
1023         switch (iter->type) {
1024             case XML_TEXT_NODE:
1025                 /* Remove it */
1026                 pcmk_free_xml_subtree(iter);
1027                 break;
1028 
1029             case XML_ELEMENT_NODE:
1030                 /* Search it */
1031                 pcmk__strip_xml_text(iter);
1032                 break;
1033 
1034             default:
1035                 /* Leave it */
1036                 break;
1037         }
1038 
1039         iter = next;
1040     }
1041 }
1042 
1043 xmlNode *
1044 filename2xml(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1045 {
1046     xmlNode *xml = NULL;
1047     xmlDocPtr output = NULL;
1048     bool uncompressed = true;
1049     xmlParserCtxtPtr ctxt = NULL;
1050     xmlErrorPtr last_error = NULL;
1051 
1052     /* create a parser context */
1053     ctxt = xmlNewParserCtxt();
1054     CRM_CHECK(ctxt != NULL, return NULL);
1055 
1056     xmlCtxtResetLastError(ctxt);
1057     xmlSetGenericErrorFunc(ctxt, log_xmllib_err);
1058 
1059     if (filename) {
1060         uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
1061     }
1062 
1063     if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
1064         /* STDIN_FILENO == fileno(stdin) */
1065         output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
1066                                PCMK__XML_PARSE_OPTS);
1067 
1068     } else if (uncompressed) {
1069         output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS);
1070 
1071     } else {
1072         char *input = decompress_file(filename);
1073 
1074         output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
1075                                 PCMK__XML_PARSE_OPTS);
1076         free(input);
1077     }
1078 
1079     if (output && (xml = xmlDocGetRootElement(output))) {
1080         pcmk__strip_xml_text(xml);
1081     }
1082 
1083     last_error = xmlCtxtGetLastError(ctxt);
1084     if (last_error && last_error->code != XML_ERR_OK) {
1085         /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
1086         /*
1087          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
1088          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
1089          */
1090         crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
1091                 last_error->domain, last_error->level, last_error->code, last_error->message);
1092 
1093         if (last_error && last_error->code != XML_ERR_OK) {
1094             crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
1095             if (xml != NULL) {
1096                 crm_log_xml_err(xml, "Partial");
1097             }
1098         }
1099     }
1100 
1101     xmlFreeParserCtxt(ctxt);
1102     return xml;
1103 }
1104 
1105 /*!
1106  * \internal
1107  * \brief Add a "last written" attribute to an XML element, set to current time
1108  *
1109  * \param[in] xe  XML element to add attribute to
1110  *
1111  * \return Value that was set, or NULL on error
1112  */
1113 const char *
1114 pcmk__xe_add_last_written(xmlNode *xe)
     /* [previous][next][first][last][top][bottom][index][help] */
1115 {
1116     const char *now_str = pcmk__epoch2str(NULL);
1117 
1118     return crm_xml_add(xe, XML_CIB_ATTR_WRITTEN,
1119                        now_str ? now_str : "Could not determine current time");
1120 }
1121 
1122 /*!
1123  * \brief Sanitize a string so it is usable as an XML ID
1124  *
1125  * \param[in,out] id  String to sanitize
1126  */
1127 void
1128 crm_xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
1129 {
1130     char *c;
1131 
1132     for (c = id; *c; ++c) {
1133         /* @TODO Sanitize more comprehensively */
1134         switch (*c) {
1135             case ':':
1136             case '#':
1137                 *c = '.';
1138         }
1139     }
1140 }
1141 
1142 /*!
1143  * \brief Set the ID of an XML element using a format
1144  *
1145  * \param[in,out] xml  XML element
1146  * \param[in]     fmt  printf-style format
1147  * \param[in]     ...  any arguments required by format
1148  */
1149 void
1150 crm_xml_set_id(xmlNode *xml, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
1151 {
1152     va_list ap;
1153     int len = 0;
1154     char *id = NULL;
1155 
1156     /* equivalent to crm_strdup_printf() */
1157     va_start(ap, format);
1158     len = vasprintf(&id, format, ap);
1159     va_end(ap);
1160     CRM_ASSERT(len > 0);
1161 
1162     crm_xml_sanitize_id(id);
1163     crm_xml_add(xml, XML_ATTR_ID, id);
1164     free(id);
1165 }
1166 
1167 /*!
1168  * \internal
1169  * \brief Write XML to a file stream
1170  *
1171  * \param[in] xml_node  XML to write
1172  * \param[in] filename  Name of file being written (for logging only)
1173  * \param[in] stream    Open file stream corresponding to filename
1174  * \param[in] compress  Whether to compress XML before writing
1175  * \param[out] nbytes   Number of bytes written
1176  *
1177  * \return Standard Pacemaker return code
1178  */
1179 static int
1180 write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream,
     /* [previous][next][first][last][top][bottom][index][help] */
1181                  bool compress, unsigned int *nbytes)
1182 {
1183     int rc = pcmk_rc_ok;
1184     char *buffer = NULL;
1185 
1186     *nbytes = 0;
1187     crm_log_xml_trace(xml_node, "writing");
1188 
1189     buffer = dump_xml_formatted(xml_node);
1190     CRM_CHECK(buffer && strlen(buffer),
1191               crm_log_xml_warn(xml_node, "formatting failed");
1192               rc = pcmk_rc_error;
1193               goto bail);
1194 
1195     if (compress) {
1196         unsigned int in = 0;
1197         BZFILE *bz_file = NULL;
1198 
1199         rc = BZ_OK;
1200         bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
1201         if (rc != BZ_OK) {
1202             crm_warn("Not compressing %s: could not prepare file stream: %s "
1203                      CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1204         } else {
1205             BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
1206             if (rc != BZ_OK) {
1207                 crm_warn("Not compressing %s: could not compress data: %s "
1208                          CRM_XS " bzerror=%d errno=%d",
1209                          filename, bz2_strerror(rc), rc, errno);
1210             }
1211         }
1212 
1213         if (rc == BZ_OK) {
1214             BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
1215             if (rc != BZ_OK) {
1216                 crm_warn("Not compressing %s: could not write compressed data: %s "
1217                          CRM_XS " bzerror=%d errno=%d",
1218                          filename, bz2_strerror(rc), rc, errno);
1219                 *nbytes = 0; // retry without compression
1220             } else {
1221                 crm_trace("Compressed XML for %s from %u bytes to %u",
1222                           filename, in, *nbytes);
1223             }
1224         }
1225         rc = pcmk_rc_ok; // Either true, or we'll retry without compression
1226     }
1227 
1228     if (*nbytes == 0) {
1229         rc = fprintf(stream, "%s", buffer);
1230         if (rc < 0) {
1231             rc = errno;
1232             crm_perror(LOG_ERR, "writing %s", filename);
1233         } else {
1234             *nbytes = (unsigned int) rc;
1235             rc = pcmk_rc_ok;
1236         }
1237     }
1238 
1239   bail:
1240 
1241     if (fflush(stream) != 0) {
1242         rc = errno;
1243         crm_perror(LOG_ERR, "flushing %s", filename);
1244     }
1245 
1246     /* Don't report error if the file does not support synchronization */
1247     if (fsync(fileno(stream)) < 0 && errno != EROFS  && errno != EINVAL) {
1248         rc = errno;
1249         crm_perror(LOG_ERR, "synchronizing %s", filename);
1250     }
1251 
1252     fclose(stream);
1253 
1254     crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
1255     free(buffer);
1256 
1257     return rc;
1258 }
1259 
1260 /*!
1261  * \brief Write XML to a file descriptor
1262  *
1263  * \param[in] xml_node  XML to write
1264  * \param[in] filename  Name of file being written (for logging only)
1265  * \param[in] fd        Open file descriptor corresponding to filename
1266  * \param[in] compress  Whether to compress XML before writing
1267  *
1268  * \return Number of bytes written on success, -errno otherwise
1269  */
1270 int
1271 write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
1272 {
1273     FILE *stream = NULL;
1274     unsigned int nbytes = 0;
1275     int rc = pcmk_rc_ok;
1276 
1277     CRM_CHECK(xml_node && (fd > 0), return -EINVAL);
1278     stream = fdopen(fd, "w");
1279     if (stream == NULL) {
1280         return -errno;
1281     }
1282     rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1283     if (rc != pcmk_rc_ok) {
1284         return pcmk_rc2legacy(rc);
1285     }
1286     return (int) nbytes;
1287 }
1288 
1289 /*!
1290  * \brief Write XML to a file
1291  *
1292  * \param[in] xml_node  XML to write
1293  * \param[in] filename  Name of file to write
1294  * \param[in] compress  Whether to compress XML before writing
1295  *
1296  * \return Number of bytes written on success, -errno otherwise
1297  */
1298 int
1299 write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
1300 {
1301     FILE *stream = NULL;
1302     unsigned int nbytes = 0;
1303     int rc = pcmk_rc_ok;
1304 
1305     CRM_CHECK(xml_node && filename, return -EINVAL);
1306     stream = fopen(filename, "w");
1307     if (stream == NULL) {
1308         return -errno;
1309     }
1310     rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1311     if (rc != pcmk_rc_ok) {
1312         return pcmk_rc2legacy(rc);
1313     }
1314     return (int) nbytes;
1315 }
1316 
1317 // Replace a portion of a dynamically allocated string (reallocating memory)
1318 static char *
1319 replace_text(char *text, int start, size_t *length, const char *replace)
     /* [previous][next][first][last][top][bottom][index][help] */
1320 {
1321     size_t offset = strlen(replace) - 1; // We have space for 1 char already
1322 
1323     *length += offset;
1324     text = pcmk__realloc(text, *length);
1325 
1326     for (size_t lpc = (*length) - 1; lpc > (start + offset); lpc--) {
1327         text[lpc] = text[lpc - offset];
1328     }
1329 
1330     memcpy(text + start, replace, offset + 1);
1331     return text;
1332 }
1333 
1334 /*!
1335  * \brief Replace special characters with their XML escape sequences
1336  *
1337  * \param[in] text  Text to escape
1338  *
1339  * \return Newly allocated string equivalent to \p text but with special
1340  *         characters replaced with XML escape sequences (or NULL if \p text
1341  *         is NULL)
1342  */
1343 char *
1344 crm_xml_escape(const char *text)
     /* [previous][next][first][last][top][bottom][index][help] */
1345 {
1346     size_t length;
1347     char *copy;
1348 
1349     /*
1350      * When xmlCtxtReadDoc() parses &lt; and friends in a
1351      * value, it converts them to their human readable
1352      * form.
1353      *
1354      * If one uses xmlNodeDump() to convert it back to a
1355      * string, all is well, because special characters are
1356      * converted back to their escape sequences.
1357      *
1358      * However xmlNodeDump() is randomly dog slow, even with the same
1359      * input. So we need to replicate the escaping in our custom
1360      * version so that the result can be re-parsed by xmlCtxtReadDoc()
1361      * when necessary.
1362      */
1363 
1364     if (text == NULL) {
1365         return NULL;
1366     }
1367 
1368     length = 1 + strlen(text);
1369     copy = strdup(text);
1370     CRM_ASSERT(copy != NULL);
1371     for (size_t index = 0; index < length; index++) {
1372         switch (copy[index]) {
1373             case 0:
1374                 break;
1375             case '<':
1376                 copy = replace_text(copy, index, &length, "&lt;");
1377                 break;
1378             case '>':
1379                 copy = replace_text(copy, index, &length, "&gt;");
1380                 break;
1381             case '"':
1382                 copy = replace_text(copy, index, &length, "&quot;");
1383                 break;
1384             case '\'':
1385                 copy = replace_text(copy, index, &length, "&apos;");
1386                 break;
1387             case '&':
1388                 copy = replace_text(copy, index, &length, "&amp;");
1389                 break;
1390             case '\t':
1391                 /* Might as well just expand to a few spaces... */
1392                 copy = replace_text(copy, index, &length, "    ");
1393                 break;
1394             case '\n':
1395                 copy = replace_text(copy, index, &length, "\\n");
1396                 break;
1397             case '\r':
1398                 copy = replace_text(copy, index, &length, "\\r");
1399                 break;
1400             default:
1401                 /* Check for and replace non-printing characters with their octal equivalent */
1402                 if(copy[index] < ' ' || copy[index] > '~') {
1403                     char *replace = crm_strdup_printf("\\%.3o", copy[index]);
1404 
1405                     copy = replace_text(copy, index, &length, replace);
1406                     free(replace);
1407                 }
1408         }
1409     }
1410     return copy;
1411 }
1412 
1413 static inline void
1414 dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
     /* [previous][next][first][last][top][bottom][index][help] */
1415 {
1416     char *p_value = NULL;
1417     const char *p_name = NULL;
1418     xml_private_t *p = NULL;
1419 
1420     CRM_ASSERT(buffer != NULL);
1421     if (attr == NULL || attr->children == NULL) {
1422         return;
1423     }
1424 
1425     p = attr->_private;
1426     if (p && pcmk_is_set(p->flags, pcmk__xf_deleted)) {
1427         return;
1428     }
1429 
1430     p_name = (const char *)attr->name;
1431     p_value = crm_xml_escape((const char *)attr->children->content);
1432     buffer_print(*buffer, *max, *offset, " %s=\"%s\"",
1433                  p_name, crm_str(p_value));
1434     free(p_value);
1435 }
1436 
1437 // Log an XML element (and any children) in a formatted way
1438 void
1439 pcmk__xe_log(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
1440              const char *prefix, xmlNode *data, int depth, int options)
1441 {
1442     int max = 0;
1443     int offset = 0;
1444     const char *name = NULL;
1445     const char *hidden = NULL;
1446 
1447     xmlNode *child = NULL;
1448 
1449     if ((data == NULL) || (log_level == LOG_NEVER)) {
1450         return;
1451     }
1452 
1453     name = crm_element_name(data);
1454 
1455     if (pcmk_is_set(options, xml_log_option_open)) {
1456         char *buffer = NULL;
1457 
1458         insert_prefix(options, &buffer, &offset, &max, depth);
1459 
1460         if (data->type == XML_COMMENT_NODE) {
1461             buffer_print(buffer, max, offset, "<!--%s-->", data->content);
1462 
1463         } else {
1464             buffer_print(buffer, max, offset, "<%s", name);
1465 
1466             hidden = crm_element_value(data, "hidden");
1467             for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL;
1468                  a = a->next) {
1469 
1470                 xml_private_t *p = a->_private;
1471                 const char *p_name = (const char *) a->name;
1472                 const char *p_value = pcmk__xml_attr_value(a);
1473                 char *p_copy = NULL;
1474 
1475                 if (pcmk_is_set(p->flags, pcmk__xf_deleted)) {
1476                     continue;
1477                 } else if (pcmk_any_flags_set(options,
1478                                               xml_log_option_diff_plus
1479                                               |xml_log_option_diff_minus)
1480                            && (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
1481                     continue;
1482 
1483                 } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
1484                     p_copy = strdup("*****");
1485 
1486                 } else {
1487                     p_copy = crm_xml_escape(p_value);
1488                 }
1489 
1490                 buffer_print(buffer, max, offset, " %s=\"%s\"",
1491                              p_name, crm_str(p_copy));
1492                 free(p_copy);
1493             }
1494 
1495             if(xml_has_children(data) == FALSE) {
1496                 buffer_print(buffer, max, offset, "/>");
1497 
1498             } else if (pcmk_is_set(options, xml_log_option_children)) {
1499                 buffer_print(buffer, max, offset, ">");
1500 
1501             } else {
1502                 buffer_print(buffer, max, offset, "/>");
1503             }
1504         }
1505 
1506         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
1507         free(buffer);
1508     }
1509 
1510     if(data->type == XML_COMMENT_NODE) {
1511         return;
1512 
1513     } else if(xml_has_children(data) == FALSE) {
1514         return;
1515 
1516     } else if (pcmk_is_set(options, xml_log_option_children)) {
1517         offset = 0;
1518         max = 0;
1519 
1520         for (child = pcmk__xml_first_child(data); child != NULL;
1521              child = pcmk__xml_next(child)) {
1522             pcmk__xe_log(log_level, file, function, line, prefix, child,
1523                          depth + 1,
1524                          options|xml_log_option_open|xml_log_option_close);
1525         }
1526     }
1527 
1528     if (pcmk_is_set(options, xml_log_option_close)) {
1529         char *buffer = NULL;
1530 
1531         insert_prefix(options, &buffer, &offset, &max, depth);
1532         buffer_print(buffer, max, offset, "</%s>", name);
1533 
1534         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
1535         free(buffer);
1536     }
1537 }
1538 
1539 // Log XML portions that have been marked as changed
1540 static void
1541 log_xml_changes(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
1542                 const char *prefix, xmlNode *data, int depth, int options)
1543 {
1544     xml_private_t *p;
1545     char *prefix_m = NULL;
1546     xmlNode *child = NULL;
1547 
1548     if ((data == NULL) || (log_level == LOG_NEVER)) {
1549         return;
1550     }
1551 
1552     p = data->_private;
1553 
1554     prefix_m = strdup(prefix);
1555     prefix_m[1] = '+';
1556 
1557     if (pcmk_all_flags_set(p->flags, pcmk__xf_dirty|pcmk__xf_created)) {
1558         /* Continue and log full subtree */
1559         pcmk__xe_log(log_level, file, function, line, prefix_m, data, depth,
1560                      options|xml_log_option_open|xml_log_option_close
1561                         |xml_log_option_children);
1562 
1563     } else if (pcmk_is_set(p->flags, pcmk__xf_dirty)) {
1564         char *spaces = calloc(80, 1);
1565         int s_count = 0, s_max = 80;
1566         char *prefix_del = NULL;
1567         char *prefix_moved = NULL;
1568         const char *flags = prefix;
1569 
1570         insert_prefix(options, &spaces, &s_count, &s_max, depth);
1571         prefix_del = strdup(prefix);
1572         prefix_del[0] = '-';
1573         prefix_del[1] = '-';
1574         prefix_moved = strdup(prefix);
1575         prefix_moved[1] = '~';
1576 
1577         if (pcmk_is_set(p->flags, pcmk__xf_moved)) {
1578             flags = prefix_moved;
1579         } else {
1580             flags = prefix;
1581         }
1582 
1583         pcmk__xe_log(log_level, file, function, line, flags, data, depth,
1584                      options|xml_log_option_open);
1585 
1586         for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1587             const char *aname = (const char*) a->name;
1588 
1589             p = a->_private;
1590             if (pcmk_is_set(p->flags, pcmk__xf_deleted)) {
1591                 const char *value = crm_element_value(data, aname);
1592                 flags = prefix_del;
1593                 do_crm_log_alias(log_level, file, function, line,
1594                                  "%s %s @%s=%s", flags, spaces, aname, value);
1595 
1596             } else if (pcmk_is_set(p->flags, pcmk__xf_dirty)) {
1597                 const char *value = crm_element_value(data, aname);
1598 
1599                 if (pcmk_is_set(p->flags, pcmk__xf_created)) {
1600                     flags = prefix_m;
1601 
1602                 } else if (pcmk_is_set(p->flags, pcmk__xf_modified)) {
1603                     flags = prefix;
1604 
1605                 } else if (pcmk_is_set(p->flags, pcmk__xf_moved)) {
1606                     flags = prefix_moved;
1607 
1608                 } else {
1609                     flags = prefix;
1610                 }
1611                 do_crm_log_alias(log_level, file, function, line,
1612                                  "%s %s @%s=%s", flags, spaces, aname, value);
1613             }
1614         }
1615         free(prefix_moved);
1616         free(prefix_del);
1617         free(spaces);
1618 
1619         for (child = pcmk__xml_first_child(data); child != NULL;
1620              child = pcmk__xml_next(child)) {
1621             log_xml_changes(log_level, file, function, line, prefix, child,
1622                             depth + 1, options);
1623         }
1624 
1625         pcmk__xe_log(log_level, file, function, line, prefix, data, depth,
1626                      options|xml_log_option_close);
1627 
1628     } else {
1629         for (child = pcmk__xml_first_child(data); child != NULL;
1630              child = pcmk__xml_next(child)) {
1631             log_xml_changes(log_level, file, function, line, prefix, child,
1632                             depth + 1, options);
1633         }
1634     }
1635 
1636     free(prefix_m);
1637 
1638 }
1639 
1640 void
1641 log_data_element(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
1642                  const char *prefix, xmlNode * data, int depth, int options)
1643 {
1644     xmlNode *a_child = NULL;
1645 
1646     char *prefix_m = NULL;
1647 
1648     if (log_level == LOG_NEVER) {
1649         return;
1650     }
1651 
1652     if (prefix == NULL) {
1653         prefix = "";
1654     }
1655 
1656     /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
1657     if (data == NULL) {
1658         do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
1659                          "No data to dump as XML");
1660         return;
1661     }
1662 
1663     if (pcmk_is_set(options, xml_log_option_dirty_add)) {
1664         log_xml_changes(log_level, file, function, line, prefix, data, depth,
1665                         options);
1666         return;
1667     }
1668 
1669     if (pcmk_is_set(options, xml_log_option_formatted)) {
1670         if (pcmk_is_set(options, xml_log_option_diff_plus)
1671             && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1672             options |= xml_log_option_diff_all;
1673             prefix_m = strdup(prefix);
1674             prefix_m[1] = '+';
1675             prefix = prefix_m;
1676 
1677         } else if (pcmk_is_set(options, xml_log_option_diff_minus)
1678                    && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1679             options |= xml_log_option_diff_all;
1680             prefix_m = strdup(prefix);
1681             prefix_m[1] = '-';
1682             prefix = prefix_m;
1683         }
1684     }
1685 
1686     if (pcmk_is_set(options, xml_log_option_diff_short)
1687                && !pcmk_is_set(options, xml_log_option_diff_all)) {
1688         /* Still searching for the actual change */
1689         for (a_child = pcmk__xml_first_child(data); a_child != NULL;
1690              a_child = pcmk__xml_next(a_child)) {
1691             log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
1692         }
1693     } else {
1694         pcmk__xe_log(log_level, file, function, line, prefix, data, depth,
1695                      options|xml_log_option_open|xml_log_option_close
1696                         |xml_log_option_children);
1697     }
1698     free(prefix_m);
1699 }
1700 
1701 static void
1702 dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
     /* [previous][next][first][last][top][bottom][index][help] */
1703 {
1704     for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1705         if (!pcmk__xa_filterable((const char *) (a->name))) {
1706             dump_xml_attr(a, options, buffer, offset, max);
1707         }
1708     }
1709 }
1710 
1711 static void
1712 dump_xml_element(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1713 {
1714     const char *name = NULL;
1715 
1716     CRM_ASSERT(max != NULL);
1717     CRM_ASSERT(offset != NULL);
1718     CRM_ASSERT(buffer != NULL);
1719 
1720     if (data == NULL) {
1721         crm_trace("Nothing to dump");
1722         return;
1723     }
1724 
1725     if (*buffer == NULL) {
1726         *offset = 0;
1727         *max = 0;
1728     }
1729 
1730     name = crm_element_name(data);
1731     CRM_ASSERT(name != NULL);
1732 
1733     insert_prefix(options, buffer, offset, max, depth);
1734     buffer_print(*buffer, *max, *offset, "<%s", name);
1735 
1736     if (options & xml_log_option_filtered) {
1737         dump_filtered_xml(data, options, buffer, offset, max);
1738 
1739     } else {
1740         for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1741             dump_xml_attr(a, options, buffer, offset, max);
1742         }
1743     }
1744 
1745     if (data->children == NULL) {
1746         buffer_print(*buffer, *max, *offset, "/>");
1747 
1748     } else {
1749         buffer_print(*buffer, *max, *offset, ">");
1750     }
1751 
1752     if (options & xml_log_option_formatted) {
1753         buffer_print(*buffer, *max, *offset, "\n");
1754     }
1755 
1756     if (data->children) {
1757         xmlNode *xChild = NULL;
1758         for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
1759             pcmk__xml2text(xChild, options, buffer, offset, max, depth + 1);
1760         }
1761 
1762         insert_prefix(options, buffer, offset, max, depth);
1763         buffer_print(*buffer, *max, *offset, "</%s>", name);
1764 
1765         if (options & xml_log_option_formatted) {
1766             buffer_print(*buffer, *max, *offset, "\n");
1767         }
1768     }
1769 }
1770 
1771 static void
1772 dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1773 {
1774     CRM_ASSERT(max != NULL);
1775     CRM_ASSERT(offset != NULL);
1776     CRM_ASSERT(buffer != NULL);
1777 
1778     if (data == NULL) {
1779         crm_trace("Nothing to dump");
1780         return;
1781     }
1782 
1783     if (*buffer == NULL) {
1784         *offset = 0;
1785         *max = 0;
1786     }
1787 
1788     insert_prefix(options, buffer, offset, max, depth);
1789 
1790     buffer_print(*buffer, *max, *offset, "%s", data->content);
1791 
1792     if (options & xml_log_option_formatted) {
1793         buffer_print(*buffer, *max, *offset, "\n");
1794     }
1795 }
1796 
1797 static void
1798 dump_xml_cdata(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1799 {
1800     CRM_ASSERT(max != NULL);
1801     CRM_ASSERT(offset != NULL);
1802     CRM_ASSERT(buffer != NULL);
1803 
1804     if (data == NULL) {
1805         crm_trace("Nothing to dump");
1806         return;
1807     }
1808 
1809     if (*buffer == NULL) {
1810         *offset = 0;
1811         *max = 0;
1812     }
1813 
1814     insert_prefix(options, buffer, offset, max, depth);
1815 
1816     buffer_print(*buffer, *max, *offset, "<![CDATA[");
1817     buffer_print(*buffer, *max, *offset, "%s", data->content);
1818     buffer_print(*buffer, *max, *offset, "]]>");
1819 
1820     if (options & xml_log_option_formatted) {
1821         buffer_print(*buffer, *max, *offset, "\n");
1822     }
1823 }
1824 
1825 static void
1826 dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
1827 {
1828     CRM_ASSERT(max != NULL);
1829     CRM_ASSERT(offset != NULL);
1830     CRM_ASSERT(buffer != NULL);
1831 
1832     if (data == NULL) {
1833         crm_trace("Nothing to dump");
1834         return;
1835     }
1836 
1837     if (*buffer == NULL) {
1838         *offset = 0;
1839         *max = 0;
1840     }
1841 
1842     insert_prefix(options, buffer, offset, max, depth);
1843 
1844     buffer_print(*buffer, *max, *offset, "<!--");
1845     buffer_print(*buffer, *max, *offset, "%s", data->content);
1846     buffer_print(*buffer, *max, *offset, "-->");
1847 
1848     if (options & xml_log_option_formatted) {
1849         buffer_print(*buffer, *max, *offset, "\n");
1850     }
1851 }
1852 
1853 #define PCMK__XMLDUMP_STATS 0
1854 
1855 /*!
1856  * \internal
1857  * \brief Create a text representation of an XML object
1858  *
1859  * \param[in]     data     XML to convert
1860  * \param[in]     options  Group of enum xml_log_options flags
1861  * \param[in,out] buffer   Buffer to store text in (may be reallocated)
1862  * \param[in,out] offset   Current position of null terminator within \p buffer
1863  * \param[in,out] max      Current size of \p buffer in bytes
1864  * \param[in]     depth    Current indentation level
1865  */
1866 void
1867 pcmk__xml2text(xmlNode *data, int options, char **buffer, int *offset,
     /* [previous][next][first][last][top][bottom][index][help] */
1868                int *max, int depth)
1869 {
1870     if(data == NULL) {
1871         *offset = 0;
1872         *max = 0;
1873         return;
1874     }
1875 
1876     if (!pcmk_is_set(options, xml_log_option_filtered)
1877         && pcmk_is_set(options, xml_log_option_full_fledged)) {
1878         /* libxml's serialization reuse is a good idea, sadly we cannot
1879            apply it for the filtered cases (preceding filtering pass
1880            would preclude further reuse of such in-situ modified XML
1881            in generic context and is likely not a win performance-wise),
1882            and there's also a historically unstable throughput argument
1883            (likely stemming from memory allocation overhead, eventhough
1884            that shall be minimized with defaults preset in crm_xml_init) */
1885 #if (PCMK__XMLDUMP_STATS - 0)
1886         time_t next, new = time(NULL);
1887 #endif
1888         xmlDoc *doc;
1889         xmlOutputBuffer *xml_buffer;
1890 
1891         doc = getDocPtr(data);
1892         /* doc will only be NULL if data is */
1893         CRM_CHECK(doc != NULL, return);
1894 
1895         xml_buffer = xmlAllocOutputBuffer(NULL);
1896         CRM_ASSERT(xml_buffer != NULL);
1897 
1898         /* XXX we could setup custom allocation scheme for the particular
1899                buffer, but it's subsumed with crm_xml_init that needs to
1900                be invoked prior to entering this function as such, since
1901                its other branch vitally depends on it -- what can be done
1902                about this all is to have a facade parsing functions that
1903                would 100% mark entering libxml code for us, since we don't
1904                do anything as crazy as swapping out the binary form of the
1905                parsed tree (but those would need to be strictly used as
1906                opposed to libxml's raw functions) */
1907 
1908         xmlNodeDumpOutput(xml_buffer, doc, data, 0,
1909                           (options & xml_log_option_formatted), NULL);
1910         /* attempt adding final NL - failing shouldn't be fatal here */
1911         (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n");
1912         if (xml_buffer->buffer != NULL) {
1913             buffer_print(*buffer, *max, *offset, "%s",
1914                          (char *) xmlBufContent(xml_buffer->buffer));
1915         }
1916 
1917 #if (PCMK__XMLDUMP_STATS - 0)
1918         next = time(NULL);
1919         if ((now + 1) < next) {
1920             crm_log_xml_trace(data, "Long time");
1921             crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
1922         }
1923 #endif
1924 
1925         /* asserted allocation before so there should be something to remove */
1926         (void) xmlOutputBufferClose(xml_buffer);
1927         return;
1928     }
1929 
1930     switch(data->type) {
1931         case XML_ELEMENT_NODE:
1932             /* Handle below */
1933             dump_xml_element(data, options, buffer, offset, max, depth);
1934             break;
1935         case XML_TEXT_NODE:
1936             /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
1937             if (options & xml_log_option_text) {
1938                 dump_xml_text(data, options, buffer, offset, max, depth);
1939             }
1940             return;
1941         case XML_COMMENT_NODE:
1942             dump_xml_comment(data, options, buffer, offset, max, depth);
1943             break;
1944         case XML_CDATA_SECTION_NODE:
1945             dump_xml_cdata(data, options, buffer, offset, max, depth);
1946             break;
1947         default:
1948             crm_warn("Unhandled type: %d", data->type);
1949             return;
1950 
1951             /*
1952             XML_ATTRIBUTE_NODE = 2
1953             XML_ENTITY_REF_NODE = 5
1954             XML_ENTITY_NODE = 6
1955             XML_PI_NODE = 7
1956             XML_DOCUMENT_NODE = 9
1957             XML_DOCUMENT_TYPE_NODE = 10
1958             XML_DOCUMENT_FRAG_NODE = 11
1959             XML_NOTATION_NODE = 12
1960             XML_HTML_DOCUMENT_NODE = 13
1961             XML_DTD_NODE = 14
1962             XML_ELEMENT_DECL = 15
1963             XML_ATTRIBUTE_DECL = 16
1964             XML_ENTITY_DECL = 17
1965             XML_NAMESPACE_DECL = 18
1966             XML_XINCLUDE_START = 19
1967             XML_XINCLUDE_END = 20
1968             XML_DOCB_DOCUMENT_NODE = 21
1969             */
1970     }
1971 
1972 }
1973 
1974 /*!
1975  * \internal
1976  * \brief Add a single character to a dynamically allocated buffer
1977  *
1978  * \param[in,out] buffer   Buffer to store text in (may be reallocated)
1979  * \param[in,out] offset   Current position of null terminator within \p buffer
1980  * \param[in,out] max      Current size of \p buffer in bytes
1981  * \param[in]     c        Character to add to \p buffer
1982  */
1983 void
1984 pcmk__buffer_add_char(char **buffer, int *offset, int *max, char c)
     /* [previous][next][first][last][top][bottom][index][help] */
1985 {
1986     buffer_print(*buffer, *max, *offset, "%c", c);
1987 }
1988 
1989 char *
1990 dump_xml_formatted_with_text(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
1991 {
1992     char *buffer = NULL;
1993     int offset = 0, max = 0;
1994 
1995     pcmk__xml2text(an_xml_node,
1996                    xml_log_option_formatted|xml_log_option_full_fledged,
1997                    &buffer, &offset, &max, 0);
1998     return buffer;
1999 }
2000 
2001 char *
2002 dump_xml_formatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2003 {
2004     char *buffer = NULL;
2005     int offset = 0, max = 0;
2006 
2007     pcmk__xml2text(an_xml_node, xml_log_option_formatted, &buffer, &offset,
2008                    &max, 0);
2009     return buffer;
2010 }
2011 
2012 char *
2013 dump_xml_unformatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2014 {
2015     char *buffer = NULL;
2016     int offset = 0, max = 0;
2017 
2018     pcmk__xml2text(an_xml_node, 0, &buffer, &offset, &max, 0);
2019     return buffer;
2020 }
2021 
2022 gboolean
2023 xml_has_children(const xmlNode * xml_root)
     /* [previous][next][first][last][top][bottom][index][help] */
2024 {
2025     if (xml_root != NULL && xml_root->children != NULL) {
2026         return TRUE;
2027     }
2028     return FALSE;
2029 }
2030 
2031 void
2032 xml_remove_prop(xmlNode * obj, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2033 {
2034     if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) {
2035         crm_trace("Cannot remove %s from %s", name, obj->name);
2036 
2037     } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
2038         /* Leave in place (marked for removal) until after the diff is calculated */
2039         xml_private_t *p = NULL;
2040         xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
2041 
2042         p = attr->_private;
2043         set_parent_flag(obj, pcmk__xf_dirty);
2044         pcmk__set_xml_flags(p, pcmk__xf_deleted);
2045     } else {
2046         xmlUnsetProp(obj, (pcmkXmlStr) name);
2047     }
2048 }
2049 
2050 void
2051 save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
2052 {
2053     char *f = NULL;
2054 
2055     if (filename == NULL) {
2056         char *uuid = crm_generate_uuid();
2057 
2058         f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
2059         filename = f;
2060         free(uuid);
2061     }
2062 
2063     crm_info("Saving %s to %s", desc, filename);
2064     write_xml_file(xml, filename, FALSE);
2065     free(f);
2066 }
2067 
2068 /*!
2069  * \internal
2070  * \brief Set a flag on all attributes of an XML element
2071  *
2072  * \param[in,out] xml   XML node to set flags on
2073  * \param[in]     flag  XML private flag to set
2074  */
2075 static void
2076 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
2077 {
2078     for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
2079         pcmk__set_xml_flags((xml_private_t *) (attr->_private), flag);
2080     }
2081 }
2082 
2083 /*!
2084  * \internal
2085  * \brief Add an XML attribute to a node, marked as deleted
2086  *
2087  * When calculating XML changes, we need to know when an attribute has been
2088  * deleted. Add the attribute back to the new XML, so that we can check the
2089  * removal against ACLs, and mark it as deleted for later removal after
2090  * differences have been calculated.
2091  */
2092 static void
2093 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
2094                   const char *old_value)
2095 {
2096     xml_private_t *p = new_xml->doc->_private;
2097     xmlAttr *attr = NULL;
2098 
2099     // Prevent the dirty flag being set recursively upwards
2100     pcmk__clear_xml_flags(p, pcmk__xf_tracking);
2101 
2102     // Restore the old value (and the tracking flag)
2103     attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2104     pcmk__set_xml_flags(p, pcmk__xf_tracking);
2105 
2106     // Reset flags (so the attribute doesn't appear as newly created)
2107     p = attr->_private;
2108     p->flags = 0;
2109 
2110     // Check ACLs and mark restored value for later removal
2111     xml_remove_prop(new_xml, attr_name);
2112 
2113     crm_trace("XML attribute %s=%s was removed from %s",
2114               attr_name, old_value, element);
2115 }
2116 
2117 /*
2118  * \internal
2119  * \brief Check ACLs for a changed XML attribute
2120  */
2121 static void
2122 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
2123                   const char *old_value)
2124 {
2125     char *vcopy = crm_element_value_copy(new_xml, attr_name);
2126 
2127     crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
2128               attr_name, old_value, vcopy, element);
2129 
2130     // Restore the original value
2131     xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2132 
2133     // Change it back to the new value, to check ACLs
2134     crm_xml_add(new_xml, attr_name, vcopy);
2135     free(vcopy);
2136 }
2137 
2138 /*!
2139  * \internal
2140  * \brief Mark an XML attribute as having changed position
2141  */
2142 static void
2143 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
     /* [previous][next][first][last][top][bottom][index][help] */
2144                 xmlAttr *new_attr, int p_old, int p_new)
2145 {
2146     xml_private_t *p = new_attr->_private;
2147 
2148     crm_trace("XML attribute %s moved from position %d to %d in %s",
2149               old_attr->name, p_old, p_new, element);
2150 
2151     // Mark document, element, and all element's parents as changed
2152     mark_xml_node_dirty(new_xml);
2153 
2154     // Mark attribute as changed
2155     pcmk__set_xml_flags(p, pcmk__xf_dirty|pcmk__xf_moved);
2156 
2157     p = (p_old > p_new)? old_attr->_private : new_attr->_private;
2158     pcmk__set_xml_flags(p, pcmk__xf_skip);
2159 }
2160 
2161 /*!
2162  * \internal
2163  * \brief Calculate differences in all previously existing XML attributes
2164  */
2165 static void
2166 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2167 {
2168     xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
2169 
2170     while (attr_iter != NULL) {
2171         xmlAttr *old_attr = attr_iter;
2172         xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
2173         const char *name = (const char *) attr_iter->name;
2174         const char *old_value = crm_element_value(old_xml, name);
2175 
2176         attr_iter = attr_iter->next;
2177         if (new_attr == NULL) {
2178             mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
2179                               old_value);
2180 
2181         } else {
2182             xml_private_t *p = new_attr->_private;
2183             int new_pos = pcmk__xml_position((xmlNode*) new_attr,
2184                                              pcmk__xf_skip);
2185             int old_pos = pcmk__xml_position((xmlNode*) old_attr,
2186                                              pcmk__xf_skip);
2187             const char *new_value = crm_element_value(new_xml, name);
2188 
2189             // This attribute isn't new
2190             pcmk__clear_xml_flags(p, pcmk__xf_created);
2191 
2192             if (strcmp(new_value, old_value) != 0) {
2193                 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
2194                                   old_value);
2195 
2196             } else if ((old_pos != new_pos)
2197                        && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
2198                 mark_attr_moved(new_xml, (const char *) old_xml->name,
2199                                 old_attr, new_attr, old_pos, new_pos);
2200             }
2201         }
2202     }
2203 }
2204 
2205 /*!
2206  * \internal
2207  * \brief Check all attributes in new XML for creation
2208  */
2209 static void
2210 mark_created_attrs(xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2211 {
2212     xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
2213 
2214     while (attr_iter != NULL) {
2215         xmlAttr *new_attr = attr_iter;
2216         xml_private_t *p = attr_iter->_private;
2217 
2218         attr_iter = attr_iter->next;
2219         if (pcmk_is_set(p->flags, pcmk__xf_created)) {
2220             const char *attr_name = (const char *) new_attr->name;
2221 
2222             crm_trace("Created new attribute %s=%s in %s",
2223                       attr_name, crm_element_value(new_xml, attr_name),
2224                       new_xml->name);
2225 
2226             /* Check ACLs (we can't use the remove-then-create trick because it
2227              * would modify the attribute position).
2228              */
2229             if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
2230                 pcmk__mark_xml_attr_dirty(new_attr);
2231             } else {
2232                 // Creation was not allowed, so remove the attribute
2233                 xmlUnsetProp(new_xml, new_attr->name);
2234             }
2235         }
2236     }
2237 }
2238 
2239 /*!
2240  * \internal
2241  * \brief Calculate differences in attributes between two XML nodes
2242  */
2243 static void
2244 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2245 {
2246     set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
2247     xml_diff_old_attrs(old_xml, new_xml);
2248     mark_created_attrs(new_xml);
2249 }
2250 
2251 /*!
2252  * \internal
2253  * \brief Add an XML child element to a node, marked as deleted
2254  *
2255  * When calculating XML changes, we need to know when a child element has been
2256  * deleted. Add the child back to the new XML, so that we can check the removal
2257  * against ACLs, and mark it as deleted for later removal after differences have
2258  * been calculated.
2259  */
2260 static void
2261 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
     /* [previous][next][first][last][top][bottom][index][help] */
2262 {
2263     // Re-create the child element so we can check ACLs
2264     xmlNode *candidate = add_node_copy(new_parent, old_child);
2265 
2266     // Clear flags on new child and its children
2267     reset_xml_node_flags(candidate);
2268 
2269     // Check whether ACLs allow the deletion
2270     pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
2271 
2272     // Remove the child again (which will track it in document's deleted_objs)
2273     free_xml_with_position(candidate,
2274                            pcmk__xml_position(old_child, pcmk__xf_skip));
2275 
2276     if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
2277         pcmk__set_xml_flags((xml_private_t *) (old_child->_private),
2278                             pcmk__xf_skip);
2279     }
2280 }
2281 
2282 static void
2283 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
     /* [previous][next][first][last][top][bottom][index][help] */
2284                  int p_old, int p_new)
2285 {
2286     xml_private_t *p = new_child->_private;
2287 
2288     crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
2289               new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
2290               p_old, p_new, new_parent->name);
2291     mark_xml_node_dirty(new_parent);
2292     pcmk__set_xml_flags(p, pcmk__xf_moved);
2293 
2294     if (p_old > p_new) {
2295         p = old_child->_private;
2296     } else {
2297         p = new_child->_private;
2298     }
2299     pcmk__set_xml_flags(p, pcmk__xf_skip);
2300 }
2301 
2302 // Given original and new XML, mark new XML portions that have changed
2303 static void
2304 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
     /* [previous][next][first][last][top][bottom][index][help] */
2305 {
2306     xmlNode *cIter = NULL;
2307     xml_private_t *p = NULL;
2308 
2309     CRM_CHECK(new_xml != NULL, return);
2310     if (old_xml == NULL) {
2311         pcmk__mark_xml_created(new_xml);
2312         pcmk__apply_creation_acl(new_xml, check_top);
2313         return;
2314     }
2315 
2316     p = new_xml->_private;
2317     CRM_CHECK(p != NULL, return);
2318 
2319     if(p->flags & pcmk__xf_processed) {
2320         /* Avoid re-comparing nodes */
2321         return;
2322     }
2323     pcmk__set_xml_flags(p, pcmk__xf_processed);
2324 
2325     xml_diff_attrs(old_xml, new_xml);
2326 
2327     // Check for differences in the original children
2328     for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
2329         xmlNode *old_child = cIter;
2330         xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
2331 
2332         cIter = pcmk__xml_next(cIter);
2333         if(new_child) {
2334             mark_xml_changes(old_child, new_child, TRUE);
2335 
2336         } else {
2337             mark_child_deleted(old_child, new_xml);
2338         }
2339     }
2340 
2341     // Check for moved or created children
2342     for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
2343         xmlNode *new_child = cIter;
2344         xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
2345 
2346         cIter = pcmk__xml_next(cIter);
2347         if(old_child == NULL) {
2348             // This is a newly created child
2349             p = new_child->_private;
2350             pcmk__set_xml_flags(p, pcmk__xf_skip);
2351             mark_xml_changes(old_child, new_child, TRUE);
2352 
2353         } else {
2354             /* Check for movement, we already checked for differences */
2355             int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
2356             int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
2357 
2358             if(p_old != p_new) {
2359                 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
2360             }
2361         }
2362     }
2363 }
2364 
2365 void
2366 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2367 {
2368     pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
2369     xml_calculate_changes(old_xml, new_xml);
2370 }
2371 
2372 void
2373 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2374 {
2375     CRM_CHECK(pcmk__str_eq(crm_element_name(old_xml), crm_element_name(new_xml), pcmk__str_casei),
2376               return);
2377     CRM_CHECK(pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_casei), return);
2378 
2379     if(xml_tracking_changes(new_xml) == FALSE) {
2380         xml_track_changes(new_xml, NULL, NULL, FALSE);
2381     }
2382 
2383     mark_xml_changes(old_xml, new_xml, FALSE);
2384 }
2385 
2386 gboolean
2387 can_prune_leaf(xmlNode * xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2388 {
2389     xmlNode *cIter = NULL;
2390     gboolean can_prune = TRUE;
2391     const char *name = crm_element_name(xml_node);
2392 
2393     if (pcmk__strcase_any_of(name, XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF,
2394                              XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, NULL)) {
2395         return FALSE;
2396     }
2397 
2398     for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
2399         const char *p_name = (const char *) a->name;
2400 
2401         if (strcmp(p_name, XML_ATTR_ID) == 0) {
2402             continue;
2403         }
2404         can_prune = FALSE;
2405     }
2406 
2407     cIter = pcmk__xml_first_child(xml_node);
2408     while (cIter) {
2409         xmlNode *child = cIter;
2410 
2411         cIter = pcmk__xml_next(cIter);
2412         if (can_prune_leaf(child)) {
2413             free_xml(child);
2414         } else {
2415             can_prune = FALSE;
2416         }
2417     }
2418     return can_prune;
2419 }
2420 
2421 /*!
2422  * \internal
2423  * \brief Find a comment with matching content in specified XML
2424  *
2425  * \param[in] root            XML to search
2426  * \param[in] search_comment  Comment whose content should be searched for
2427  * \param[in] exact           If true, comment must also be at same position
2428  */
2429 xmlNode *
2430 pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
2431 {
2432     xmlNode *a_child = NULL;
2433     int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
2434 
2435     CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
2436 
2437     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
2438          a_child = pcmk__xml_next(a_child)) {
2439         if (exact) {
2440             int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
2441             xml_private_t *p = a_child->_private;
2442 
2443             if (offset < search_offset) {
2444                 continue;
2445 
2446             } else if (offset > search_offset) {
2447                 return NULL;
2448             }
2449 
2450             if (pcmk_is_set(p->flags, pcmk__xf_skip)) {
2451                 continue;
2452             }
2453         }
2454 
2455         if (a_child->type == XML_COMMENT_NODE
2456             && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
2457             return a_child;
2458 
2459         } else if (exact) {
2460             return NULL;
2461         }
2462     }
2463 
2464     return NULL;
2465 }
2466 
2467 /*!
2468  * \internal
2469  * \brief Make one XML comment match another (in content)
2470  *
2471  * \param[in,out] parent   If \p target is NULL and this is not, add or update
2472  *                         comment child of this XML node that matches \p update
2473  * \param[in,out] target   If not NULL, update this XML comment node
2474  * \param[in]     update   Make comment content match this (must not be NULL)
2475  *
2476  * \note At least one of \parent and \target must be non-NULL
2477  */
2478 void
2479 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
     /* [previous][next][first][last][top][bottom][index][help] */
2480 {
2481     CRM_CHECK(update != NULL, return);
2482     CRM_CHECK(update->type == XML_COMMENT_NODE, return);
2483 
2484     if (target == NULL) {
2485         target = pcmk__xc_match(parent, update, false);
2486     }
2487 
2488     if (target == NULL) {
2489         add_node_copy(parent, update);
2490 
2491     } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
2492         xmlFree(target->content);
2493         target->content = xmlStrdup(update->content);
2494     }
2495 }
2496 
2497 /*!
2498  * \internal
2499  * \brief Make one XML tree match another (in children and attributes)
2500  *
2501  * \param[in,out] parent   If \p target is NULL and this is not, add or update
2502  *                         child of this XML node that matches \p update
2503  * \param[in,out] target   If not NULL, update this XML
2504  * \param[in]     update   Make the desired XML match this (must not be NULL)
2505  * \param[in]     as_diff  If true, expand "++" when making attributes match
2506  *
2507  * \note At least one of \parent and \target must be non-NULL
2508  */
2509 void
2510 pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
     /* [previous][next][first][last][top][bottom][index][help] */
2511                  bool as_diff)
2512 {
2513     xmlNode *a_child = NULL;
2514     const char *object_name = NULL,
2515                *object_href = NULL,
2516                *object_href_val = NULL;
2517 
2518 #if XML_PARSER_DEBUG
2519     crm_log_xml_trace("update:", update);
2520     crm_log_xml_trace("target:", target);
2521 #endif
2522 
2523     CRM_CHECK(update != NULL, return);
2524 
2525     if (update->type == XML_COMMENT_NODE) {
2526         pcmk__xc_update(parent, target, update);
2527         return;
2528     }
2529 
2530     object_name = crm_element_name(update);
2531     object_href_val = ID(update);
2532     if (object_href_val != NULL) {
2533         object_href = XML_ATTR_ID;
2534     } else {
2535         object_href_val = crm_element_value(update, XML_ATTR_IDREF);
2536         object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
2537     }
2538 
2539     CRM_CHECK(object_name != NULL, return);
2540     CRM_CHECK(target != NULL || parent != NULL, return);
2541 
2542     if (target == NULL) {
2543         target = pcmk__xe_match(parent, object_name,
2544                                 object_href, object_href_val);
2545     }
2546 
2547     if (target == NULL) {
2548         target = create_xml_node(parent, object_name);
2549         CRM_CHECK(target != NULL, return);
2550 #if XML_PARSER_DEBUG
2551         crm_trace("Added  <%s%s%s%s%s/>", crm_str(object_name),
2552                   object_href ? " " : "",
2553                   object_href ? object_href : "",
2554                   object_href ? "=" : "",
2555                   object_href ? object_href_val : "");
2556 
2557     } else {
2558         crm_trace("Found node <%s%s%s%s%s/> to update", crm_str(object_name),
2559                   object_href ? " " : "",
2560                   object_href ? object_href : "",
2561                   object_href ? "=" : "",
2562                   object_href ? object_href_val : "");
2563 #endif
2564     }
2565 
2566     CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(update),
2567                            pcmk__str_casei),
2568               return);
2569 
2570     if (as_diff == FALSE) {
2571         /* So that expand_plus_plus() gets called */
2572         copy_in_properties(target, update);
2573 
2574     } else {
2575         /* No need for expand_plus_plus(), just raw speed */
2576         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2577              a = a->next) {
2578             const char *p_value = pcmk__xml_attr_value(a);
2579 
2580             /* Remove it first so the ordering of the update is preserved */
2581             xmlUnsetProp(target, a->name);
2582             xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
2583         }
2584     }
2585 
2586     for (a_child = pcmk__xml_first_child(update); a_child != NULL;
2587          a_child = pcmk__xml_next(a_child)) {
2588 #if XML_PARSER_DEBUG
2589         crm_trace("Updating child <%s%s%s%s%s/>", crm_str(object_name),
2590                   object_href ? " " : "",
2591                   object_href ? object_href : "",
2592                   object_href ? "=" : "",
2593                   object_href ? object_href_val : "");
2594 #endif
2595         pcmk__xml_update(target, NULL, a_child, as_diff);
2596     }
2597 
2598 #if XML_PARSER_DEBUG
2599     crm_trace("Finished with <%s%s%s%s%s/>", crm_str(object_name),
2600               object_href ? " " : "",
2601               object_href ? object_href : "",
2602               object_href ? "=" : "",
2603               object_href ? object_href_val : "");
2604 #endif
2605 }
2606 
2607 gboolean
2608 update_xml_child(xmlNode * child, xmlNode * to_update)
     /* [previous][next][first][last][top][bottom][index][help] */
2609 {
2610     gboolean can_update = TRUE;
2611     xmlNode *child_of_child = NULL;
2612 
2613     CRM_CHECK(child != NULL, return FALSE);
2614     CRM_CHECK(to_update != NULL, return FALSE);
2615 
2616     if (!pcmk__str_eq(crm_element_name(to_update), crm_element_name(child), pcmk__str_none)) {
2617         can_update = FALSE;
2618 
2619     } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) {
2620         can_update = FALSE;
2621 
2622     } else if (can_update) {
2623 #if XML_PARSER_DEBUG
2624         crm_log_xml_trace(child, "Update match found...");
2625 #endif
2626         pcmk__xml_update(NULL, child, to_update, false);
2627     }
2628 
2629     for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
2630          child_of_child = pcmk__xml_next(child_of_child)) {
2631         /* only update the first one */
2632         if (can_update) {
2633             break;
2634         }
2635         can_update = update_xml_child(child_of_child, to_update);
2636     }
2637 
2638     return can_update;
2639 }
2640 
2641 int
2642 find_xml_children(xmlNode ** children, xmlNode * root,
     /* [previous][next][first][last][top][bottom][index][help] */
2643                   const char *tag, const char *field, const char *value, gboolean search_matches)
2644 {
2645     int match_found = 0;
2646 
2647     CRM_CHECK(root != NULL, return FALSE);
2648     CRM_CHECK(children != NULL, return FALSE);
2649 
2650     if (tag != NULL && !pcmk__str_eq(tag, crm_element_name(root), pcmk__str_casei)) {
2651 
2652     } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
2653 
2654     } else {
2655         if (*children == NULL) {
2656             *children = create_xml_node(NULL, __func__);
2657         }
2658         add_node_copy(*children, root);
2659         match_found = 1;
2660     }
2661 
2662     if (search_matches || match_found == 0) {
2663         xmlNode *child = NULL;
2664 
2665         for (child = pcmk__xml_first_child(root); child != NULL;
2666              child = pcmk__xml_next(child)) {
2667             match_found += find_xml_children(children, child, tag, field, value, search_matches);
2668         }
2669     }
2670 
2671     return match_found;
2672 }
2673 
2674 gboolean
2675 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
     /* [previous][next][first][last][top][bottom][index][help] */
2676 {
2677     gboolean can_delete = FALSE;
2678     xmlNode *child_of_child = NULL;
2679 
2680     const char *up_id = NULL;
2681     const char *child_id = NULL;
2682     const char *right_val = NULL;
2683 
2684     CRM_CHECK(child != NULL, return FALSE);
2685     CRM_CHECK(update != NULL, return FALSE);
2686 
2687     up_id = ID(update);
2688     child_id = ID(child);
2689 
2690     if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
2691         can_delete = TRUE;
2692     }
2693     if (!pcmk__str_eq(crm_element_name(update), crm_element_name(child), pcmk__str_casei)) {
2694         can_delete = FALSE;
2695     }
2696     if (can_delete && delete_only) {
2697         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2698              a = a->next) {
2699             const char *p_name = (const char *) a->name;
2700             const char *p_value = pcmk__xml_attr_value(a);
2701 
2702             right_val = crm_element_value(child, p_name);
2703             if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
2704                 can_delete = FALSE;
2705             }
2706         }
2707     }
2708 
2709     if (can_delete && parent != NULL) {
2710         crm_log_xml_trace(child, "Delete match found...");
2711         if (delete_only || update == NULL) {
2712             free_xml(child);
2713 
2714         } else {
2715             xmlNode *tmp = copy_xml(update);
2716             xmlDoc *doc = tmp->doc;
2717             xmlNode *old = NULL;
2718 
2719             xml_accept_changes(tmp);
2720             old = xmlReplaceNode(child, tmp);
2721 
2722             if(xml_tracking_changes(tmp)) {
2723                 /* Replaced sections may have included relevant ACLs */
2724                 pcmk__apply_acl(tmp);
2725             }
2726 
2727             xml_calculate_changes(old, tmp);
2728             xmlDocSetRootElement(doc, old);
2729             free_xml(old);
2730         }
2731         child = NULL;
2732         return TRUE;
2733 
2734     } else if (can_delete) {
2735         crm_log_xml_debug(child, "Cannot delete the search root");
2736         can_delete = FALSE;
2737     }
2738 
2739     child_of_child = pcmk__xml_first_child(child);
2740     while (child_of_child) {
2741         xmlNode *next = pcmk__xml_next(child_of_child);
2742 
2743         can_delete = replace_xml_child(child, child_of_child, update, delete_only);
2744 
2745         /* only delete the first one */
2746         if (can_delete) {
2747             child_of_child = NULL;
2748         } else {
2749             child_of_child = next;
2750         }
2751     }
2752 
2753     return can_delete;
2754 }
2755 
2756 xmlNode *
2757 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
     /* [previous][next][first][last][top][bottom][index][help] */
2758 {
2759     xmlNode *child = NULL;
2760     GSList *nvpairs = NULL;
2761     xmlNode *result = NULL;
2762     const char *name = NULL;
2763 
2764     CRM_CHECK(input != NULL, return NULL);
2765 
2766     name = crm_element_name(input);
2767     CRM_CHECK(name != NULL, return NULL);
2768 
2769     result = create_xml_node(parent, name);
2770     nvpairs = pcmk_xml_attrs2nvpairs(input);
2771     nvpairs = pcmk_sort_nvpairs(nvpairs);
2772     pcmk_nvpairs2xml_attrs(nvpairs, result);
2773     pcmk_free_nvpairs(nvpairs);
2774 
2775     for (child = pcmk__xml_first_child(input); child != NULL;
2776          child = pcmk__xml_next(child)) {
2777 
2778         if (recursive) {
2779             sorted_xml(child, result, recursive);
2780         } else {
2781             add_node_copy(result, child);
2782         }
2783     }
2784 
2785     return result;
2786 }
2787 
2788 xmlNode *
2789 first_named_child(const xmlNode *parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2790 {
2791     xmlNode *match = NULL;
2792 
2793     for (match = pcmk__xe_first_child(parent); match != NULL;
2794          match = pcmk__xe_next(match)) {
2795         /*
2796          * name == NULL gives first child regardless of name; this is
2797          * semantically incorrect in this function, but may be necessary
2798          * due to prior use of xml_child_iter_filter
2799          */
2800         if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) {
2801             return match;
2802         }
2803     }
2804     return NULL;
2805 }
2806 
2807 /*!
2808  * \brief Get next instance of same XML tag
2809  *
2810  * \param[in] sibling  XML tag to start from
2811  *
2812  * \return Next sibling XML tag with same name
2813  */
2814 xmlNode *
2815 crm_next_same_xml(const xmlNode *sibling)
     /* [previous][next][first][last][top][bottom][index][help] */
2816 {
2817     xmlNode *match = pcmk__xe_next(sibling);
2818     const char *name = crm_element_name(sibling);
2819 
2820     while (match != NULL) {
2821         if (!strcmp(crm_element_name(match), name)) {
2822             return match;
2823         }
2824         match = pcmk__xe_next(match);
2825     }
2826     return NULL;
2827 }
2828 
2829 void
2830 crm_xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2831 {
2832     static bool init = true;
2833 
2834     if(init) {
2835         init = false;
2836         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
2837          * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
2838          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
2839          * less than 1 second.
2840          */
2841         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
2842 
2843         /* Populate and free the _private field when nodes are created and destroyed */
2844         xmlDeregisterNodeDefault(free_private_data);
2845         xmlRegisterNodeDefault(new_private_data);
2846 
2847         crm_schema_init();
2848     }
2849 }
2850 
2851 void
2852 crm_xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2853 {
2854     crm_info("Cleaning up memory from libxml2");
2855     crm_schema_cleanup();
2856     xmlCleanupParser();
2857 }
2858 
2859 #define XPATH_MAX 512
2860 
2861 xmlNode *
2862 expand_idref(xmlNode * input, xmlNode * top)
     /* [previous][next][first][last][top][bottom][index][help] */
2863 {
2864     const char *tag = NULL;
2865     const char *ref = NULL;
2866     xmlNode *result = input;
2867 
2868     if (result == NULL) {
2869         return NULL;
2870 
2871     } else if (top == NULL) {
2872         top = input;
2873     }
2874 
2875     tag = crm_element_name(result);
2876     ref = crm_element_value(result, XML_ATTR_IDREF);
2877 
2878     if (ref != NULL) {
2879         char *xpath_string = crm_strdup_printf("//%s[@id='%s']", tag, ref);
2880 
2881         result = get_xpath_object(xpath_string, top, LOG_ERR);
2882         if (result == NULL) {
2883             char *nodePath = (char *)xmlGetNodePath(top);
2884 
2885             crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
2886                     crm_str(nodePath));
2887             free(nodePath);
2888         }
2889         free(xpath_string);
2890     }
2891     return result;
2892 }
2893 
2894 void
2895 crm_destroy_xml(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
2896 {
2897     free_xml(data);
2898 }
2899 
2900 char *
2901 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
     /* [previous][next][first][last][top][bottom][index][help] */
2902 {
2903     static const char *base = NULL;
2904     char *ret = NULL;
2905 
2906     if (base == NULL) {
2907         base = getenv("PCMK_schema_directory");
2908     }
2909     if (pcmk__str_empty(base)) {
2910         base = CRM_SCHEMA_DIRECTORY;
2911     }
2912 
2913     switch (ns) {
2914         case pcmk__xml_artefact_ns_legacy_rng:
2915         case pcmk__xml_artefact_ns_legacy_xslt:
2916             ret = strdup(base);
2917             break;
2918         case pcmk__xml_artefact_ns_base_rng:
2919         case pcmk__xml_artefact_ns_base_xslt:
2920             ret = crm_strdup_printf("%s/base", base);
2921             break;
2922         default:
2923             crm_err("XML artefact family specified as %u not recognized", ns);
2924     }
2925     return ret;
2926 }
2927 
2928 char *
2929 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
2930 {
2931     char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
2932 
2933     switch (ns) {
2934         case pcmk__xml_artefact_ns_legacy_rng:
2935         case pcmk__xml_artefact_ns_base_rng:
2936             ret = crm_strdup_printf("%s/%s.rng", base, filespec);
2937             break;
2938         case pcmk__xml_artefact_ns_legacy_xslt:
2939         case pcmk__xml_artefact_ns_base_xslt:
2940             ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
2941             break;
2942         default:
2943             crm_err("XML artefact family specified as %u not recognized", ns);
2944     }
2945     free(base);
2946 
2947     return ret;
2948 }
2949 
2950 void
2951 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
     /* [previous][next][first][last][top][bottom][index][help] */
2952 {
2953     while (true) {
2954         const char *name, *value;
2955 
2956         name = va_arg(pairs, const char *);
2957         if (name == NULL) {
2958             return;
2959         }
2960 
2961         value = va_arg(pairs, const char *);
2962         if (value != NULL) {
2963             crm_xml_add(node, name, value);
2964         }
2965     }
2966 }
2967 
2968 void
2969 pcmk__xe_set_props(xmlNodePtr node, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
2970 {
2971     va_list pairs;
2972     va_start(pairs, node);
2973     pcmk__xe_set_propv(node, pairs);
2974     va_end(pairs);
2975 }
2976 
2977 // Deprecated functions kept only for backward API compatibility
2978 // LCOV_EXCL_START
2979 
2980 #include <crm/common/xml_compat.h>
2981 
2982 xmlNode *
2983 find_entity(xmlNode *parent, const char *node_name, const char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
2984 {
2985     return pcmk__xe_match(parent, node_name,
2986                           ((id == NULL)? id : XML_ATTR_ID), id);
2987 }
2988 
2989 // LCOV_EXCL_STOP
2990 // End deprecated API

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