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