LCOV - code coverage report
Current view: top level - common - xml.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 188 856 22.0 %
Date: 2024-05-07 11:09:47 Functions: 7 71 9.9 %

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

Generated by: LCOV version 1.14