1    	/*
2    	 * Copyright 2004-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdarg.h>
13   	#include <stdbool.h>
14   	#include <stdint.h>                     // uint32_t
15   	#include <stdio.h>
16   	#include <stdlib.h>
17   	#include <string.h>
18   	#include <sys/stat.h>                   // stat(), S_ISREG, etc.
19   	#include <sys/types.h>
20   	
21   	#include <glib.h>                       // gboolean, GString
22   	#include <libxml/tree.h>                // xmlNode, etc.
23   	#include <libxml/xmlstring.h>           // xmlChar, xmlGetUTF8Char()
24   	
25   	#include <crm/crm.h>
26   	#include <crm/common/xml.h>
27   	#include "crmcommon_private.h"
28   	
29   	//! libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
30   	#define XML_VERSION ((const xmlChar *) "1.0")
31   	
32   	/*!
33   	 * \internal
34   	 * \brief Get a string representation of an XML element type for logging
35   	 *
36   	 * \param[in] type  XML element type
37   	 *
38   	 * \return String representation of \p type
39   	 */
40   	const char *
41   	pcmk__xml_element_type_text(xmlElementType type)
42   	{
43   	    static const char *const element_type_names[] = {
44   	        [XML_ELEMENT_NODE]       = "element",
45   	        [XML_ATTRIBUTE_NODE]     = "attribute",
46   	        [XML_TEXT_NODE]          = "text",
47   	        [XML_CDATA_SECTION_NODE] = "CDATA section",
48   	        [XML_ENTITY_REF_NODE]    = "entity reference",
49   	        [XML_ENTITY_NODE]        = "entity",
50   	        [XML_PI_NODE]            = "PI",
51   	        [XML_COMMENT_NODE]       = "comment",
52   	        [XML_DOCUMENT_NODE]      = "document",
53   	        [XML_DOCUMENT_TYPE_NODE] = "document type",
54   	        [XML_DOCUMENT_FRAG_NODE] = "document fragment",
55   	        [XML_NOTATION_NODE]      = "notation",
56   	        [XML_HTML_DOCUMENT_NODE] = "HTML document",
57   	        [XML_DTD_NODE]           = "DTD",
58   	        [XML_ELEMENT_DECL]       = "element declaration",
59   	        [XML_ATTRIBUTE_DECL]     = "attribute declaration",
60   	        [XML_ENTITY_DECL]        = "entity declaration",
61   	        [XML_NAMESPACE_DECL]     = "namespace declaration",
62   	        [XML_XINCLUDE_START]     = "XInclude start",
63   	        [XML_XINCLUDE_END]       = "XInclude end",
64   	    };
65   	
66   	    // Assumes the numeric values of the indices are in ascending order
67   	    if ((type < XML_ELEMENT_NODE) || (type > XML_XINCLUDE_END)) {
68   	        return "unrecognized type";
69   	    }
70   	    return element_type_names[type];
71   	}
72   	
73   	/*!
74   	 * \internal
75   	 * \brief Apply a function to each XML node in a tree (pre-order, depth-first)
76   	 *
77   	 * \param[in,out] xml        XML tree to traverse
78   	 * \param[in,out] fn         Function to call for each node (returns \c true to
79   	 *                           continue traversing the tree or \c false to stop)
80   	 * \param[in,out] user_data  Argument to \p fn
81   	 *
82   	 * \return \c false if any \p fn call returned \c false, or \c true otherwise
83   	 *
84   	 * \note This function is recursive.
85   	 */
86   	bool
87   	pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
88   	                       void *user_data)
89   	{
90   	    pcmk__assert(fn != NULL);
91   	
92   	    if (xml == NULL) {
93   	        return true;
94   	    }
95   	
96   	    if (!fn(xml, user_data)) {
97   	        return false;
98   	    }
99   	
100  	    for (xml = pcmk__xml_first_child(xml); xml != NULL;
101  	         xml = pcmk__xml_next(xml)) {
102  	
103  	        if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
104  	            return false;
105  	        }
106  	    }
107  	    return true;
108  	}
109  	
110  	void
111  	pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
112  	{
113  	    for (; xml != NULL; xml = xml->parent) {
114  	        xml_node_private_t *nodepriv = xml->_private;
115  	
116  	        if (nodepriv != NULL) {
117  	            pcmk__set_xml_flags(nodepriv, flags);
118  	        }
119  	    }
120  	}
121  	
122  	/*!
123  	 * \internal
124  	 * \brief Set flags for an XML document
125  	 *
126  	 * \param[in,out] doc    XML document
127  	 * \param[in]     flags  Group of <tt>enum pcmk__xml_flags</tt>
128  	 */
129  	void
130  	pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags)
131  	{
132  	    if (doc != NULL) {
133  	        xml_doc_private_t *docpriv = doc->_private;
134  	
135  	        pcmk__set_xml_flags(docpriv, flags);
136  	    }
137  	}
138  	
139  	/*!
140  	 * \internal
141  	 * \brief Check whether the given flags are set for an XML document
142  	 *
143  	 * \param[in] doc    XML document to check
144  	 * \param[in] flags  Group of <tt>enum pcmk__xml_flags</tt>
145  	 *
146  	 * \return \c true if all of \p flags are set for \p doc, or \c false otherwise
147  	 */
148  	bool
149  	pcmk__xml_doc_all_flags_set(const xmlDoc *doc, uint32_t flags)
150  	{
151  	    if (doc != NULL) {
152  	        xml_doc_private_t *docpriv = doc->_private;
153  	
154  	        return (docpriv != NULL) && pcmk__all_flags_set(docpriv->flags, flags);
155  	    }
156  	    return false;
157  	}
158  	
159  	// Mark document, element, and all element's parents as changed
160  	void
161  	pcmk__mark_xml_node_dirty(xmlNode *xml)
162  	{
163  	    if (xml == NULL) {
164  	        return;
165  	    }
166  	    pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_dirty);
167  	    pcmk__xml_set_parent_flags(xml, pcmk__xf_dirty);
168  	}
169  	
170  	/*!
171  	 * \internal
172  	 * \brief Clear flags on an XML node
173  	 *
174  	 * \param[in,out] xml        XML node whose flags to reset
175  	 * \param[in,out] user_data  Ignored
176  	 *
177  	 * \return \c true (to continue traversing the tree)
178  	 *
179  	 * \note This is compatible with \c pcmk__xml_tree_foreach().
180  	 */
181  	bool
182  	pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
183  	{
184  	    xml_node_private_t *nodepriv = xml->_private;
185  	
186  	    if (nodepriv != NULL) {
187  	        nodepriv->flags = pcmk__xf_none;
188  	    }
189  	    return true;
190  	}
191  	
192  	/*!
193  	 * \internal
194  	 * \brief Set the \c pcmk__xf_dirty and \c pcmk__xf_created flags on an XML node
195  	 *
196  	 * \param[in,out] xml        Node whose flags to set
197  	 * \param[in]     user_data  Ignored
198  	 *
199  	 * \return \c true (to continue traversing the tree)
200  	 *
201  	 * \note This is compatible with \c pcmk__xml_tree_foreach().
202  	 */
203  	static bool
204  	mark_xml_dirty_created(xmlNode *xml, void *user_data)
205  	{
206  	    xml_node_private_t *nodepriv = xml->_private;
207  	
208  	    if (nodepriv != NULL) {
209  	        pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
210  	    }
211  	    return true;
212  	}
213  	
214  	/*!
215  	 * \internal
216  	 * \brief Mark an XML tree as dirty and created, and mark its parents dirty
217  	 *
218  	 * Also mark the document dirty.
219  	 *
220  	 * \param[in,out] xml  Tree to mark as dirty and created
221  	 */
222  	static void
223  	mark_xml_tree_dirty_created(xmlNode *xml)
224  	{
225  	    pcmk__assert(xml != NULL);
226  	
227  	    if (!pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking)) {
228  	        // Tracking is disabled for entire document
229  	        return;
230  	    }
231  	
232  	    // Mark all parents and document dirty
233  	    pcmk__mark_xml_node_dirty(xml);
234  	
235  	    pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
236  	}
237  	
238  	// Free an XML object previously marked as deleted
239  	static void
240  	free_deleted_object(void *data)
241  	{
242  	    if(data) {
243  	        pcmk__deleted_xml_t *deleted_obj = data;
244  	
245  	        g_free(deleted_obj->path);
246  	        free(deleted_obj);
247  	    }
248  	}
249  	
250  	/*!
251  	 * \internal
252  	 * \brief Allocate and initialize private data for an XML document
253  	 *
254  	 * \param[in,out] doc  XML document
255  	 */
256  	static void
257  	new_doc_private_data(xmlDoc *doc)
258  	{
259  	    xml_doc_private_t *priv = pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
260  	
261  	    priv->check = PCMK__XML_DOC_PRIVATE_MAGIC;
262  	    doc->_private = priv;
263  	}
264  	
265  	/*!
266  	 * \internal
267  	 * \brief Allocate and initialize private data for a non-document XML node
268  	 *
269  	 * \param[in,out] xml  XML node
270  	 */
271  	static void
272  	new_node_private_data(xmlNode *xml)
273  	{
274  	    const bool tracking = pcmk__xml_doc_all_flags_set(xml->doc,
275  	                                                      pcmk__xf_tracking);
276  	    xml_node_private_t *priv = pcmk__assert_alloc(1,
277  	                                                  sizeof(xml_node_private_t));
278  	
279  	    priv->check = PCMK__XML_NODE_PRIVATE_MAGIC;
280  	    xml->_private = priv;
281  	
282  	    if (tracking) {
283  	        pcmk__set_xml_flags(priv, pcmk__xf_created);
284  	        pcmk__mark_xml_node_dirty(xml);
285  	    }
286  	}
287  	
288  	/*!
289  	 * \internal
290  	 * \brief Allocate and initialize private data for an XML attribute
291  	 *
292  	 * \param[in,out] attr       XML attribute
293  	 * \param[in]     user_data  Ignored
294  	 *
295  	 * \return \c true (to continue iterating)
296  	 *
297  	 * \note This is compatible with \c pcmk__xe_foreach_attr().
298  	 */
299  	static bool
300  	new_attr_private_data(xmlAttr *attr, void *user_data)
301  	{
302  	    new_node_private_data((xmlNode *) attr);
303  	    return true;
304  	}
305  	
306  	/*!
307  	 * \internal
308  	 * \brief Allocate and initialize private data for an XML element
309  	 *
310  	 * \param[in,out] xml  XML element
311  	 */
312  	static void
313  	new_element_private_data(xmlNode *xml)
314  	{
315  	    new_node_private_data(xml);
316  	    pcmk__xe_foreach_attr(xml, new_attr_private_data, NULL);
317  	}
318  	
319  	/*!
320  	 * \internal
321  	 * \brief Allocate and initialize private data for an XML node
322  	 *
323  	 * \param[in,out] node       XML node whose private data to initialize
324  	 * \param[in]     user_data  Ignored
325  	 *
326  	 * \return \c true (to continue traversing the tree)
327  	 *
328  	 * \note This is compatible with \c pcmk__xml_tree_foreach().
329  	 */
330  	static bool
331  	new_private_data(xmlNode *node, void *user_data)
332  	{
333  	    CRM_CHECK(node != NULL, return true);
334  	
335  	    if (node->_private != NULL) {
336  	        return true;
337  	    }
338  	
339  	    switch (node->type) {
340  	        case XML_DOCUMENT_NODE:
341  	            new_doc_private_data((xmlDoc *) node);
342  	            return true;
343  	
344  	        case XML_ATTRIBUTE_NODE:
345  	        case XML_COMMENT_NODE:
346  	            new_node_private_data(node);
347  	            return true;
348  	
349  	        case XML_ELEMENT_NODE:
350  	            new_element_private_data(node);
351  	            return true;
352  	
353  	        case XML_TEXT_NODE:
354  	        case XML_DTD_NODE:
355  	        case XML_CDATA_SECTION_NODE:
356  	            return true;
357  	
358  	        default:
359  	            CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
360  	            return true;
361  	    }
362  	}
363  	
364  	/*!
365  	 * \internal
366  	 * \brief Free and zero all data fields of an XML document's private data
367  	 *
368  	 * This function does not clear the \c check field or free the private data
369  	 * object itself.
370  	 *
371  	 * \param[in,out] docpriv  XML document private data
372  	 */
373  	static void
374  	reset_doc_private_data(xml_doc_private_t *docpriv)
375  	{
(1) Event path: Condition "docpriv == NULL", taking false branch.
376  	    if (docpriv == NULL) {
377  	        return;
378  	    }
379  	
(2) Event path: Condition "!(docpriv->check == 2171757396UL)", taking false branch.
380  	    pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC);
381  	
382  	    docpriv->flags = pcmk__xf_none;
383  	
(3) Event path: Condition "_p", taking true branch.
384  	    g_clear_pointer(&docpriv->acl_user, free);
CID (unavailable; MK=5ab9845ba0821954122dbabbc65debed) (#2 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(4) Event assign_union_field: The union field "in" of "_pp" is written.
(5) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
385  	    g_clear_pointer(&docpriv->acls, pcmk__free_acls);
386  	
387  	    g_list_free_full(docpriv->deleted_objs, free_deleted_object);
388  	    docpriv->deleted_objs = NULL;
389  	}
390  	
391  	/*!
392  	 * \internal
393  	 * \brief Free and clear private data for an XML document
394  	 *
395  	 * \param[in,out] doc  XML document
396  	 */
397  	static void
398  	free_doc_private_data(xmlDoc *doc)
399  	{
400  	    reset_doc_private_data(doc->_private);
401  	    g_clear_pointer(&doc->_private, free);
402  	}
403  	
404  	/*!
405  	 * \internal
406  	 * \brief Free and clear private data for a non-document XML node
407  	 *
408  	 * \param[in,out] xml  XML node
409  	 */
410  	static void
411  	free_node_private_data(xmlNode *xml)
412  	{
413  	    xml_node_private_t *nodepriv = xml->_private;
414  	
415  	    pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC);
416  	
417  	    g_clear_pointer(&xml->_private, free);
418  	}
419  	
420  	/*!
421  	 * \internal
422  	 * \brief Free and clear private data for an XML attribute
423  	 *
424  	 * \param[in,out] attr       XML attribute
425  	 * \param[in]     user_data  Ignored
426  	 *
427  	 * \return \c true (to continue iterating)
428  	 *
429  	 * \note This is compatible with \c pcmk__xe_foreach_attr().
430  	 */
431  	static bool
432  	free_attr_private_data(xmlAttr *xml, void *user_data)
433  	{
434  	    free_node_private_data((xmlNode *) xml);
435  	    return true;
436  	}
437  	
438  	/*!
439  	 * \internal
440  	 * \brief Free and clear private data for an XML element and its attributes
441  	 *
442  	 * \param[in,out] xml  XML element
443  	 */
444  	static void
445  	free_element_private_data(xmlNode *xml)
446  	{
447  	    free_node_private_data(xml);
448  	    pcmk__xe_foreach_attr(xml, free_attr_private_data, NULL);
449  	}
450  	
451  	/*!
452  	 * \internal
453  	 * \brief Free private data for an XML node
454  	 *
455  	 * \param[in,out] node       XML node whose private data to free
456  	 * \param[in]     user_data  Ignored
457  	 *
458  	 * \return \c true (to continue traversing the tree)
459  	 *
460  	 * \note This is compatible with \c pcmk__xml_tree_foreach().
461  	 */
462  	static bool
463  	free_private_data(xmlNode *node, void *user_data)
464  	{
465  	    CRM_CHECK(node != NULL, return true);
466  	
467  	    if (node->_private == NULL) {
468  	        return true;
469  	    }
470  	
471  	    switch (node->type) {
472  	        case XML_DOCUMENT_NODE:
473  	            free_doc_private_data((xmlDoc *) node);
474  	            return true;
475  	
476  	        case XML_ELEMENT_NODE:
477  	            free_element_private_data(node);
478  	            return true;
479  	
480  	        default:
481  	            free_node_private_data(node);
482  	            return true;
483  	    }
484  	}
485  	
486  	/*!
487  	 * \internal
488  	 * \brief Allocate and initialize private data recursively for an XML tree
489  	 *
490  	 * \param[in,out] node  XML node whose private data to initialize
491  	 */
492  	void
493  	pcmk__xml_new_private_data(xmlNode *xml)
494  	{
495  	    pcmk__xml_tree_foreach(xml, new_private_data, NULL);
496  	}
497  	
498  	/*!
499  	 * \internal
500  	 * \brief Free private data recursively for an XML tree
501  	 *
502  	 * \param[in,out] node  XML node whose private data to free
503  	 */
504  	void
505  	pcmk__xml_free_private_data(xmlNode *xml)
506  	{
507  	    pcmk__xml_tree_foreach(xml, free_private_data, NULL);
508  	}
509  	
510  	/*!
511  	 * \internal
512  	 * \brief Return ordinal position of an XML node among its siblings
513  	 *
514  	 * \param[in] xml            XML node to check
515  	 * \param[in] ignore_if_set  Don't count siblings with this flag set
516  	 *
517  	 * \return Ordinal position of \p xml (starting with 0)
518  	 */
519  	int
520  	pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set)
521  	{
522  	    int position = 0;
523  	
524  	    for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
525  	        xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
526  	
527  	        if (!pcmk__is_set(nodepriv->flags, ignore_if_set)) {
528  	            position++;
529  	        }
530  	    }
531  	
532  	    return position;
533  	}
534  	
535  	/*!
536  	 * \internal
537  	 * \brief Remove all attributes marked as deleted from an XML node
538  	 *
539  	 * \param[in,out] xml        XML node whose deleted attributes to remove
540  	 * \param[in,out] user_data  Ignored
541  	 *
542  	 * \return \c true (to continue traversing the tree)
543  	 *
544  	 * \note This is compatible with \c pcmk__xml_tree_foreach().
545  	 */
546  	static bool
547  	commit_attr_deletions(xmlNode *xml, void *user_data)
548  	{
549  	    pcmk__xml_reset_node_flags(xml, NULL);
550  	    pcmk__xe_remove_matching_attrs(xml, true, pcmk__marked_as_deleted, NULL);
551  	    return true;
552  	}
553  	
554  	/*!
555  	 * \internal
556  	 * \brief Finalize all pending changes to an XML document and reset private data
557  	 *
558  	 * Clear the ACL user and all flags, unpacked ACLs, and deleted node records for
559  	 * the document; clear all flags on each node in the tree; and delete any
560  	 * attributes that are marked for deletion.
561  	 *
562  	 * \param[in,out] doc  XML document
563  	 *
564  	 * \note When change tracking is enabled, "deleting" an attribute simply marks
565  	 *       it for deletion (using \c pcmk__xf_deleted) until changes are
566  	 *       committed. Freeing a node (using \c pcmk__xml_free()) adds a deleted
567  	 *       node record (\c pcmk__deleted_xml_t) to the node's document before
568  	 *       freeing it.
569  	 * \note This function clears all flags, not just flags that indicate changes.
570  	 *       In particular, note that it clears the \c pcmk__xf_tracking flag, thus
571  	 *       disabling tracking.
572  	 */
573  	void
574  	pcmk__xml_commit_changes(xmlDoc *doc)
575  	{
576  	    xml_doc_private_t *docpriv = NULL;
577  	
578  	    if (doc == NULL) {
579  	        return;
580  	    }
581  	
582  	    docpriv = doc->_private;
583  	    if (docpriv == NULL) {
584  	        return;
585  	    }
586  	
587  	    if (pcmk__is_set(docpriv->flags, pcmk__xf_dirty)) {
588  	        pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions,
589  	                               NULL);
590  	    }
591  	    reset_doc_private_data(docpriv);
592  	}
593  	
594  	/*!
595  	 * \internal
596  	 * \brief Create a new XML document
597  	 *
598  	 * \return Newly allocated XML document (guaranteed not to be \c NULL)
599  	 *
600  	 * \note The caller is responsible for freeing the return value using
601  	 *       \c pcmk__xml_free_doc().
602  	 */
603  	xmlDoc *
604  	pcmk__xml_new_doc(void)
605  	{
606  	    xmlDoc *doc = xmlNewDoc(XML_VERSION);
607  	
608  	    pcmk__mem_assert(doc);
609  	    pcmk__xml_new_private_data((xmlNode *) doc);
610  	    return doc;
611  	}
612  	
613  	/*!
614  	 * \internal
615  	 * \brief Free a new XML document
616  	 *
617  	 * \param[in,out] doc  XML document to free
618  	 */
619  	void
620  	pcmk__xml_free_doc(xmlDoc *doc)
621  	{
622  	    if (doc != NULL) {
623  	        pcmk__xml_free_private_data((xmlNode *) doc);
624  	        xmlFreeDoc(doc);
625  	    }
626  	}
627  	
628  	/*!
629  	 * \internal
630  	 * \brief Check whether the first character of a string is an XML NameStartChar
631  	 *
632  	 * See https://www.w3.org/TR/xml/#NT-NameStartChar.
633  	 *
634  	 * This is almost identical to libxml2's \c xmlIsDocNameStartChar(), but they
635  	 * don't expose it as part of the public API.
636  	 *
637  	 * \param[in]  utf8  UTF-8 encoded string
638  	 * \param[out] len   If not \c NULL, where to store size in bytes of first
639  	 *                   character in \p utf8
640  	 *
641  	 * \return \c true if \p utf8 begins with a valid XML NameStartChar, or \c false
642  	 *         otherwise
643  	 */
644  	bool
645  	pcmk__xml_is_name_start_char(const char *utf8, int *len)
646  	{
647  	    int c = 0;
648  	    int local_len = 0;
649  	
650  	    if (len == NULL) {
651  	        len = &local_len;
652  	    }
653  	
654  	    /* xmlGetUTF8Char() abuses the len argument. At call time, it must be set to
655  	     * "the minimum number of bytes present in the sequence... to assure the
656  	     * next character is completely contained within the sequence." It's similar
657  	     * to the "n" in the strn*() functions. However, this doesn't make any sense
658  	     * for null-terminated strings, and there's no value that indicates "keep
659  	     * going until '\0'." So we set it to 4, the max number of bytes in a UTF-8
660  	     * character.
661  	     *
662  	     * At return, it's set to the actual number of bytes in the char, or 0 on
663  	     * error.
664  	     */
665  	    *len = 4;
666  	
667  	    // Note: xmlGetUTF8Char() assumes a 32-bit int
668  	    c = xmlGetUTF8Char((const xmlChar *) utf8, len);
669  	    if (c < 0) {
670  	        GString *buf = g_string_sized_new(32);
671  	
672  	        for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
673  	            g_string_append_printf(buf, " 0x%.2X", utf8[i]);
674  	        }
675  	        pcmk__info("Invalid UTF-8 character (bytes:%s)",
676  	                   (pcmk__str_empty(buf->str)? " <none>" : buf->str));
677  	        g_string_free(buf, TRUE);
678  	        return false;
679  	    }
680  	
681  	    return (c == '_')
682  	           || (c == ':')
683  	           || ((c >= 'a') && (c <= 'z'))
684  	           || ((c >= 'A') && (c <= 'Z'))
685  	           || ((c >= 0xC0) && (c <= 0xD6))
686  	           || ((c >= 0xD8) && (c <= 0xF6))
687  	           || ((c >= 0xF8) && (c <= 0x2FF))
688  	           || ((c >= 0x370) && (c <= 0x37D))
689  	           || ((c >= 0x37F) && (c <= 0x1FFF))
690  	           || ((c >= 0x200C) && (c <= 0x200D))
691  	           || ((c >= 0x2070) && (c <= 0x218F))
692  	           || ((c >= 0x2C00) && (c <= 0x2FEF))
693  	           || ((c >= 0x3001) && (c <= 0xD7FF))
694  	           || ((c >= 0xF900) && (c <= 0xFDCF))
695  	           || ((c >= 0xFDF0) && (c <= 0xFFFD))
696  	           || ((c >= 0x10000) && (c <= 0xEFFFF));
697  	}
698  	
699  	/*!
700  	 * \internal
701  	 * \brief Check whether the first character of a string is an XML NameChar
702  	 *
703  	 * See https://www.w3.org/TR/xml/#NT-NameChar.
704  	 *
705  	 * This is almost identical to libxml2's \c xmlIsDocNameChar(), but they don't
706  	 * expose it as part of the public API.
707  	 *
708  	 * \param[in]  utf8  UTF-8 encoded string
709  	 * \param[out] len   If not \c NULL, where to store size in bytes of first
710  	 *                   character in \p utf8
711  	 *
712  	 * \return \c true if \p utf8 begins with a valid XML NameChar, or \c false
713  	 *         otherwise
714  	 */
715  	bool
716  	pcmk__xml_is_name_char(const char *utf8, int *len)
717  	{
718  	    int c = 0;
719  	    int local_len = 0;
720  	
721  	    if (len == NULL) {
722  	        len = &local_len;
723  	    }
724  	
725  	    // See comment regarding len in pcmk__xml_is_name_start_char()
726  	    *len = 4;
727  	
728  	    // Note: xmlGetUTF8Char() assumes a 32-bit int
729  	    c = xmlGetUTF8Char((const xmlChar *) utf8, len);
730  	    if (c < 0) {
731  	        GString *buf = g_string_sized_new(32);
732  	
733  	        for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
734  	            g_string_append_printf(buf, " 0x%.2X", utf8[i]);
735  	        }
736  	        pcmk__info("Invalid UTF-8 character (bytes:%s)",
737  	                   (pcmk__str_empty(buf->str)? " <none>" : buf->str));
738  	        g_string_free(buf, TRUE);
739  	        return false;
740  	    }
741  	
742  	    return ((c >= 'a') && (c <= 'z'))
743  	           || ((c >= 'A') && (c <= 'Z'))
744  	           || ((c >= '0') && (c <= '9'))
745  	           || (c == '_')
746  	           || (c == ':')
747  	           || (c == '-')
748  	           || (c == '.')
749  	           || (c == 0xB7)
750  	           || ((c >= 0xC0) && (c <= 0xD6))
751  	           || ((c >= 0xD8) && (c <= 0xF6))
752  	           || ((c >= 0xF8) && (c <= 0x2FF))
753  	           || ((c >= 0x300) && (c <= 0x36F))
754  	           || ((c >= 0x370) && (c <= 0x37D))
755  	           || ((c >= 0x37F) && (c <= 0x1FFF))
756  	           || ((c >= 0x200C) && (c <= 0x200D))
757  	           || ((c >= 0x203F) && (c <= 0x2040))
758  	           || ((c >= 0x2070) && (c <= 0x218F))
759  	           || ((c >= 0x2C00) && (c <= 0x2FEF))
760  	           || ((c >= 0x3001) && (c <= 0xD7FF))
761  	           || ((c >= 0xF900) && (c <= 0xFDCF))
762  	           || ((c >= 0xFDF0) && (c <= 0xFFFD))
763  	           || ((c >= 0x10000) && (c <= 0xEFFFF));
764  	}
765  	
766  	/*!
767  	 * \internal
768  	 * \brief Sanitize a string so it is usable as an XML ID
769  	 *
770  	 * An ID must match the Name production as defined here:
771  	 * https://www.w3.org/TR/xml/#NT-Name.
772  	 *
773  	 * Convert an invalid start character to \c '_'. Convert an invalid character
774  	 * after the start character to \c '.'.
775  	 *
776  	 * \param[in,out] id  String to sanitize
777  	 */
778  	void
779  	pcmk__xml_sanitize_id(char *id)
780  	{
781  	    bool valid = true;
782  	    int len = 0;
783  	
784  	    // If id is empty or NULL, there's no way to make it a valid XML ID
785  	    pcmk__assert(!pcmk__str_empty(id));
786  	
787  	    /* @TODO Suppose there are two strings and each has an invalid ID character
788  	     * in the same position. The strings are otherwise identical. Both strings
789  	     * will be sanitized to the same valid ID, which is incorrect.
790  	     *
791  	     * The caller is responsible for ensuring the sanitized ID does not already
792  	     * exist in a given XML document before using it, if uniqueness is desired.
793  	     */
794  	    valid = pcmk__xml_is_name_start_char(id, &len);
795  	    CRM_CHECK(len > 0, return); // UTF-8 encoding error
796  	    if (!valid) {
797  	        *id = '_';
798  	        for (int i = 1; i < len; i++) {
799  	            id[i] = '.';
800  	        }
801  	    }
802  	
803  	    for (id += len; *id != '\0'; id += len) {
804  	        valid = pcmk__xml_is_name_char(id, &len);
805  	        CRM_CHECK(len > 0, return); // UTF-8 encoding error
806  	        if (!valid) {
807  	            for (int i = 0; i < len; i++) {
808  	                id[i] = '.';
809  	            }
810  	        }
811  	    }
812  	}
813  	
814  	/*!
815  	 * \internal
816  	 * \brief Free an XML tree without ACL checks or change tracking
817  	 *
818  	 * \param[in,out] xml  XML node to free
819  	 */
820  	void
821  	pcmk__xml_free_node(xmlNode *xml)
822  	{
823  	    pcmk__xml_free_private_data(xml);
824  	    xmlUnlinkNode(xml);
825  	    xmlFreeNode(xml);
826  	}
827  	
828  	/*!
829  	 * \internal
830  	 * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
831  	 *
832  	 * If \p node is the root of its document, free the entire document.
833  	 *
834  	 * \param[in,out] node      XML node to free
835  	 * \param[in]     position  Position of \p node among its siblings for change
836  	 *                          tracking (negative to calculate automatically if
837  	 *                          needed)
838  	 *
839  	 * \return Standard Pacemaker return code
840  	 */
841  	static int
842  	free_xml_with_position(xmlNode *node, int position)
843  	{
844  	    xmlDoc *doc = NULL;
845  	    xml_node_private_t *nodepriv = NULL;
846  	    xml_doc_private_t *docpriv = NULL;
847  	    GString *xpath = NULL;
848  	
849  	    if (node == NULL) {
850  	        return pcmk_rc_ok;
851  	    }
852  	    doc = node->doc;
853  	    nodepriv = node->_private;
854  	
855  	    if ((doc != NULL) && (xmlDocGetRootElement(doc) == node)) {
856  	        /* @TODO Should we check ACLs first? Otherwise it seems like we could
857  	         * free the root element without write permission.
858  	         */
859  	        pcmk__xml_free_doc(doc);
860  	        return pcmk_rc_ok;
861  	    }
862  	
863  	    if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
864  	        pcmk__if_tracing(
865  	            {
866  	                GString *xpath = pcmk__element_xpath(node);
867  	
868  	                qb_log_from_external_source(__func__, __FILE__,
869  	                                            "Cannot remove %s %x", LOG_TRACE,
870  	                                            __LINE__, 0, xpath->str,
871  	                                            nodepriv->flags);
872  	                g_string_free(xpath, TRUE);
873  	            },
874  	            {}
875  	        );
876  	        return EACCES;
877  	    }
878  	
879  	    if (!pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking)
880  	        || pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
881  	        pcmk__xml_free_node(node);
882  	        return pcmk_rc_ok;
883  	    }
884  	
885  	    pcmk__assert(doc != NULL);
886  	
887  	    docpriv = doc->_private;
888  	    xpath = pcmk__element_xpath(node);
889  	
890  	    if (xpath != NULL) {
891  	        pcmk__deleted_xml_t *deleted_obj = NULL;
892  	
893  	        pcmk__trace("Deleting %s %p from %p", xpath->str, node, doc);
894  	
895  	        deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
896  	        deleted_obj->path = g_string_free(xpath, FALSE);
897  	        deleted_obj->position = -1;
898  	
899  	        // Record the position only for XML comments for now
900  	        if (node->type == XML_COMMENT_NODE) {
901  	            if (position >= 0) {
902  	                deleted_obj->position = position;
903  	
904  	            } else {
905  	                deleted_obj->position = pcmk__xml_position(node, pcmk__xf_skip);
906  	            }
907  	        }
908  	
909  	        docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
910  	                                              deleted_obj);
911  	        pcmk__xml_doc_set_flags(node->doc, pcmk__xf_dirty);
912  	    }
913  	
914  	    pcmk__xml_free_node(node);
915  	    return pcmk_rc_ok;
916  	}
917  	
918  	/*!
919  	 * \internal
920  	 * \brief Free an XML tree if ACLs allow; track deletion if tracking is enabled
921  	 *
922  	 * If \p xml is the root of its document, free the entire document.
923  	 *
924  	 * \param[in,out] xml  XML node to free
925  	 */
926  	void
927  	pcmk__xml_free(xmlNode *xml)
928  	{
929  	    free_xml_with_position(xml, -1);
930  	}
931  	
932  	/*!
933  	 * \internal
934  	 * \brief Make a deep copy of an XML node under a given parent
935  	 *
936  	 * \param[in,out] parent  XML element that will be the copy's parent (\c NULL
937  	 *                        to create a new XML document with the copy as root)
938  	 * \param[in]     src     XML node to copy
939  	 *
940  	 * \return Deep copy of \p src, or \c NULL if \p src is \c NULL
941  	 */
942  	xmlNode *
943  	pcmk__xml_copy(xmlNode *parent, xmlNode *src)
944  	{
945  	    xmlNode *copy = NULL;
946  	
947  	    if (src == NULL) {
948  	        return NULL;
949  	    }
950  	
951  	    if (parent == NULL) {
952  	        xmlDoc *doc = NULL;
953  	
954  	        // The copy will be the root element of a new document
955  	        pcmk__assert(src->type == XML_ELEMENT_NODE);
956  	
957  	        doc = pcmk__xml_new_doc();
958  	        copy = xmlDocCopyNode(src, doc, 1);
959  	        pcmk__mem_assert(copy);
960  	
961  	        xmlDocSetRootElement(doc, copy);
962  	
963  	    } else {
964  	        copy = xmlDocCopyNode(src, parent->doc, 1);
965  	        pcmk__mem_assert(copy);
966  	
967  	        xmlAddChild(parent, copy);
968  	    }
969  	
970  	    pcmk__xml_new_private_data(copy);
971  	    return copy;
972  	}
973  	
974  	/*!
975  	 * \internal
976  	 * \brief Replace one XML node with a copy of another XML node
977  	 *
978  	 * This function handles change tracking and applies ACLs.
979  	 *
980  	 * \param[in,out] old  XML node to replace
981  	 * \param[in]     new  XML node to copy as replacement for \p old
982  	 *
983  	 * \return Copy of \p new that replaced \p old
984  	 *
985  	 * \note This frees \p old.
986  	 * \note The caller is responsible for freeing the return value using
987  	 *       \c pcmk__xml_free() (but note that it may be part of a larger XML
988  	 *       tree).
989  	 */
990  	xmlNode *
991  	pcmk__xml_replace_with_copy(xmlNode *old, xmlNode *new)
992  	{
993  	    xmlNode *new_copy = NULL;
994  	
995  	    pcmk__assert((old != NULL) && (new != NULL));
996  	
997  	    /* Pass old to pcmk__xml_copy() so that new_copy gets created within the
998  	     * same doc. But old won't remain its parent.
999  	     */
1000 	    new_copy = pcmk__xml_copy(old, new);
1001 	    old = xmlReplaceNode(old, new_copy);
1002 	
1003 	    // old == NULL means memory allocation error
1004 	    pcmk__assert(old != NULL);
1005 	
1006 	    // May be unnecessary but avoids slight changes to some test outputs
1007 	    pcmk__xml_tree_foreach(new_copy, pcmk__xml_reset_node_flags, NULL);
1008 	
1009 	    if (pcmk__xml_doc_all_flags_set(new_copy->doc, pcmk__xf_tracking)) {
1010 	        // Replaced sections may have included relevant ACLs
1011 	        pcmk__apply_acls(new_copy->doc);
1012 	    }
1013 	    pcmk__xml_mark_changes(old, new_copy);
1014 	    pcmk__xml_free_node(old);
1015 	
1016 	    return new_copy;
1017 	}
1018 	
1019 	/*!
1020 	 * \internal
1021 	 * \brief Remove XML text nodes from specified XML and all its children
1022 	 *
1023 	 * \param[in,out] xml  XML to strip text from
1024 	 */
1025 	void
1026 	pcmk__strip_xml_text(xmlNode *xml)
1027 	{
1028 	    xmlNode *iter = xml->children;
1029 	
1030 	    while (iter) {
1031 	        xmlNode *next = iter->next;
1032 	
1033 	        switch (iter->type) {
1034 	            case XML_TEXT_NODE:
1035 	                pcmk__xml_free_node(iter);
1036 	                break;
1037 	
1038 	            case XML_ELEMENT_NODE:
1039 	                /* Search it */
1040 	                pcmk__strip_xml_text(iter);
1041 	                break;
1042 	
1043 	            default:
1044 	                /* Leave it */
1045 	                break;
1046 	        }
1047 	
1048 	        iter = next;
1049 	    }
1050 	}
1051 	
1052 	/*!
1053 	 * \internal
1054 	 * \brief Check whether a string has XML special characters that must be escaped
1055 	 *
1056 	 * See \c pcmk__xml_escape() and \c pcmk__xml_escape_type for more details.
1057 	 *
1058 	 * \param[in] text  String to check
1059 	 * \param[in] type  Type of escaping
1060 	 *
1061 	 * \return \c true if \p text has special characters that need to be escaped, or
1062 	 *         \c false otherwise
1063 	 */
1064 	bool
1065 	pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
1066 	{
1067 	    if (text == NULL) {
1068 	        return false;
1069 	    }
1070 	
1071 	    while (*text != '\0') {
1072 	        switch (type) {
1073 	            case pcmk__xml_escape_text:
1074 	                switch (*text) {
1075 	                    case '<':
1076 	                    case '>':
1077 	                    case '&':
1078 	                        return true;
1079 	                    case '\n':
1080 	                    case '\t':
1081 	                        break;
1082 	                    default:
1083 	                        if (g_ascii_iscntrl(*text)) {
1084 	                            return true;
1085 	                        }
1086 	                        break;
1087 	                }
1088 	                break;
1089 	
1090 	            case pcmk__xml_escape_attr:
1091 	                switch (*text) {
1092 	                    case '<':
1093 	                    case '>':
1094 	                    case '&':
1095 	                    case '"':
1096 	                        return true;
1097 	                    default:
1098 	                        if (g_ascii_iscntrl(*text)) {
1099 	                            return true;
1100 	                        }
1101 	                        break;
1102 	                }
1103 	                break;
1104 	
1105 	            case pcmk__xml_escape_attr_pretty:
1106 	                switch (*text) {
1107 	                    case '\n':
1108 	                    case '\r':
1109 	                    case '\t':
1110 	                    case '"':
1111 	                        return true;
1112 	                    default:
1113 	                        break;
1114 	                }
1115 	                break;
1116 	
1117 	            default:    // Invalid enum value
1118 	                pcmk__assert(false);
1119 	                break;
1120 	        }
1121 	
1122 	        text = g_utf8_next_char(text);
1123 	    }
1124 	    return false;
1125 	}
1126 	
1127 	/*!
1128 	 * \internal
1129 	 * \brief Replace special characters with their XML escape sequences
1130 	 *
1131 	 * \param[in] text  Text to escape
1132 	 * \param[in] type  Type of escaping
1133 	 *
1134 	 * \return Newly allocated string equivalent to \p text but with special
1135 	 *         characters replaced with XML escape sequences (or \c NULL if \p text
1136 	 *         is \c NULL). If \p text is not \c NULL, the return value is
1137 	 *         guaranteed not to be \c NULL.
1138 	 *
1139 	 * \note There are libxml functions that purport to do this:
1140 	 *       \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
1141 	 *       However, their escaping is incomplete. See:
1142 	 *       https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
1143 	 * \note The caller is responsible for freeing the return value using
1144 	 *       \c g_free().
1145 	 */
1146 	gchar *
1147 	pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
1148 	{
1149 	    GString *copy = NULL;
1150 	
1151 	    if (text == NULL) {
1152 	        return NULL;
1153 	    }
1154 	    copy = g_string_sized_new(strlen(text));
1155 	
1156 	    while (*text != '\0') {
1157 	        // Don't escape any non-ASCII characters
1158 	        if ((*text & 0x80) != 0) {
1159 	            size_t bytes = g_utf8_next_char(text) - text;
1160 	
1161 	            g_string_append_len(copy, text, bytes);
1162 	            text += bytes;
1163 	            continue;
1164 	        }
1165 	
1166 	        switch (type) {
1167 	            case pcmk__xml_escape_text:
1168 	                switch (*text) {
1169 	                    case '<':
1170 	                        g_string_append(copy, PCMK__XML_ENTITY_LT);
1171 	                        break;
1172 	                    case '>':
1173 	                        g_string_append(copy, PCMK__XML_ENTITY_GT);
1174 	                        break;
1175 	                    case '&':
1176 	                        g_string_append(copy, PCMK__XML_ENTITY_AMP);
1177 	                        break;
1178 	                    case '\n':
1179 	                    case '\t':
1180 	                        g_string_append_c(copy, *text);
1181 	                        break;
1182 	                    default:
1183 	                        if (g_ascii_iscntrl(*text)) {
1184 	                            g_string_append_printf(copy, "&#x%.2X;", *text);
1185 	                        } else {
1186 	                            g_string_append_c(copy, *text);
1187 	                        }
1188 	                        break;
1189 	                }
1190 	                break;
1191 	
1192 	            case pcmk__xml_escape_attr:
1193 	                switch (*text) {
1194 	                    case '<':
1195 	                        g_string_append(copy, PCMK__XML_ENTITY_LT);
1196 	                        break;
1197 	                    case '>':
1198 	                        g_string_append(copy, PCMK__XML_ENTITY_GT);
1199 	                        break;
1200 	                    case '&':
1201 	                        g_string_append(copy, PCMK__XML_ENTITY_AMP);
1202 	                        break;
1203 	                    case '"':
1204 	                        g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1205 	                        break;
1206 	                    default:
1207 	                        if (g_ascii_iscntrl(*text)) {
1208 	                            g_string_append_printf(copy, "&#x%.2X;", *text);
1209 	                        } else {
1210 	                            g_string_append_c(copy, *text);
1211 	                        }
1212 	                        break;
1213 	                }
1214 	                break;
1215 	
1216 	            case pcmk__xml_escape_attr_pretty:
1217 	                switch (*text) {
1218 	                    case '"':
1219 	                        g_string_append(copy, "\\\"");
1220 	                        break;
1221 	                    case '\n':
1222 	                        g_string_append(copy, "\\n");
1223 	                        break;
1224 	                    case '\r':
1225 	                        g_string_append(copy, "\\r");
1226 	                        break;
1227 	                    case '\t':
1228 	                        g_string_append(copy, "\\t");
1229 	                        break;
1230 	                    default:
1231 	                        g_string_append_c(copy, *text);
1232 	                        break;
1233 	                }
1234 	                break;
1235 	
1236 	            default:    // Invalid enum value
1237 	                pcmk__assert(false);
1238 	                break;
1239 	        }
1240 	
1241 	        text = g_utf8_next_char(text);
1242 	    }
1243 	    return g_string_free(copy, FALSE);
1244 	}
1245 	
1246 	/*!
1247 	 * \internal
1248 	 * \brief Set the \c pcmk__xf_created flag on an attribute
1249 	 *
1250 	 * \param[in,out] attr       XML attribute
1251 	 * \param[in]     user_data  Ignored
1252 	 *
1253 	 * \return \c true (to continue iterating)
1254 	 *
1255 	 * \note This is compatible with \c pcmk__xe_foreach_attr().
1256 	 */
1257 	static bool
1258 	mark_attr_created(xmlAttr *attr, void *user_data)
1259 	{
1260 	    xml_node_private_t *nodepriv = attr->_private;
1261 	
1262 	    pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
1263 	    return true;
1264 	}
1265 	
1266 	/*!
1267 	 * \internal
1268 	 * \brief Add an XML attribute to a node, marked as deleted
1269 	 *
1270 	 * When calculating XML changes, we need to know when an attribute has been
1271 	 * deleted. Add the attribute back to the new XML, so that we can check the
1272 	 * removal against ACLs, and mark it as deleted for later removal after
1273 	 * differences have been calculated.
1274 	 *
1275 	 * \param[in,out] new_xml     XML to modify
1276 	 * \param[in]     attr_name   Name of attribute that was deleted
1277 	 * \param[in]     old_value   Value of attribute that was deleted
1278 	 */
1279 	static void
1280 	mark_attr_deleted(xmlNode *new_xml, const char *attr_name,
1281 	                  const char *old_value)
1282 	{
1283 	    xml_doc_private_t *docpriv = new_xml->doc->_private;
1284 	    xmlAttr *attr = NULL;
1285 	    xml_node_private_t *nodepriv;
1286 	
1287 	    /* Restore the old value (without setting dirty flag recursively upwards or
1288 	     * checking ACLs)
1289 	     */
1290 	    pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1291 	    pcmk__xe_set(new_xml, attr_name, old_value);
1292 	    pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1293 	
1294 	    // Reset flags (so the attribute doesn't appear as newly created)
1295 	    attr = xmlHasProp(new_xml, (const xmlChar *) attr_name);
1296 	    nodepriv = attr->_private;
1297 	    nodepriv->flags = 0;
1298 	
1299 	    // Check ACLs and mark restored value for later removal
1300 	    pcmk__xa_remove(attr, false);
1301 	
1302 	    pcmk__trace("XML attribute %s=%s was removed from %s", attr_name, old_value,
1303 	                (const char *) new_xml->name);
1304 	}
1305 	
1306 	/*
1307 	 * \internal
1308 	 * \brief Check ACLs for a changed XML attribute
1309 	 */
1310 	static void
1311 	mark_attr_changed(xmlNode *new_xml, const char *attr_name,
1312 	                  const char *old_value)
1313 	{
1314 	    xml_doc_private_t *docpriv = new_xml->doc->_private;
1315 	    char *vcopy = pcmk__xe_get_copy(new_xml, attr_name);
1316 	
1317 	    pcmk__trace("XML attribute %s was changed from '%s' to '%s' in %s",
1318 	                attr_name, old_value, vcopy, (const char *) new_xml->name);
1319 	
1320 	    // Restore the original value (without checking ACLs)
1321 	    pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1322 	    pcmk__xe_set(new_xml, attr_name, old_value);
1323 	    pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1324 	
1325 	    // Change it back to the new value, to check ACLs
1326 	    pcmk__xe_set(new_xml, attr_name, vcopy);
1327 	    free(vcopy);
1328 	}
1329 	
1330 	/*!
1331 	 * \internal
1332 	 * \brief Mark an XML attribute as having changed position
1333 	 *
1334 	 * \param[in,out] new_xml     XML to modify
1335 	 * \param[in,out] old_attr    Attribute that moved, in original XML
1336 	 * \param[in,out] new_attr    Attribute that moved, in \p new_xml
1337 	 * \param[in]     p_old       Ordinal position of \p old_attr in original XML
1338 	 * \param[in]     p_new       Ordinal position of \p new_attr in \p new_xml
1339 	 */
1340 	static void
1341 	mark_attr_moved(xmlNode *new_xml, xmlAttr *old_attr, xmlAttr *new_attr,
1342 	                int p_old, int p_new)
1343 	{
1344 	    xml_node_private_t *nodepriv = new_attr->_private;
1345 	
1346 	    pcmk__trace("XML attribute %s moved from position %d to %d in %s",
1347 	                old_attr->name, p_old, p_new, (const char *) new_xml->name);
1348 	
1349 	    // Mark document, element, and all element's parents as changed
1350 	    pcmk__mark_xml_node_dirty(new_xml);
1351 	
1352 	    // Mark attribute as changed
1353 	    pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
1354 	
1355 	    nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1356 	    pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1357 	}
1358 	
1359 	/*!
1360 	 * \internal
1361 	 * \brief Mark an XML attribute as deleted, changed, or moved if appropriate
1362 	 *
1363 	 * Given an attribute (from an old XML element) and a new XML element, check
1364 	 * whether the attribute has been deleted, changed, or moved between the old and
1365 	 * new elements. If so, mark the new XML element to indicate what changed.
1366 	 *
1367 	 * \param[in,out] old_attr   XML attribute from old element
1368 	 * \param[in,out] user_data  New XML element
1369 	 *
1370 	 * \return \c true (to continue iterating)
1371 	 *
1372 	 * \note This is compatible with \c pcmk__xe_foreach_attr().
1373 	 */
1374 	static bool
1375 	mark_attr_diff(xmlAttr *old_attr, void *user_data)
1376 	{
1377 	    xmlNode *new_xml = user_data;
1378 	
1379 	    const char *name = (const char *) old_attr->name;
1380 	
1381 	    xmlAttr *new_attr = xmlHasProp(new_xml, old_attr->name);
1382 	    xml_node_private_t *new_priv = NULL;
1383 	
1384 	    const char *old_value = pcmk__xml_attr_value(old_attr);
1385 	    const char *new_value = NULL;
1386 	
1387 	    int old_pos = 0;
1388 	    int new_pos = 0;
1389 	
1390 	    if (new_attr == NULL) {
1391 	        mark_attr_deleted(new_xml, name, old_value);
1392 	        return true;
1393 	    }
1394 	
1395 	    new_priv = new_attr->_private;
1396 	    new_value = pcmk__xe_get(new_xml, name);
1397 	
1398 	    // This attribute isn't new
1399 	    pcmk__clear_xml_flags(new_priv, pcmk__xf_created);
1400 	
1401 	    if (!pcmk__str_eq(old_value, new_value, pcmk__str_none)) {
1402 	        mark_attr_changed(new_xml, name, old_value);
1403 	        return true;
1404 	    }
1405 	
1406 	    old_pos = pcmk__xml_position((xmlNode *) old_attr, pcmk__xf_skip);
1407 	    new_pos = pcmk__xml_position((xmlNode *) new_attr, pcmk__xf_skip);
1408 	
1409 	    if ((old_pos == new_pos)
1410 	        || pcmk__xml_doc_all_flags_set(new_xml->doc,
1411 	                                       pcmk__xf_ignore_attr_pos)) {
1412 	        return true;
1413 	    }
1414 	
1415 	    mark_attr_moved(new_xml, old_attr, new_attr, old_pos, new_pos);
1416 	    return true;
1417 	}
1418 	
1419 	/*!
1420 	 * \internal
1421 	 * \brief Mark a new attribute dirty if ACLs allow creation, or remove otherwise
1422 	 *
1423 	 * We set the \c pcmk__xf_created flag on all attributes in the new XML at an
1424 	 * earlier stage of change calculation. Then we checked whether each attribute
1425 	 * was present in the old XML, and we cleared the flag if so. If the flag is
1426 	 * still set, then the attribute is truly new.
1427 	 *
1428 	 * Now we check whether ACLs allow the attribute's creation. If so, we "accept"
1429 	 * it: we mark the attribute as dirty and modified, and we mark all of its
1430 	 * parents as dirty. Otherwise, we reject it by removing the attribute (ignoring
1431 	 * ACLs and change tracking for the removal).
1432 	 *
1433 	 * \param[in,out] attr       XML attribute to mark dirty or remove
1434 	 * \param[in]     user_data  Ignored
1435 	 *
1436 	 * \return \c true (to continue iterating)
1437 	 *
1438 	 * \note This is compatible with \c pcmk__xe_foreach_attr().
1439 	 */
1440 	static bool
1441 	check_new_attr_acls(xmlAttr *attr, void *user_data)
1442 	{
1443 	    const char *name = (const char *) attr->name;
1444 	    const char *value = pcmk__xml_attr_value(attr);
1445 	    const xml_node_private_t *nodepriv = attr->_private;
1446 	    xmlNode *new_xml = attr->parent;
1447 	    const char *new_xml_id = pcmk__s(pcmk__xe_id(new_xml), "without ID");
1448 	
1449 	    if (!pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
1450 	        return true;
1451 	    }
1452 	
1453 	    /* Check ACLs (we can't use the remove-then-create trick because it
1454 	     * would modify the attribute position).
1455 	     */
1456 	    if (!pcmk__check_acl(new_xml, name, pcmk__xf_acl_write)) {
1457 	        pcmk__trace("ACLs prevent creation of attribute %s=%s in %s %s", name,
1458 	                    value, (const char *) new_xml->name, new_xml_id);
1459 	        pcmk__xa_remove(attr, true);
1460 	        return true;
1461 	    }
1462 	
1463 	    pcmk__trace("Created new attribute %s=%s in %s %s", name, value,
1464 	                (const char *) new_xml->name, new_xml_id);
1465 	    pcmk__mark_xml_attr_dirty(attr);
1466 	    return true;
1467 	}
1468 	
1469 	/*!
1470 	 * \internal
1471 	 * \brief Calculate differences in attributes between two XML nodes
1472 	 *
1473 	 * \param[in,out] old_xml  Original XML to compare
1474 	 * \param[in,out] new_xml  New XML to compare
1475 	 */
1476 	static void
1477 	xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
1478 	{
1479 	    // Cleared later if attributes are not really new
1480 	    pcmk__xe_foreach_attr(new_xml, mark_attr_created, NULL);
1481 	
1482 	    pcmk__xe_foreach_attr(old_xml, mark_attr_diff, new_xml);
1483 	    pcmk__xe_foreach_attr(new_xml, check_new_attr_acls, NULL);
1484 	}
1485 	
1486 	/*!
1487 	 * \internal
1488 	 * \brief Add a deleted object record for an old XML child if ACLs allow
1489 	 *
1490 	 * This is intended to be called for a child of an old XML element that is not
1491 	 * present as a child of a new XML element.
1492 	 *
1493 	 * Add a temporary copy of the old child to the new XML. Then check whether ACLs
1494 	 * would have allowed the deletion of that element. If so, add a deleted object
1495 	 * record for it to the new XML's document, and set the \c pcmk__xf_skip flag on
1496 	 * the old child.
1497 	 *
1498 	 * The temporary copy is removed before returning. The new XML and all of its
1499 	 * ancestors will have the \c pcmk__xf_dirty flag set because of the creation,
1500 	 * however.
1501 	 *
1502 	 * \param[in,out] old_child   Child of old XML
1503 	 * \param[in,out] new_parent  New XML that does not contain \p old_child
1504 	 *
1505 	 * \note The deletion is checked using the new XML's ACLs. The ACLs may have
1506 	 *       also changed between the old and new XML trees. Callers should take
1507 	 *       reasonable action if there were ACL changes that themselves would have
1508 	 *       been denied.
1509 	 */
1510 	static void
1511 	mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
1512 	{
1513 	    int pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1514 	
1515 	    // Re-create the child element so we can check ACLs
1516 	    xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1517 	
1518 	    // Clear flags on new child and its children
1519 	    pcmk__xml_tree_foreach(candidate, pcmk__xml_reset_node_flags, NULL);
1520 	
1521 	    // free_xml_with_position() will check whether ACLs allow the deletion
1522 	    pcmk__apply_acls(candidate->doc);
1523 	
1524 	    /* Try to remove the child again (which will track it in document's
1525 	     * deleted_objs on success)
1526 	     */
1527 	    if (free_xml_with_position(candidate, pos) != pcmk_rc_ok) {
1528 	        // ACLs denied deletion in free_xml_with_position. Free candidate here.
1529 	        pcmk__xml_free_node(candidate);
1530 	    }
1531 	
1532 	    pcmk__set_xml_flags((xml_node_private_t *) old_child->_private,
1533 	                        pcmk__xf_skip);
1534 	}
1535 	
1536 	/*!
1537 	 * \internal
1538 	 * \brief Mark a new child as moved and set \c pcmk__xf_skip as appropriate
1539 	 *
1540 	 * \param[in,out] old_child  Child of old XML
1541 	 * \param[in,out] new_child  Child of new XML that matches \p old_child
1542 	 * \param[in]     old_pos    Position of \p old_child among its siblings
1543 	 * \param[in]     new_pos    Position of \p new_child among its siblings
1544 	 */
1545 	static void
1546 	mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos,
1547 	                 int new_pos)
1548 	{
1549 	    const char *id_s = pcmk__s(pcmk__xe_id(new_child), "<no id>");
1550 	    xmlNode *new_parent = new_child->parent;
1551 	    xml_node_private_t *nodepriv = new_child->_private;
1552 	
1553 	    pcmk__trace("Child element %s with " PCMK_XA_ID "='%s' moved from position "
1554 	                "%d to %d under %s",
1555 	                new_child->name, id_s, old_pos, new_pos, new_parent->name);
1556 	    pcmk__mark_xml_node_dirty(new_parent);
1557 	    pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
1558 	
1559 	    /* @TODO Figure out and document why we skip the old child in future
1560 	     * position calculations if the old position is higher, and skip the new
1561 	     * child in future position calculations if the new position is higher. This
1562 	     * goes back to d028b52, and there's no explanation in the commit message.
1563 	     */
1564 	    if (old_pos > new_pos) {
1565 	        nodepriv = old_child->_private;
1566 	    }
1567 	    pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1568 	}
1569 	
1570 	/*!
1571 	 * \internal
1572 	 * \brief Check whether a new XML child comment matches an old XML child comment
1573 	 *
1574 	 * Two comments match if they have the same position among their siblings and
1575 	 * the same contents.
1576 	 *
1577 	 * If \p new_comment has the \c pcmk__xf_skip flag set, then it is automatically
1578 	 * considered not to match.
1579 	 *
1580 	 * \param[in] old_comment  Old XML child element
1581 	 * \param[in] new_comment  New XML child element
1582 	 *
1583 	 * \retval \c true   if \p new_comment matches \p old_comment
1584 	 * \retval \c false  otherwise
1585 	 */
1586 	static bool
1587 	new_comment_matches(const xmlNode *old_comment, const xmlNode *new_comment)
1588 	{
1589 	    xml_node_private_t *nodepriv = new_comment->_private;
1590 	
1591 	    if (pcmk__is_set(nodepriv->flags, pcmk__xf_skip)) {
1592 	        /* @TODO Should we also return false if old_comment has pcmk__xf_skip
1593 	         * set? This preserves existing behavior at time of writing.
1594 	         */
1595 	        return false;
1596 	    }
1597 	    if (pcmk__xml_position(old_comment, pcmk__xf_skip)
1598 	        != pcmk__xml_position(new_comment, pcmk__xf_skip)) {
1599 	        return false;
1600 	    }
1601 	    return pcmk__xc_matches(old_comment, new_comment);
1602 	}
1603 	
1604 	/*!
1605 	 * \internal
1606 	 * \brief Check whether a new XML child element matches an old XML child element
1607 	 *
1608 	 * Two elements match if they have the same name and the same ID. (Both IDs can
1609 	 * be \c NULL.)
1610 	 *
1611 	 * For XML attributes other than \c PCMK_XA_ID, we can treat a value change as
1612 	 * an in-place modification. However, when Pacemaker applies a patchset, it uses
1613 	 * the \c PCMK_XA_ID attribute to find the node to update (modify, delete, or
1614 	 * move). If we treat two nodes with different \c PCMK_XA_ID attributes as
1615 	 * matching and then mark that attribute as changed, it can cause this lookup to
1616 	 * fail.
1617 	 *
1618 	 * There's unlikely to ever be much practical reason to treat elements with
1619 	 * different IDs as a change. Unless that changes, we'll treat them as a
1620 	 * mismatch.
1621 	 *
1622 	 * \param[in] old_element  Old XML child element
1623 	 * \param[in] new_element  New XML child element
1624 	 *
1625 	 * \retval \c true   if \p new_element matches \p old_element
1626 	 * \retval \c false  otherwise
1627 	 */
1628 	static bool
1629 	new_element_matches(const xmlNode *old_element, const xmlNode *new_element)
1630 	{
1631 	    return pcmk__xe_is(new_element, (const char *) old_element->name)
1632 	           && pcmk__str_eq(pcmk__xe_id(old_element), pcmk__xe_id(new_element),
1633 	                           pcmk__str_none);
1634 	}
1635 	
1636 	/*!
1637 	 * \internal
1638 	 * \brief Check whether a new XML child node matches an old XML child node
1639 	 *
1640 	 * Node types must be the same in order to match.
1641 	 *
1642 	 * For comments, a match is a comment at the same position with the same
1643 	 * content.
1644 	 *
1645 	 * For elements, a match is an element with the same name and the same ID. (Both
1646 	 * IDs can be \c NULL.)
1647 	 *
1648 	 * For other node types, there is no match.
1649 	 *
1650 	 * \param[in] old_child  Child of old XML
1651 	 * \param[in] new_child  Child of new XML
1652 	 *
1653 	 * \retval \c true   if \p new_child matches \p old_child
1654 	 * \retval \c false  otherwise
1655 	 */
1656 	static bool
1657 	new_child_matches(const xmlNode *old_child, const xmlNode *new_child)
1658 	{
1659 	    if (old_child->type != new_child->type) {
1660 	        return false;
1661 	    }
1662 	
1663 	    switch (old_child->type) {
1664 	        case XML_COMMENT_NODE:
1665 	            return new_comment_matches(old_child, new_child);
1666 	        case XML_ELEMENT_NODE:
1667 	            return new_element_matches(old_child, new_child);
1668 	        default:
1669 	            return false;
1670 	    }
1671 	}
1672 	
1673 	/*!
1674 	 * \internal
1675 	 * \brief Find matching XML node pairs between old and new XML's children
1676 	 *
1677 	 * A node that is part of a matching pair gets its <tt>_private:match</tt>
1678 	 * member set to the matching node.
1679 	 *
1680 	 * \param[in,out] old_xml       Old XML
1681 	 * \param[in,out] new_xml       New XML
1682 	 */
1683 	static void
1684 	find_matching_children(xmlNode *old_xml, xmlNode *new_xml)
1685 	{
1686 	    for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1687 	         old_child = pcmk__xml_next(old_child)) {
1688 	
1689 	        xml_node_private_t *old_nodepriv = old_child->_private;
1690 	
1691 	        if ((old_nodepriv == NULL) || (old_nodepriv->match != NULL)) {
1692 	            // Can't process, or we already found a match for this old child
1693 	            continue;
1694 	        }
1695 	
1696 	        for (xmlNode *new_child = pcmk__xml_first_child(new_xml);
1697 	             new_child != NULL; new_child = pcmk__xml_next(new_child)) {
1698 	
1699 	            xml_node_private_t *new_nodepriv = new_child->_private;
1700 	
1701 	            if ((new_nodepriv == NULL) || (new_nodepriv->match != NULL)) {
1702 	                /* Can't process, or this new child already matched some old
1703 	                 * child
1704 	                 */
1705 	                continue;
1706 	            }
1707 	
1708 	            if (new_child_matches(old_child, new_child)) {
1709 	                old_nodepriv->match = new_child;
1710 	                new_nodepriv->match = old_child;
1711 	                break;
1712 	            }
1713 	        }
1714 	    }
1715 	}
1716 	
1717 	/*!
1718 	 * \internal
1719 	 * \brief Mark changes between two XML trees
1720 	 *
1721 	 * Set flags in a new XML tree to indicate changes relative to an old XML tree.
1722 	 *
1723 	 * \param[in,out] old_xml  XML before changes
1724 	 * \param[in,out] new_xml  XML after changes
1725 	 *
1726 	 * \note This may set \c pcmk__xf_skip on parts of \p old_xml.
1727 	 */
1728 	void
1729 	pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml)
1730 	{
1731 	    /* This function may set the xml_node_private_t:match member on children of
1732 	     * old_xml and new_xml, but it clears that member before returning.
1733 	     *
1734 	     * @TODO Ensure we handle (for example, by copying) or reject user-created
1735 	     * XML that is missing xml_node_private_t at top level or in any children.
1736 	     * Similarly, check handling of node types for which we don't create private
1737 	     * data. For now, we'll skip them in the loops below.
1738 	     */
1739 	    CRM_CHECK((old_xml != NULL) && (new_xml != NULL), return);
1740 	    if ((old_xml->_private == NULL) || (new_xml->_private == NULL)) {
1741 	        return;
1742 	    }
1743 	
1744 	    pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking);
1745 	    xml_diff_attrs(old_xml, new_xml);
1746 	
1747 	    find_matching_children(old_xml, new_xml);
1748 	
1749 	    // Process matches (changed children) and deletions
1750 	    for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1751 	         old_child = pcmk__xml_next(old_child)) {
1752 	
1753 	        xml_node_private_t *nodepriv = old_child->_private;
1754 	        xmlNode *new_child = NULL;
1755 	
1756 	        if (nodepriv == NULL) {
1757 	            continue;
1758 	        }
1759 	
1760 	        if (nodepriv->match == NULL) {
1761 	            // No match in new XML means the old child was deleted
1762 	            mark_child_deleted(old_child, new_xml);
1763 	            continue;
1764 	        }
1765 	
1766 	        /* Fetch the match and clear old_child->_private's match member.
1767 	         * new_child->_private's match member is handled in the new_xml loop.
1768 	         */
1769 	        new_child = nodepriv->match;
1770 	        nodepriv->match = NULL;
1771 	
1772 	        pcmk__assert(old_child->type == new_child->type);
1773 	
1774 	        if (old_child->type == XML_COMMENT_NODE) {
1775 	            // Comments match only if their positions and contents match
1776 	            continue;
1777 	        }
1778 	
1779 	        pcmk__xml_mark_changes(old_child, new_child);
1780 	    }
1781 	
1782 	    /* Mark unmatched new children as created, and mark matched new children as
1783 	     * moved if their positions changed. Grab the next new child in advance,
1784 	     * since new_child may get freed in the loop body.
1785 	     */
1786 	    for (xmlNode *new_child = pcmk__xml_first_child(new_xml),
1787 	                 *next = pcmk__xml_next(new_child);
1788 	         new_child != NULL;
1789 	         new_child = next, next = pcmk__xml_next(new_child)) {
1790 	
1791 	        xml_node_private_t *nodepriv = new_child->_private;
1792 	
1793 	        if (nodepriv == NULL) {
1794 	            continue;
1795 	        }
1796 	
1797 	        if (nodepriv->match != NULL) {
1798 	            /* Fetch the match and clear new_child->_private's match member. Any
1799 	             * changes were marked in the old_xml loop. Mark the move.
1800 	             *
1801 	             * We might be able to mark the move earlier, when we mark changes
1802 	             * for matches in the old_xml loop, consolidating both actions. We'd
1803 	             * have to think about whether the timing of setting the
1804 	             * pcmk__xf_skip flag makes any difference.
1805 	             */
1806 	            xmlNode *old_child = nodepriv->match;
1807 	            int old_pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1808 	            int new_pos = pcmk__xml_position(new_child, pcmk__xf_skip);
1809 	
1810 	            if (old_pos != new_pos) {
1811 	                mark_child_moved(old_child, new_child, old_pos, new_pos);
1812 	            }
1813 	            nodepriv->match = NULL;
1814 	            continue;
1815 	        }
1816 	
1817 	        // No match in old XML means the new child is newly created
1818 	        pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1819 	        mark_xml_tree_dirty_created(new_child);
1820 	
1821 	        // Check whether creation was allowed (may free new_child)
1822 	        pcmk__apply_creation_acl(new_child, true);
1823 	    }
1824 	}
1825 	
1826 	char *
1827 	pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
1828 	{
1829 	    static const char *base = NULL;
1830 	    char *ret = NULL;
1831 	
1832 	    if (base == NULL) {
1833 	        base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
1834 	    }
1835 	    if (pcmk__str_empty(base)) {
1836 	        base = PCMK_SCHEMA_DIR;
1837 	    }
1838 	
1839 	    switch (ns) {
1840 	        case pcmk__xml_artefact_ns_legacy_rng:
1841 	        case pcmk__xml_artefact_ns_legacy_xslt:
1842 	            ret = strdup(base);
1843 	            break;
1844 	        case pcmk__xml_artefact_ns_base_rng:
1845 	        case pcmk__xml_artefact_ns_base_xslt:
1846 	            ret = pcmk__assert_asprintf("%s/base", base);
1847 	            break;
1848 	        default:
1849 	            pcmk__err("XML artefact family specified as %u not recognized", ns);
1850 	    }
1851 	    return ret;
1852 	}
1853 	
1854 	static char *
1855 	find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
1856 	{
1857 	    char *ret = NULL;
1858 	
1859 	    switch (ns) {
1860 	        case pcmk__xml_artefact_ns_legacy_rng:
1861 	        case pcmk__xml_artefact_ns_base_rng:
1862 	            if (g_str_has_suffix(filespec, ".rng")) {
1863 	                ret = pcmk__assert_asprintf("%s/%s", path, filespec);
1864 	            } else {
1865 	                ret = pcmk__assert_asprintf("%s/%s.rng", path, filespec);
1866 	            }
1867 	            break;
1868 	        case pcmk__xml_artefact_ns_legacy_xslt:
1869 	        case pcmk__xml_artefact_ns_base_xslt:
1870 	            if (g_str_has_suffix(filespec, ".xsl")) {
1871 	                ret = pcmk__assert_asprintf("%s/%s", path, filespec);
1872 	            } else {
1873 	                ret = pcmk__assert_asprintf("%s/%s.xsl", path, filespec);
1874 	            }
1875 	            break;
1876 	        default:
1877 	            pcmk__err("XML artefact family specified as %u not recognized", ns);
1878 	    }
1879 	
1880 	    return ret;
1881 	}
1882 	
1883 	char *
1884 	pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
1885 	{
1886 	    struct stat sb;
1887 	    char *base = pcmk__xml_artefact_root(ns);
1888 	    char *ret = NULL;
1889 	
1890 	    ret = find_artefact(ns, base, filespec);
1891 	    free(base);
1892 	
1893 	    if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
1894 	        const char *remote_schema_dir = pcmk__remote_schema_dir();
1895 	
1896 	        free(ret);
1897 	        ret = find_artefact(ns, remote_schema_dir, filespec);
1898 	    }
1899 	
1900 	    return ret;
1901 	}
1902 	
1903 	// Deprecated functions kept only for backward API compatibility
1904 	// LCOV_EXCL_START
1905 	
1906 	#include <libxml/parser.h>              // xmlCleanupParser()
1907 	
1908 	#include <crm/common/xml_compat.h>
1909 	
1910 	xmlNode *
1911 	copy_xml(xmlNode *src)
1912 	{
1913 	    xmlDoc *doc = pcmk__xml_new_doc();
1914 	    xmlNode *copy = NULL;
1915 	
1916 	    copy = xmlDocCopyNode(src, doc, 1);
1917 	    pcmk__mem_assert(copy);
1918 	
1919 	    xmlDocSetRootElement(doc, copy);
1920 	    pcmk__xml_new_private_data(copy);
1921 	    return copy;
1922 	}
1923 	
1924 	void
1925 	crm_xml_init(void)
1926 	{
1927 	    pcmk__schema_init();
1928 	}
1929 	
1930 	void
1931 	crm_xml_cleanup(void)
1932 	{
1933 	    pcmk__schema_cleanup();
1934 	    xmlCleanupParser();
1935 	}
1936 	
1937 	void
1938 	pcmk_free_xml_subtree(xmlNode *xml)
1939 	{
1940 	    pcmk__xml_free_node(xml);
1941 	}
1942 	
1943 	void
1944 	free_xml(xmlNode *child)
1945 	{
1946 	    pcmk__xml_free(child);
1947 	}
1948 	
1949 	void
1950 	crm_xml_sanitize_id(char *id)
1951 	{
1952 	    char *c;
1953 	
1954 	    for (c = id; *c; ++c) {
1955 	        switch (*c) {
1956 	            case ':':
1957 	            case '#':
1958 	                *c = '.';
1959 	        }
1960 	    }
1961 	}
1962 	
1963 	bool
1964 	xml_tracking_changes(xmlNode *xml)
1965 	{
1966 	    return (xml != NULL)
1967 	           && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking);
1968 	}
1969 	
1970 	bool
1971 	xml_document_dirty(xmlNode *xml)
1972 	{
1973 	    return (xml != NULL)
1974 	           && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_dirty);
1975 	}
1976 	
1977 	void
1978 	xml_accept_changes(xmlNode *xml)
1979 	{
1980 	    if (xml != NULL) {
1981 	        pcmk__xml_commit_changes(xml->doc);
1982 	    }
1983 	}
1984 	
1985 	void
1986 	xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source,
1987 	                  bool enforce_acls)
1988 	{
1989 	    if (xml == NULL) {
1990 	        return;
1991 	    }
1992 	
1993 	    pcmk__xml_commit_changes(xml->doc);
1994 	    pcmk__trace("Tracking changes%s to %p", (enforce_acls? " with ACLs" : ""),
1995 	                xml);
1996 	    pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_tracking);
1997 	    if (enforce_acls) {
1998 	        if (acl_source == NULL) {
1999 	            acl_source = xml;
2000 	        }
2001 	        pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_acl_enabled);
2002 	        pcmk__unpack_acls(acl_source->doc, xml->doc->_private, user);
2003 	        pcmk__apply_acls(xml->doc);
2004 	    }
2005 	}
2006 	
2007 	void
2008 	xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
2009 	{
2010 	    CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
2011 	              && pcmk__xe_is(old_xml, (const char *) new_xml->name)
2012 	              && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
2013 	                              pcmk__str_none),
2014 	              return);
2015 	
2016 	    if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
2017 	        // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
2018 	        pcmk__xml_commit_changes(new_xml->doc);
2019 	    }
2020 	
2021 	    pcmk__xml_mark_changes(old_xml, new_xml);
2022 	}
2023 	
2024 	void
2025 	xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
2026 	{
2027 	    CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
2028 	              && pcmk__xe_is(old_xml, (const char *) new_xml->name)
2029 	              && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
2030 	                              pcmk__str_none),
2031 	              return);
2032 	
2033 	    /* BUG: If pcmk__xf_tracking is not set for new_xml when this function is
2034 	     * called, then we unset pcmk__xf_ignore_attr_pos via
2035 	     * pcmk__xml_commit_changes(). Since this function is about to be
2036 	     * deprecated, it's not worth fixing this and changing the user-facing
2037 	     * behavior.
2038 	     */
2039 	    pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_ignore_attr_pos);
2040 	
2041 	    if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
2042 	        // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
2043 	        pcmk__xml_commit_changes(new_xml->doc);
2044 	    }
2045 	
2046 	    pcmk__xml_mark_changes(old_xml, new_xml);
2047 	}
2048 	
2049 	// LCOV_EXCL_STOP
2050 	// End deprecated API
2051