root/lib/common/xml.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__xml_tree_foreach
  2. pcmk__tracking_xml_changes
  3. pcmk__xml_set_parent_flags
  4. pcmk__set_xml_doc_flag
  5. pcmk__mark_xml_node_dirty
  6. pcmk__xml_reset_node_flags
  7. mark_xml_dirty_created
  8. mark_xml_tree_dirty_created
  9. free_deleted_object
  10. reset_xml_private_data
  11. new_private_data
  12. free_private_data
  13. pcmk__xml_new_private_data
  14. pcmk__xml_free_private_data
  15. xml_track_changes
  16. xml_tracking_changes
  17. xml_document_dirty
  18. pcmk__xml_position
  19. accept_attr_deletions
  20. pcmk__xml_match
  21. xml_accept_changes
  22. pcmk__xml_new_doc
  23. pcmk__xml_free_doc
  24. pcmk__xml_is_name_start_char
  25. pcmk__xml_is_name_char
  26. pcmk__xml_sanitize_id
  27. pcmk__xml_free_node
  28. free_xml_with_position
  29. pcmk__xml_free
  30. pcmk__xml_copy
  31. pcmk__strip_xml_text
  32. pcmk__xml_needs_escape
  33. pcmk__xml_escape
  34. set_attrs_flag
  35. mark_attr_deleted
  36. mark_attr_changed
  37. mark_attr_moved
  38. xml_diff_old_attrs
  39. mark_created_attrs
  40. xml_diff_attrs
  41. mark_child_deleted
  42. mark_child_moved
  43. mark_xml_changes
  44. xml_calculate_significant_changes
  45. xml_calculate_changes
  46. pcmk__xml_init
  47. pcmk__xml_cleanup
  48. pcmk__xml_artefact_root
  49. find_artefact
  50. pcmk__xml_artefact_path
  51. copy_xml
  52. crm_xml_init
  53. crm_xml_cleanup
  54. pcmk_free_xml_subtree
  55. free_xml
  56. crm_xml_sanitize_id

   1 /*
   2  * Copyright 2004-2024 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 <stdarg.h>
  13 #include <stdint.h>                     // uint32_t
  14 #include <stdio.h>
  15 #include <stdlib.h>
  16 #include <string.h>
  17 #include <sys/stat.h>                   // stat(), S_ISREG, etc.
  18 #include <sys/types.h>
  19 
  20 #include <glib.h>                       // gboolean, GString
  21 #include <libxml/parser.h>              // xmlCleanupParser()
  22 #include <libxml/tree.h>                // xmlNode, etc.
  23 #include <libxml/xmlstring.h>           // xmlGetUTF8Char()
  24 
  25 #include <crm/crm.h>
  26 #include <crm/common/xml.h>
  27 #include <crm/common/xml_internal.h>    // PCMK__XML_LOG_BASE, etc.
  28 #include "crmcommon_private.h"
  29 
  30 //! libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
  31 #define XML_VERSION ((pcmkXmlStr) "1.0")
  32 
  33 /*!
  34  * \internal
  35  * \brief Apply a function to each XML node in a tree (pre-order, depth-first)
  36  *
  37  * \param[in,out] xml        XML tree to traverse
  38  * \param[in,out] fn         Function to call for each node (returns \c true to
  39  *                           continue traversing the tree or \c false to stop)
  40  * \param[in,out] user_data  Argument to \p fn
  41  *
  42  * \return \c false if any \p fn call returned \c false, or \c true otherwise
  43  *
  44  * \note This function is recursive.
  45  */
  46 bool
  47 pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
     /* [previous][next][first][last][top][bottom][index][help] */
  48                        void *user_data)
  49 {
  50     if (xml == NULL) {
  51         return true;
  52     }
  53 
  54     if (!fn(xml, user_data)) {
  55         return false;
  56     }
  57 
  58     for (xml = pcmk__xml_first_child(xml); xml != NULL;
  59          xml = pcmk__xml_next(xml)) {
  60 
  61         if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
  62             return false;
  63         }
  64     }
  65     return true;
  66 }
  67 
  68 bool
  69 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
     /* [previous][next][first][last][top][bottom][index][help] */
  70 {
  71     if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
  72         return FALSE;
  73     } else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  74                             pcmk__xf_tracking)) {
  75         return FALSE;
  76     } else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
  77                                     pcmk__xf_lazy)) {
  78         return FALSE;
  79     }
  80     return TRUE;
  81 }
  82 
  83 void
  84 pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
     /* [previous][next][first][last][top][bottom][index][help] */
  85 {
  86     for (; xml != NULL; xml = xml->parent) {
  87         xml_node_private_t *nodepriv = xml->_private;
  88 
  89         if (nodepriv != NULL) {
  90             pcmk__set_xml_flags(nodepriv, flags);
  91         }
  92     }
  93 }
  94 
  95 void
  96 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
  97 {
  98     if (xml != NULL) {
  99         xml_doc_private_t *docpriv = xml->doc->_private;
 100 
 101         pcmk__set_xml_flags(docpriv, flag);
 102     }
 103 }
 104 
 105 // Mark document, element, and all element's parents as changed
 106 void
 107 pcmk__mark_xml_node_dirty(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 108 {
 109     pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
 110     pcmk__xml_set_parent_flags(xml, pcmk__xf_dirty);
 111 }
 112 
 113 /*!
 114  * \internal
 115  * \brief Clear flags on an XML node
 116  *
 117  * \param[in,out] xml        XML node whose flags to reset
 118  * \param[in,out] user_data  Ignored
 119  *
 120  * \return \c true (to continue traversing the tree)
 121  *
 122  * \note This is compatible with \c pcmk__xml_tree_foreach().
 123  */
 124 bool
 125 pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 126 {
 127     xml_node_private_t *nodepriv = xml->_private;
 128 
 129     if (nodepriv != NULL) {
 130         nodepriv->flags = pcmk__xf_none;
 131     }
 132     return true;
 133 }
 134 
 135 /*!
 136  * \internal
 137  * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
 138  *
 139  * \param[in,out] xml        Node whose flags to set
 140  * \param[in]     user_data  Ignored
 141  *
 142  * \return \c true (to continue traversing the tree)
 143  *
 144  * \note This is compatible with \c pcmk__xml_tree_foreach().
 145  */
 146 static bool
 147 mark_xml_dirty_created(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 148 {
 149     xml_node_private_t *nodepriv = xml->_private;
 150 
 151     if (nodepriv != NULL) {
 152         pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
 153     }
 154     return true;
 155 }
 156 
 157 /*!
 158  * \internal
 159  * \brief Mark an XML tree as dirty and created, and mark its parents dirty
 160  *
 161  * Also mark the document dirty.
 162  *
 163  * \param[in,out] xml  Tree to mark as dirty and created
 164  */
 165 static void
 166 mark_xml_tree_dirty_created(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 167 {
 168     pcmk__assert(xml != NULL);
 169 
 170     if (!pcmk__tracking_xml_changes(xml, false)) {
 171         // Tracking is disabled for entire document
 172         return;
 173     }
 174 
 175     // Mark all parents and document dirty
 176     pcmk__mark_xml_node_dirty(xml);
 177 
 178     pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
 179 }
 180 
 181 // Free an XML object previously marked as deleted
 182 static void
 183 free_deleted_object(void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 184 {
 185     if(data) {
 186         pcmk__deleted_xml_t *deleted_obj = data;
 187 
 188         g_free(deleted_obj->path);
 189         free(deleted_obj);
 190     }
 191 }
 192 
 193 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
 194 static void
 195 reset_xml_private_data(xml_doc_private_t *docpriv)
     /* [previous][next][first][last][top][bottom][index][help] */
 196 {
 197     if (docpriv != NULL) {
 198         pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC);
 199 
 200         free(docpriv->user);
 201         docpriv->user = NULL;
 202 
 203         if (docpriv->acls != NULL) {
 204             pcmk__free_acls(docpriv->acls);
 205             docpriv->acls = NULL;
 206         }
 207 
 208         if(docpriv->deleted_objs) {
 209             g_list_free_full(docpriv->deleted_objs, free_deleted_object);
 210             docpriv->deleted_objs = NULL;
 211         }
 212     }
 213 }
 214 
 215 /*!
 216  * \internal
 217  * \brief Allocate and initialize private data for an XML node
 218  *
 219  * \param[in,out] node       XML node whose private data to initialize
 220  * \param[in]     user_data  Ignored
 221  *
 222  * \return \c true (to continue traversing the tree)
 223  *
 224  * \note This is compatible with \c pcmk__xml_tree_foreach().
 225  */
 226 static bool
 227 new_private_data(xmlNode *node, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 228 {
 229     CRM_CHECK(node != NULL, return true);
 230 
 231     if (node->_private != NULL) {
 232         return true;
 233     }
 234 
 235     switch (node->type) {
 236         case XML_DOCUMENT_NODE:
 237             {
 238                 xml_doc_private_t *docpriv =
 239                     pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
 240 
 241                 docpriv->check = PCMK__XML_DOC_PRIVATE_MAGIC;
 242                 node->_private = docpriv;
 243                 pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
 244             }
 245             break;
 246 
 247         case XML_ELEMENT_NODE:
 248         case XML_ATTRIBUTE_NODE:
 249         case XML_COMMENT_NODE:
 250             {
 251                 xml_node_private_t *nodepriv =
 252                     pcmk__assert_alloc(1, sizeof(xml_node_private_t));
 253 
 254                 nodepriv->check = PCMK__XML_NODE_PRIVATE_MAGIC;
 255                 node->_private = nodepriv;
 256                 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
 257 
 258                 for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
 259                      iter = iter->next) {
 260 
 261                     new_private_data((xmlNode *) iter, user_data);
 262                 }
 263             }
 264             break;
 265 
 266         case XML_TEXT_NODE:
 267         case XML_DTD_NODE:
 268         case XML_CDATA_SECTION_NODE:
 269             return true;
 270 
 271         default:
 272             CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
 273             return true;
 274     }
 275 
 276     if (pcmk__tracking_xml_changes(node, false)) {
 277         pcmk__mark_xml_node_dirty(node);
 278     }
 279     return true;
 280 }
 281 
 282 /*!
 283  * \internal
 284  * \brief Free private data for an XML node
 285  *
 286  * \param[in,out] node       XML node whose private data to free
 287  * \param[in]     user_data  Ignored
 288  *
 289  * \return \c true (to continue traversing the tree)
 290  *
 291  * \note This is compatible with \c pcmk__xml_tree_foreach().
 292  */
 293 static bool
 294 free_private_data(xmlNode *node, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 295 {
 296     CRM_CHECK(node != NULL, return true);
 297 
 298     if (node->_private == NULL) {
 299         return true;
 300     }
 301 
 302     if (node->type == XML_DOCUMENT_NODE) {
 303         reset_xml_private_data((xml_doc_private_t *) node->_private);
 304 
 305     } else {
 306         xml_node_private_t *nodepriv = node->_private;
 307 
 308         pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC);
 309 
 310         for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
 311              iter = iter->next) {
 312 
 313             free_private_data((xmlNode *) iter, user_data);
 314         }
 315     }
 316     free(node->_private);
 317     node->_private = NULL;
 318     return true;
 319 }
 320 
 321 /*!
 322  * \internal
 323  * \brief Allocate and initialize private data recursively for an XML tree
 324  *
 325  * \param[in,out] node  XML node whose private data to initialize
 326  */
 327 void
 328 pcmk__xml_new_private_data(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 329 {
 330     pcmk__xml_tree_foreach(xml, new_private_data, NULL);
 331 }
 332 
 333 /*!
 334  * \internal
 335  * \brief Free private data recursively for an XML tree
 336  *
 337  * \param[in,out] node  XML node whose private data to free
 338  */
 339 void
 340 pcmk__xml_free_private_data(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 341 {
 342     pcmk__xml_tree_foreach(xml, free_private_data, NULL);
 343 }
 344 
 345 void
 346 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) 
     /* [previous][next][first][last][top][bottom][index][help] */
 347 {
 348     xml_accept_changes(xml);
 349     crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
 350     pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
 351     if(enforce_acls) {
 352         if(acl_source == NULL) {
 353             acl_source = xml;
 354         }
 355         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
 356         pcmk__unpack_acl(acl_source, xml, user);
 357         pcmk__apply_acl(xml);
 358     }
 359 }
 360 
 361 bool xml_tracking_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 362 {
 363     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 364            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 365                           pcmk__xf_tracking);
 366 }
 367 
 368 bool xml_document_dirty(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 369 {
 370     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
 371            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
 372                           pcmk__xf_dirty);
 373 }
 374 
 375 /*!
 376  * \internal
 377  * \brief Return ordinal position of an XML node among its siblings
 378  *
 379  * \param[in] xml            XML node to check
 380  * \param[in] ignore_if_set  Don't count siblings with this flag set
 381  *
 382  * \return Ordinal position of \p xml (starting with 0)
 383  */
 384 int
 385 pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 386 {
 387     int position = 0;
 388 
 389     for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
 390         xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
 391 
 392         if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
 393             position++;
 394         }
 395     }
 396 
 397     return position;
 398 }
 399 
 400 /*!
 401  * \internal
 402  * \brief Remove all attributes marked as deleted from an XML node
 403  *
 404  * \param[in,out] xml        XML node whose deleted attributes to remove
 405  * \param[in,out] user_data  Ignored
 406  *
 407  * \return \c true (to continue traversing the tree)
 408  *
 409  * \note This is compatible with \c pcmk__xml_tree_foreach().
 410  */
 411 static bool
 412 accept_attr_deletions(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 413 {
 414     pcmk__xml_reset_node_flags(xml, NULL);
 415     pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
 416     return true;
 417 }
 418 
 419 /*!
 420  * \internal
 421  * \brief Find first child XML node matching another given XML node
 422  *
 423  * \param[in] haystack  XML whose children should be checked
 424  * \param[in] needle    XML to match (comment content or element name and ID)
 425  * \param[in] exact     If true and needle is a comment, position must match
 426  */
 427 xmlNode *
 428 pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
     /* [previous][next][first][last][top][bottom][index][help] */
 429 {
 430     CRM_CHECK(needle != NULL, return NULL);
 431 
 432     if (needle->type == XML_COMMENT_NODE) {
 433         return pcmk__xc_match(haystack, needle, exact);
 434 
 435     } else {
 436         const char *id = pcmk__xe_id(needle);
 437         const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
 438 
 439         return pcmk__xe_first_child(haystack, (const char *) needle->name, attr,
 440                                     id);
 441     }
 442 }
 443 
 444 void
 445 xml_accept_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 446 {
 447     xmlNode *top = NULL;
 448     xml_doc_private_t *docpriv = NULL;
 449 
 450     if(xml == NULL) {
 451         return;
 452     }
 453 
 454     crm_trace("Accepting changes to %p", xml);
 455     docpriv = xml->doc->_private;
 456     top = xmlDocGetRootElement(xml->doc);
 457 
 458     reset_xml_private_data(xml->doc->_private);
 459 
 460     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 461         docpriv->flags = pcmk__xf_none;
 462         return;
 463     }
 464 
 465     docpriv->flags = pcmk__xf_none;
 466     pcmk__xml_tree_foreach(top, accept_attr_deletions, NULL);
 467 }
 468 
 469 /*!
 470  * \internal
 471  * \brief Create a new XML document
 472  *
 473  * \return Newly allocated XML document (guaranteed not to be \c NULL)
 474  *
 475  * \note The caller is responsible for freeing the return value using
 476  *       \c pcmk__xml_free_doc().
 477  */
 478 xmlDoc *
 479 pcmk__xml_new_doc(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 480 {
 481     xmlDoc *doc = xmlNewDoc(XML_VERSION);
 482 
 483     pcmk__mem_assert(doc);
 484     pcmk__xml_new_private_data((xmlNode *) doc);
 485     return doc;
 486 }
 487 
 488 /*!
 489  * \internal
 490  * \brief Free a new XML document
 491  *
 492  * \param[in,out] doc  XML document to free
 493  */
 494 void
 495 pcmk__xml_free_doc(xmlDoc *doc)
     /* [previous][next][first][last][top][bottom][index][help] */
 496 {
 497     if (doc != NULL) {
 498         pcmk__xml_free_private_data((xmlNode *) doc);
 499         xmlFreeDoc(doc);
 500     }
 501 }
 502 
 503 /*!
 504  * \internal
 505  * \brief Check whether the first character of a string is an XML NameStartChar
 506  *
 507  * See https://www.w3.org/TR/xml/#NT-NameStartChar.
 508  *
 509  * This is almost identical to libxml2's \c xmlIsDocNameStartChar(), but they
 510  * don't expose it as part of the public API.
 511  *
 512  * \param[in]  utf8  UTF-8 encoded string
 513  * \param[out] len   If not \c NULL, where to store size in bytes of first
 514  *                   character in \p utf8
 515  *
 516  * \return \c true if \p utf8 begins with a valid XML NameStartChar, or \c false
 517  *         otherwise
 518  */
 519 bool
 520 pcmk__xml_is_name_start_char(const char *utf8, int *len)
     /* [previous][next][first][last][top][bottom][index][help] */
 521 {
 522     int c = 0;
 523     int local_len = 0;
 524 
 525     if (len == NULL) {
 526         len = &local_len;
 527     }
 528 
 529     /* xmlGetUTF8Char() abuses the len argument. At call time, it must be set to
 530      * "the minimum number of bytes present in the sequence... to assure the
 531      * next character is completely contained within the sequence." It's similar
 532      * to the "n" in the strn*() functions. However, this doesn't make any sense
 533      * for null-terminated strings, and there's no value that indicates "keep
 534      * going until '\0'." So we set it to 4, the max number of bytes in a UTF-8
 535      * character.
 536      *
 537      * At return, it's set to the actual number of bytes in the char, or 0 on
 538      * error.
 539      */
 540     *len = 4;
 541 
 542     // Note: xmlGetUTF8Char() assumes a 32-bit int
 543     c = xmlGetUTF8Char((pcmkXmlStr) utf8, len);
 544     if (c < 0) {
 545         GString *buf = g_string_sized_new(32);
 546 
 547         for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
 548             g_string_append_printf(buf, " 0x%.2X", utf8[i]);
 549         }
 550         crm_info("Invalid UTF-8 character (bytes:%s)",
 551                  (pcmk__str_empty(buf->str)? " <none>" : buf->str));
 552         g_string_free(buf, TRUE);
 553         return false;
 554     }
 555 
 556     return (c == '_')
 557            || (c == ':')
 558            || ((c >= 'a') && (c <= 'z'))
 559            || ((c >= 'A') && (c <= 'Z'))
 560            || ((c >= 0xC0) && (c <= 0xD6))
 561            || ((c >= 0xD8) && (c <= 0xF6))
 562            || ((c >= 0xF8) && (c <= 0x2FF))
 563            || ((c >= 0x370) && (c <= 0x37D))
 564            || ((c >= 0x37F) && (c <= 0x1FFF))
 565            || ((c >= 0x200C) && (c <= 0x200D))
 566            || ((c >= 0x2070) && (c <= 0x218F))
 567            || ((c >= 0x2C00) && (c <= 0x2FEF))
 568            || ((c >= 0x3001) && (c <= 0xD7FF))
 569            || ((c >= 0xF900) && (c <= 0xFDCF))
 570            || ((c >= 0xFDF0) && (c <= 0xFFFD))
 571            || ((c >= 0x10000) && (c <= 0xEFFFF));
 572 }
 573 
 574 /*!
 575  * \internal
 576  * \brief Check whether the first character of a string is an XML NameChar
 577  *
 578  * See https://www.w3.org/TR/xml/#NT-NameChar.
 579  *
 580  * This is almost identical to libxml2's \c xmlIsDocNameChar(), but they don't
 581  * expose it as part of the public API.
 582  *
 583  * \param[in]  utf8  UTF-8 encoded string
 584  * \param[out] len   If not \c NULL, where to store size in bytes of first
 585  *                   character in \p utf8
 586  *
 587  * \return \c true if \p utf8 begins with a valid XML NameChar, or \c false
 588  *         otherwise
 589  */
 590 bool
 591 pcmk__xml_is_name_char(const char *utf8, int *len)
     /* [previous][next][first][last][top][bottom][index][help] */
 592 {
 593     int c = 0;
 594     int local_len = 0;
 595 
 596     if (len == NULL) {
 597         len = &local_len;
 598     }
 599 
 600     // See comment regarding len in pcmk__xml_is_name_start_char()
 601     *len = 4;
 602 
 603     // Note: xmlGetUTF8Char() assumes a 32-bit int
 604     c = xmlGetUTF8Char((pcmkXmlStr) utf8, len);
 605     if (c < 0) {
 606         GString *buf = g_string_sized_new(32);
 607 
 608         for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
 609             g_string_append_printf(buf, " 0x%.2X", utf8[i]);
 610         }
 611         crm_info("Invalid UTF-8 character (bytes:%s)",
 612                  (pcmk__str_empty(buf->str)? " <none>" : buf->str));
 613         g_string_free(buf, TRUE);
 614         return false;
 615     }
 616 
 617     return ((c >= 'a') && (c <= 'z'))
 618            || ((c >= 'A') && (c <= 'Z'))
 619            || ((c >= '0') && (c <= '9'))
 620            || (c == '_')
 621            || (c == ':')
 622            || (c == '-')
 623            || (c == '.')
 624            || (c == 0xB7)
 625            || ((c >= 0xC0) && (c <= 0xD6))
 626            || ((c >= 0xD8) && (c <= 0xF6))
 627            || ((c >= 0xF8) && (c <= 0x2FF))
 628            || ((c >= 0x300) && (c <= 0x36F))
 629            || ((c >= 0x370) && (c <= 0x37D))
 630            || ((c >= 0x37F) && (c <= 0x1FFF))
 631            || ((c >= 0x200C) && (c <= 0x200D))
 632            || ((c >= 0x203F) && (c <= 0x2040))
 633            || ((c >= 0x2070) && (c <= 0x218F))
 634            || ((c >= 0x2C00) && (c <= 0x2FEF))
 635            || ((c >= 0x3001) && (c <= 0xD7FF))
 636            || ((c >= 0xF900) && (c <= 0xFDCF))
 637            || ((c >= 0xFDF0) && (c <= 0xFFFD))
 638            || ((c >= 0x10000) && (c <= 0xEFFFF));
 639 }
 640 
 641 /*!
 642  * \internal
 643  * \brief Sanitize a string so it is usable as an XML ID
 644  *
 645  * An ID must match the Name production as defined here:
 646  * https://www.w3.org/TR/xml/#NT-Name.
 647  *
 648  * Convert an invalid start character to \c '_'. Convert an invalid character
 649  * after the start character to \c '.'.
 650  *
 651  * \param[in,out] id  String to sanitize
 652  */
 653 void
 654 pcmk__xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
 655 {
 656     bool valid = true;
 657     int len = 0;
 658 
 659     // If id is empty or NULL, there's no way to make it a valid XML ID
 660     pcmk__assert(!pcmk__str_empty(id));
 661 
 662     /* @TODO Suppose there are two strings and each has an invalid ID character
 663      * in the same position. The strings are otherwise identical. Both strings
 664      * will be sanitized to the same valid ID, which is incorrect.
 665      *
 666      * The caller is responsible for ensuring the sanitized ID does not already
 667      * exist in a given XML document before using it, if uniqueness is desired.
 668      */
 669     valid = pcmk__xml_is_name_start_char(id, &len);
 670     CRM_CHECK(len > 0, return); // UTF-8 encoding error
 671     if (!valid) {
 672         *id = '_';
 673         for (int i = 1; i < len; i++) {
 674             id[i] = '.';
 675         }
 676     }
 677 
 678     for (id += len; *id != '\0'; id += len) {
 679         valid = pcmk__xml_is_name_char(id, &len);
 680         CRM_CHECK(len > 0, return); // UTF-8 encoding error
 681         if (!valid) {
 682             for (int i = 0; i < len; i++) {
 683                 id[i] = '.';
 684             }
 685         }
 686     }
 687 }
 688 
 689 /*!
 690  * \internal
 691  * \brief Free an XML tree without ACL checks or change tracking
 692  *
 693  * \param[in,out] xml  XML node to free
 694  */
 695 void
 696 pcmk__xml_free_node(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 697 {
 698     pcmk__xml_free_private_data(xml);
 699     xmlUnlinkNode(xml);
 700     xmlFreeNode(xml);
 701 }
 702 
 703 /*!
 704  * \internal
 705  * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
 706  *
 707  * If \p node is the root of its document, free the entire document.
 708  *
 709  * \param[in,out] node      XML node to free
 710  * \param[in]     position  Position of \p node among its siblings for change
 711  *                          tracking (negative to calculate automatically if
 712  *                          needed)
 713  */
 714 static void
 715 free_xml_with_position(xmlNode *node, int position)
     /* [previous][next][first][last][top][bottom][index][help] */
 716 {
 717     xmlDoc *doc = NULL;
 718     xml_node_private_t *nodepriv = NULL;
 719 
 720     if (node == NULL) {
 721         return;
 722     }
 723     doc = node->doc;
 724     nodepriv = node->_private;
 725 
 726     if ((doc != NULL) && (xmlDocGetRootElement(doc) == node)) {
 727         /* @TODO Should we check ACLs first? Otherwise it seems like we could
 728          * free the root element without write permission.
 729          */
 730         pcmk__xml_free_doc(doc);
 731         return;
 732     }
 733 
 734     if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
 735         GString *xpath = NULL;
 736 
 737         pcmk__if_tracing({}, return);
 738         xpath = pcmk__element_xpath(node);
 739         qb_log_from_external_source(__func__, __FILE__,
 740                                     "Cannot remove %s %x", LOG_TRACE,
 741                                     __LINE__, 0, xpath->str, nodepriv->flags);
 742         g_string_free(xpath, TRUE);
 743         return;
 744     }
 745 
 746     if ((doc != NULL) && pcmk__tracking_xml_changes(node, false)
 747         && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 748 
 749         xml_doc_private_t *docpriv = doc->_private;
 750         GString *xpath = pcmk__element_xpath(node);
 751 
 752         if (xpath != NULL) {
 753             pcmk__deleted_xml_t *deleted_obj = NULL;
 754 
 755             crm_trace("Deleting %s %p from %p", xpath->str, node, doc);
 756 
 757             deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
 758             deleted_obj->path = g_string_free(xpath, FALSE);
 759             deleted_obj->position = -1;
 760 
 761             // Record the position only for XML comments for now
 762             if (node->type == XML_COMMENT_NODE) {
 763                 if (position >= 0) {
 764                     deleted_obj->position = position;
 765 
 766                 } else {
 767                     deleted_obj->position = pcmk__xml_position(node,
 768                                                                pcmk__xf_skip);
 769                 }
 770             }
 771 
 772             docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
 773                                                   deleted_obj);
 774             pcmk__set_xml_doc_flag(node, pcmk__xf_dirty);
 775         }
 776     }
 777     pcmk__xml_free_node(node);
 778 }
 779 
 780 /*!
 781  * \internal
 782  * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
 783  *
 784  * If \p xml is the root of its document, free the entire document.
 785  *
 786  * \param[in,out] xml  XML node to free
 787  */
 788 void
 789 pcmk__xml_free(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 790 {
 791     free_xml_with_position(xml, -1);
 792 }
 793 
 794 /*!
 795  * \internal
 796  * \brief Make a deep copy of an XML node under a given parent
 797  *
 798  * \param[in,out] parent  XML element that will be the copy's parent (\c NULL
 799  *                        to create a new XML document with the copy as root)
 800  * \param[in]     src     XML node to copy
 801  *
 802  * \return Deep copy of \p src, or \c NULL if \p src is \c NULL
 803  */
 804 xmlNode *
 805 pcmk__xml_copy(xmlNode *parent, xmlNode *src)
     /* [previous][next][first][last][top][bottom][index][help] */
 806 {
 807     xmlNode *copy = NULL;
 808 
 809     if (src == NULL) {
 810         return NULL;
 811     }
 812 
 813     if (parent == NULL) {
 814         xmlDoc *doc = NULL;
 815 
 816         // The copy will be the root element of a new document
 817         pcmk__assert(src->type == XML_ELEMENT_NODE);
 818 
 819         doc = pcmk__xml_new_doc();
 820         copy = xmlDocCopyNode(src, doc, 1);
 821         pcmk__mem_assert(copy);
 822 
 823         xmlDocSetRootElement(doc, copy);
 824 
 825     } else {
 826         copy = xmlDocCopyNode(src, parent->doc, 1);
 827         pcmk__mem_assert(copy);
 828 
 829         xmlAddChild(parent, copy);
 830     }
 831 
 832     pcmk__xml_new_private_data(copy);
 833     return copy;
 834 }
 835 
 836 /*!
 837  * \internal
 838  * \brief Remove XML text nodes from specified XML and all its children
 839  *
 840  * \param[in,out] xml  XML to strip text from
 841  */
 842 void
 843 pcmk__strip_xml_text(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 844 {
 845     xmlNode *iter = xml->children;
 846 
 847     while (iter) {
 848         xmlNode *next = iter->next;
 849 
 850         switch (iter->type) {
 851             case XML_TEXT_NODE:
 852                 pcmk__xml_free_node(iter);
 853                 break;
 854 
 855             case XML_ELEMENT_NODE:
 856                 /* Search it */
 857                 pcmk__strip_xml_text(iter);
 858                 break;
 859 
 860             default:
 861                 /* Leave it */
 862                 break;
 863         }
 864 
 865         iter = next;
 866     }
 867 }
 868 
 869 /*!
 870  * \internal
 871  * \brief Check whether a string has XML special characters that must be escaped
 872  *
 873  * See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
 874  *
 875  * \param[in] text  String to check
 876  * \param[in] type  Type of escaping
 877  *
 878  * \return \c true if \p text has special characters that need to be escaped, or
 879  *         \c false otherwise
 880  */
 881 bool
 882 pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
 883 {
 884     if (text == NULL) {
 885         return false;
 886     }
 887 
 888     while (*text != '\0') {
 889         switch (type) {
 890             case pcmk__xml_escape_text:
 891                 switch (*text) {
 892                     case '<':
 893                     case '>':
 894                     case '&':
 895                         return true;
 896                     case '\n':
 897                     case '\t':
 898                         break;
 899                     default:
 900                         if (g_ascii_iscntrl(*text)) {
 901                             return true;
 902                         }
 903                         break;
 904                 }
 905                 break;
 906 
 907             case pcmk__xml_escape_attr:
 908                 switch (*text) {
 909                     case '<':
 910                     case '>':
 911                     case '&':
 912                     case '"':
 913                         return true;
 914                     default:
 915                         if (g_ascii_iscntrl(*text)) {
 916                             return true;
 917                         }
 918                         break;
 919                 }
 920                 break;
 921 
 922             case pcmk__xml_escape_attr_pretty:
 923                 switch (*text) {
 924                     case '\n':
 925                     case '\r':
 926                     case '\t':
 927                     case '"':
 928                         return true;
 929                     default:
 930                         break;
 931                 }
 932                 break;
 933 
 934             default:    // Invalid enum value
 935                 pcmk__assert(false);
 936                 break;
 937         }
 938 
 939         text = g_utf8_next_char(text);
 940     }
 941     return false;
 942 }
 943 
 944 /*!
 945  * \internal
 946  * \brief Replace special characters with their XML escape sequences
 947  *
 948  * \param[in] text  Text to escape
 949  * \param[in] type  Type of escaping
 950  *
 951  * \return Newly allocated string equivalent to \p text but with special
 952  *         characters replaced with XML escape sequences (or \c NULL if \p text
 953  *         is \c NULL). If \p text is not \c NULL, the return value is
 954  *         guaranteed not to be \c NULL.
 955  *
 956  * \note There are libxml functions that purport to do this:
 957  *       \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
 958  *       However, their escaping is incomplete. See:
 959  *       https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
 960  * \note The caller is responsible for freeing the return value using
 961  *       \c g_free().
 962  */
 963 gchar *
 964 pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
     /* [previous][next][first][last][top][bottom][index][help] */
 965 {
 966     GString *copy = NULL;
 967 
 968     if (text == NULL) {
 969         return NULL;
 970     }
 971     copy = g_string_sized_new(strlen(text));
 972 
 973     while (*text != '\0') {
 974         // Don't escape any non-ASCII characters
 975         if ((*text & 0x80) != 0) {
 976             size_t bytes = g_utf8_next_char(text) - text;
 977 
 978             g_string_append_len(copy, text, bytes);
 979             text += bytes;
 980             continue;
 981         }
 982 
 983         switch (type) {
 984             case pcmk__xml_escape_text:
 985                 switch (*text) {
 986                     case '<':
 987                         g_string_append(copy, PCMK__XML_ENTITY_LT);
 988                         break;
 989                     case '>':
 990                         g_string_append(copy, PCMK__XML_ENTITY_GT);
 991                         break;
 992                     case '&':
 993                         g_string_append(copy, PCMK__XML_ENTITY_AMP);
 994                         break;
 995                     case '\n':
 996                     case '\t':
 997                         g_string_append_c(copy, *text);
 998                         break;
 999                     default:
1000                         if (g_ascii_iscntrl(*text)) {
1001                             g_string_append_printf(copy, "&#x%.2X;", *text);
1002                         } else {
1003                             g_string_append_c(copy, *text);
1004                         }
1005                         break;
1006                 }
1007                 break;
1008 
1009             case pcmk__xml_escape_attr:
1010                 switch (*text) {
1011                     case '<':
1012                         g_string_append(copy, PCMK__XML_ENTITY_LT);
1013                         break;
1014                     case '>':
1015                         g_string_append(copy, PCMK__XML_ENTITY_GT);
1016                         break;
1017                     case '&':
1018                         g_string_append(copy, PCMK__XML_ENTITY_AMP);
1019                         break;
1020                     case '"':
1021                         g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1022                         break;
1023                     default:
1024                         if (g_ascii_iscntrl(*text)) {
1025                             g_string_append_printf(copy, "&#x%.2X;", *text);
1026                         } else {
1027                             g_string_append_c(copy, *text);
1028                         }
1029                         break;
1030                 }
1031                 break;
1032 
1033             case pcmk__xml_escape_attr_pretty:
1034                 switch (*text) {
1035                     case '"':
1036                         g_string_append(copy, "\\\"");
1037                         break;
1038                     case '\n':
1039                         g_string_append(copy, "\\n");
1040                         break;
1041                     case '\r':
1042                         g_string_append(copy, "\\r");
1043                         break;
1044                     case '\t':
1045                         g_string_append(copy, "\\t");
1046                         break;
1047                     default:
1048                         g_string_append_c(copy, *text);
1049                         break;
1050                 }
1051                 break;
1052 
1053             default:    // Invalid enum value
1054                 pcmk__assert(false);
1055                 break;
1056         }
1057 
1058         text = g_utf8_next_char(text);
1059     }
1060     return g_string_free(copy, FALSE);
1061 }
1062 
1063 /*!
1064  * \internal
1065  * \brief Set a flag on all attributes of an XML element
1066  *
1067  * \param[in,out] xml   XML node to set flags on
1068  * \param[in]     flag  XML private flag to set
1069  */
1070 static void
1071 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
     /* [previous][next][first][last][top][bottom][index][help] */
1072 {
1073     for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
1074         pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
1075     }
1076 }
1077 
1078 /*!
1079  * \internal
1080  * \brief Add an XML attribute to a node, marked as deleted
1081  *
1082  * When calculating XML changes, we need to know when an attribute has been
1083  * deleted. Add the attribute back to the new XML, so that we can check the
1084  * removal against ACLs, and mark it as deleted for later removal after
1085  * differences have been calculated.
1086  *
1087  * \param[in,out] new_xml     XML to modify
1088  * \param[in]     element     Name of XML element that changed (for logging)
1089  * \param[in]     attr_name   Name of attribute that was deleted
1090  * \param[in]     old_value   Value of attribute that was deleted
1091  */
1092 static void
1093 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1094                   const char *old_value)
1095 {
1096     xml_doc_private_t *docpriv = new_xml->doc->_private;
1097     xmlAttr *attr = NULL;
1098     xml_node_private_t *nodepriv;
1099 
1100     /* Restore the old value (without setting dirty flag recursively upwards or
1101      * checking ACLs)
1102      */
1103     pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1104     crm_xml_add(new_xml, attr_name, old_value);
1105     pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1106 
1107     // Reset flags (so the attribute doesn't appear as newly created)
1108     attr = xmlHasProp(new_xml, (pcmkXmlStr) attr_name);
1109     nodepriv = attr->_private;
1110     nodepriv->flags = 0;
1111 
1112     // Check ACLs and mark restored value for later removal
1113     pcmk__xa_remove(attr, false);
1114 
1115     crm_trace("XML attribute %s=%s was removed from %s",
1116               attr_name, old_value, element);
1117 }
1118 
1119 /*
1120  * \internal
1121  * \brief Check ACLs for a changed XML attribute
1122  */
1123 static void
1124 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1125                   const char *old_value)
1126 {
1127     xml_doc_private_t *docpriv = new_xml->doc->_private;
1128     char *vcopy = crm_element_value_copy(new_xml, attr_name);
1129 
1130     crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
1131               attr_name, old_value, vcopy, element);
1132 
1133     // Restore the original value (without checking ACLs)
1134     pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1135     crm_xml_add(new_xml, attr_name, old_value);
1136     pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1137 
1138     // Change it back to the new value, to check ACLs
1139     crm_xml_add(new_xml, attr_name, vcopy);
1140     free(vcopy);
1141 }
1142 
1143 /*!
1144  * \internal
1145  * \brief Mark an XML attribute as having changed position
1146  *
1147  * \param[in,out] new_xml     XML to modify
1148  * \param[in]     element     Name of XML element that changed (for logging)
1149  * \param[in,out] old_attr    Attribute that moved, in original XML
1150  * \param[in,out] new_attr    Attribute that moved, in \p new_xml
1151  * \param[in]     p_old       Ordinal position of \p old_attr in original XML
1152  * \param[in]     p_new       Ordinal position of \p new_attr in \p new_xml
1153  */
1154 static void
1155 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
     /* [previous][next][first][last][top][bottom][index][help] */
1156                 xmlAttr *new_attr, int p_old, int p_new)
1157 {
1158     xml_node_private_t *nodepriv = new_attr->_private;
1159 
1160     crm_trace("XML attribute %s moved from position %d to %d in %s",
1161               old_attr->name, p_old, p_new, element);
1162 
1163     // Mark document, element, and all element's parents as changed
1164     pcmk__mark_xml_node_dirty(new_xml);
1165 
1166     // Mark attribute as changed
1167     pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
1168 
1169     nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1170     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1171 }
1172 
1173 /*!
1174  * \internal
1175  * \brief Calculate differences in all previously existing XML attributes
1176  *
1177  * \param[in,out] old_xml  Original XML to compare
1178  * \param[in,out] new_xml  New XML to compare
1179  */
1180 static void
1181 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1182 {
1183     xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
1184 
1185     while (attr_iter != NULL) {
1186         const char *name = (const char *) attr_iter->name;
1187         xmlAttr *old_attr = attr_iter;
1188         xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
1189         const char *old_value = pcmk__xml_attr_value(attr_iter);
1190 
1191         attr_iter = attr_iter->next;
1192         if (new_attr == NULL) {
1193             mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
1194                               old_value);
1195 
1196         } else {
1197             xml_node_private_t *nodepriv = new_attr->_private;
1198             int new_pos = pcmk__xml_position((xmlNode*) new_attr,
1199                                              pcmk__xf_skip);
1200             int old_pos = pcmk__xml_position((xmlNode*) old_attr,
1201                                              pcmk__xf_skip);
1202             const char *new_value = crm_element_value(new_xml, name);
1203 
1204             // This attribute isn't new
1205             pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
1206 
1207             if (strcmp(new_value, old_value) != 0) {
1208                 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
1209                                   old_value);
1210 
1211             } else if ((old_pos != new_pos)
1212                        && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
1213                 mark_attr_moved(new_xml, (const char *) old_xml->name,
1214                                 old_attr, new_attr, old_pos, new_pos);
1215             }
1216         }
1217     }
1218 }
1219 
1220 /*!
1221  * \internal
1222  * \brief Check all attributes in new XML for creation
1223  *
1224  * For each of a given XML element's attributes marked as newly created, accept
1225  * (and mark as dirty) or reject the creation according to ACLs.
1226  *
1227  * \param[in,out] new_xml  XML to check
1228  */
1229 static void
1230 mark_created_attrs(xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1231 {
1232     xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
1233 
1234     while (attr_iter != NULL) {
1235         xmlAttr *new_attr = attr_iter;
1236         xml_node_private_t *nodepriv = attr_iter->_private;
1237 
1238         attr_iter = attr_iter->next;
1239         if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
1240             const char *attr_name = (const char *) new_attr->name;
1241 
1242             crm_trace("Created new attribute %s=%s in %s",
1243                       attr_name, pcmk__xml_attr_value(new_attr),
1244                       new_xml->name);
1245 
1246             /* Check ACLs (we can't use the remove-then-create trick because it
1247              * would modify the attribute position).
1248              */
1249             if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
1250                 pcmk__mark_xml_attr_dirty(new_attr);
1251             } else {
1252                 // Creation was not allowed, so remove the attribute
1253                 pcmk__xa_remove(new_attr, true);
1254             }
1255         }
1256     }
1257 }
1258 
1259 /*!
1260  * \internal
1261  * \brief Calculate differences in attributes between two XML nodes
1262  *
1263  * \param[in,out] old_xml  Original XML to compare
1264  * \param[in,out] new_xml  New XML to compare
1265  */
1266 static void
1267 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1268 {
1269     set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
1270     xml_diff_old_attrs(old_xml, new_xml);
1271     mark_created_attrs(new_xml);
1272 }
1273 
1274 /*!
1275  * \internal
1276  * \brief Add an XML child element to a node, marked as deleted
1277  *
1278  * When calculating XML changes, we need to know when a child element has been
1279  * deleted. Add the child back to the new XML, so that we can check the removal
1280  * against ACLs, and mark it as deleted for later removal after differences have
1281  * been calculated.
1282  *
1283  * \param[in,out] old_child    Child element from original XML
1284  * \param[in,out] new_parent   New XML to add marked copy to
1285  */
1286 static void
1287 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
     /* [previous][next][first][last][top][bottom][index][help] */
1288 {
1289     // Re-create the child element so we can check ACLs
1290     xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1291 
1292     // Clear flags on new child and its children
1293     pcmk__xml_tree_foreach(candidate, pcmk__xml_reset_node_flags, NULL);
1294 
1295     // Check whether ACLs allow the deletion
1296     pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
1297 
1298     // Remove the child again (which will track it in document's deleted_objs)
1299     free_xml_with_position(candidate,
1300                            pcmk__xml_position(old_child, pcmk__xf_skip));
1301 
1302     if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
1303         pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
1304                             pcmk__xf_skip);
1305     }
1306 }
1307 
1308 static void
1309 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
     /* [previous][next][first][last][top][bottom][index][help] */
1310                  int p_old, int p_new)
1311 {
1312     xml_node_private_t *nodepriv = new_child->_private;
1313 
1314     crm_trace("Child element %s with "
1315               PCMK_XA_ID "='%s' moved from position %d to %d under %s",
1316               new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
1317               p_old, p_new, new_parent->name);
1318     pcmk__mark_xml_node_dirty(new_parent);
1319     pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
1320 
1321     if (p_old > p_new) {
1322         nodepriv = old_child->_private;
1323     } else {
1324         nodepriv = new_child->_private;
1325     }
1326     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1327 }
1328 
1329 // Given original and new XML, mark new XML portions that have changed
1330 static void
1331 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
     /* [previous][next][first][last][top][bottom][index][help] */
1332 {
1333     xmlNode *old_child = NULL;
1334     xmlNode *new_child = NULL;
1335     xml_node_private_t *nodepriv = NULL;
1336 
1337     CRM_CHECK(new_xml != NULL, return);
1338     if (old_xml == NULL) {
1339         mark_xml_tree_dirty_created(new_xml);
1340         pcmk__apply_creation_acl(new_xml, check_top);
1341         return;
1342     }
1343 
1344     nodepriv = new_xml->_private;
1345     CRM_CHECK(nodepriv != NULL, return);
1346 
1347     if(nodepriv->flags & pcmk__xf_processed) {
1348         /* Avoid re-comparing nodes */
1349         return;
1350     }
1351     pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
1352 
1353     xml_diff_attrs(old_xml, new_xml);
1354 
1355     // Check for differences in the original children
1356     for (old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1357          old_child = pcmk__xml_next(old_child)) {
1358 
1359         new_child = pcmk__xml_match(new_xml, old_child, true);
1360 
1361         if (new_child != NULL) {
1362             mark_xml_changes(old_child, new_child, true);
1363 
1364         } else {
1365             mark_child_deleted(old_child, new_xml);
1366         }
1367     }
1368 
1369     // Check for moved or created children
1370     new_child = pcmk__xml_first_child(new_xml);
1371     while (new_child != NULL) {
1372         xmlNode *next = pcmk__xml_next(new_child);
1373 
1374         old_child = pcmk__xml_match(old_xml, new_child, true);
1375 
1376         if (old_child == NULL) {
1377             // This is a newly created child
1378             nodepriv = new_child->_private;
1379             pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1380 
1381             // May free new_child
1382             mark_xml_changes(old_child, new_child, true);
1383 
1384         } else {
1385             /* Check for movement, we already checked for differences */
1386             int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
1387             int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
1388 
1389             if(p_old != p_new) {
1390                 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
1391             }
1392         }
1393 
1394         new_child = next;
1395     }
1396 }
1397 
1398 void
1399 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1400 {
1401     pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
1402     xml_calculate_changes(old_xml, new_xml);
1403 }
1404 
1405 // Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
1406 void
1407 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1408 {
1409     CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
1410               && pcmk__xe_is(old_xml, (const char *) new_xml->name)
1411               && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
1412                               pcmk__str_none),
1413               return);
1414 
1415     if(xml_tracking_changes(new_xml) == FALSE) {
1416         xml_track_changes(new_xml, NULL, NULL, FALSE);
1417     }
1418 
1419     mark_xml_changes(old_xml, new_xml, FALSE);
1420 }
1421 
1422 /*!
1423  * \internal
1424  * \brief Initialize the Pacemaker XML environment
1425  *
1426  * Set an XML buffer allocation scheme, set XML node create and destroy
1427  * callbacks, and load schemas into the cache.
1428  */
1429 void
1430 pcmk__xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1431 {
1432     // @TODO Try to find a better caller than crm_log_preinit()
1433     static bool initialized = false;
1434 
1435     if (!initialized) {
1436         initialized = true;
1437 
1438         /* Double the buffer size when the buffer needs to grow. The default
1439          * allocator XML_BUFFER_ALLOC_EXACT was found to cause poor performance
1440          * due to the number of reallocs.
1441          */
1442         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
1443 
1444         // Load schemas into the cache
1445         pcmk__schema_init();
1446     }
1447 }
1448 
1449 /*!
1450  * \internal
1451  * \brief Tear down the Pacemaker XML environment
1452  *
1453  * Destroy schema cache and clean up memory allocated by libxml2.
1454  */
1455 void
1456 pcmk__xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1457 {
1458     pcmk__schema_cleanup();
1459     xmlCleanupParser();
1460 }
1461 
1462 char *
1463 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
     /* [previous][next][first][last][top][bottom][index][help] */
1464 {
1465     static const char *base = NULL;
1466     char *ret = NULL;
1467 
1468     if (base == NULL) {
1469         base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
1470     }
1471     if (pcmk__str_empty(base)) {
1472         base = PCMK_SCHEMA_DIR;
1473     }
1474 
1475     switch (ns) {
1476         case pcmk__xml_artefact_ns_legacy_rng:
1477         case pcmk__xml_artefact_ns_legacy_xslt:
1478             ret = strdup(base);
1479             break;
1480         case pcmk__xml_artefact_ns_base_rng:
1481         case pcmk__xml_artefact_ns_base_xslt:
1482             ret = crm_strdup_printf("%s/base", base);
1483             break;
1484         default:
1485             crm_err("XML artefact family specified as %u not recognized", ns);
1486     }
1487     return ret;
1488 }
1489 
1490 static char *
1491 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
1492 {
1493     char *ret = NULL;
1494 
1495     switch (ns) {
1496         case pcmk__xml_artefact_ns_legacy_rng:
1497         case pcmk__xml_artefact_ns_base_rng:
1498             if (pcmk__ends_with(filespec, ".rng")) {
1499                 ret = crm_strdup_printf("%s/%s", path, filespec);
1500             } else {
1501                 ret = crm_strdup_printf("%s/%s.rng", path, filespec);
1502             }
1503             break;
1504         case pcmk__xml_artefact_ns_legacy_xslt:
1505         case pcmk__xml_artefact_ns_base_xslt:
1506             if (pcmk__ends_with(filespec, ".xsl")) {
1507                 ret = crm_strdup_printf("%s/%s", path, filespec);
1508             } else {
1509                 ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
1510             }
1511             break;
1512         default:
1513             crm_err("XML artefact family specified as %u not recognized", ns);
1514     }
1515 
1516     return ret;
1517 }
1518 
1519 char *
1520 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
     /* [previous][next][first][last][top][bottom][index][help] */
1521 {
1522     struct stat sb;
1523     char *base = pcmk__xml_artefact_root(ns);
1524     char *ret = NULL;
1525 
1526     ret = find_artefact(ns, base, filespec);
1527     free(base);
1528 
1529     if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
1530         const char *remote_schema_dir = pcmk__remote_schema_dir();
1531 
1532         free(ret);
1533         ret = find_artefact(ns, remote_schema_dir, filespec);
1534     }
1535 
1536     return ret;
1537 }
1538 
1539 // Deprecated functions kept only for backward API compatibility
1540 // LCOV_EXCL_START
1541 
1542 #include <crm/common/xml_compat.h>
1543 
1544 xmlNode *
1545 copy_xml(xmlNode *src)
     /* [previous][next][first][last][top][bottom][index][help] */
1546 {
1547     xmlDoc *doc = pcmk__xml_new_doc();
1548     xmlNode *copy = NULL;
1549 
1550     copy = xmlDocCopyNode(src, doc, 1);
1551     pcmk__mem_assert(copy);
1552 
1553     xmlDocSetRootElement(doc, copy);
1554     pcmk__xml_new_private_data(copy);
1555     return copy;
1556 }
1557 
1558 void
1559 crm_xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1560 {
1561     pcmk__xml_init();
1562 }
1563 
1564 void
1565 crm_xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1566 {
1567     pcmk__xml_cleanup();
1568 }
1569 
1570 void
1571 pcmk_free_xml_subtree(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1572 {
1573     pcmk__xml_free_node(xml);
1574 }
1575 
1576 void
1577 free_xml(xmlNode *child)
     /* [previous][next][first][last][top][bottom][index][help] */
1578 {
1579     pcmk__xml_free(child);
1580 }
1581 
1582 void
1583 crm_xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
1584 {
1585     char *c;
1586 
1587     for (c = id; *c; ++c) {
1588         switch (*c) {
1589             case ':':
1590             case '#':
1591                 *c = '.';
1592         }
1593     }
1594 }
1595 
1596 // LCOV_EXCL_STOP
1597 // End deprecated API

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