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