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