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