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

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

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