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

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

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