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