pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
xml.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2024 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdarg.h>
13 #include <stdint.h> // uint32_t
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/stat.h> // stat(), S_ISREG, etc.
18 #include <sys/types.h>
19 
20 #include <glib.h> // gboolean, GString
21 #include <libxml/parser.h> // xmlCleanupParser()
22 #include <libxml/tree.h> // xmlNode, etc.
23 #include <libxml/xmlstring.h> // xmlGetUTF8Char()
24 
25 #include <crm/crm.h>
26 #include <crm/common/xml.h>
27 #include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
28 #include "crmcommon_private.h"
29 
31 #define XML_VERSION ((pcmkXmlStr) "1.0")
32 
46 bool
47 pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
48  void *user_data)
49 {
50  if (xml == NULL) {
51  return true;
52  }
53 
54  if (!fn(xml, user_data)) {
55  return false;
56  }
57 
58  for (xml = pcmk__xml_first_child(xml); xml != NULL;
59  xml = pcmk__xml_next(xml)) {
60 
61  if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
62  return false;
63  }
64  }
65  return true;
66 }
67 
68 bool
69 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
70 {
71  if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
72  return FALSE;
73  } else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
75  return FALSE;
76  } else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
77  pcmk__xf_lazy)) {
78  return FALSE;
79  }
80  return TRUE;
81 }
82 
83 void
84 pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
85 {
86  for (; xml != NULL; xml = xml->parent) {
87  xml_node_private_t *nodepriv = xml->_private;
88 
89  if (nodepriv != NULL) {
90  pcmk__set_xml_flags(nodepriv, flags);
91  }
92  }
93 }
94 
95 void
96 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
97 {
98  if (xml != NULL) {
99  xml_doc_private_t *docpriv = xml->doc->_private;
100 
101  pcmk__set_xml_flags(docpriv, flag);
102  }
103 }
104 
105 // Mark document, element, and all element's parents as changed
106 void
108 {
111 }
112 
124 bool
125 pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
126 {
127  xml_node_private_t *nodepriv = xml->_private;
128 
129  if (nodepriv != NULL) {
130  nodepriv->flags = pcmk__xf_none;
131  }
132  return true;
133 }
134 
146 static bool
147 mark_xml_dirty_created(xmlNode *xml, void *user_data)
148 {
149  xml_node_private_t *nodepriv = xml->_private;
150 
151  if (nodepriv != NULL) {
153  }
154  return true;
155 }
156 
165 static void
166 mark_xml_tree_dirty_created(xmlNode *xml)
167 {
168  pcmk__assert(xml != NULL);
169 
170  if (!pcmk__tracking_xml_changes(xml, false)) {
171  // Tracking is disabled for entire document
172  return;
173  }
174 
175  // Mark all parents and document dirty
177 
178  pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
179 }
180 
181 // Free an XML object previously marked as deleted
182 static void
183 free_deleted_object(void *data)
184 {
185  if(data) {
186  pcmk__deleted_xml_t *deleted_obj = data;
187 
188  g_free(deleted_obj->path);
189  free(deleted_obj);
190  }
191 }
192 
193 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
194 static void
195 reset_xml_private_data(xml_doc_private_t *docpriv)
196 {
197  if (docpriv != NULL) {
199 
200  free(docpriv->user);
201  docpriv->user = NULL;
202 
203  if (docpriv->acls != NULL) {
204  pcmk__free_acls(docpriv->acls);
205  docpriv->acls = NULL;
206  }
207 
208  if(docpriv->deleted_objs) {
209  g_list_free_full(docpriv->deleted_objs, free_deleted_object);
210  docpriv->deleted_objs = NULL;
211  }
212  }
213 }
214 
226 static bool
227 new_private_data(xmlNode *node, void *user_data)
228 {
229  CRM_CHECK(node != NULL, return true);
230 
231  if (node->_private != NULL) {
232  return true;
233  }
234 
235  switch (node->type) {
236  case XML_DOCUMENT_NODE:
237  {
238  xml_doc_private_t *docpriv =
240 
242  node->_private = docpriv;
244  }
245  break;
246 
247  case XML_ELEMENT_NODE:
248  case XML_ATTRIBUTE_NODE:
249  case XML_COMMENT_NODE:
250  {
251  xml_node_private_t *nodepriv =
253 
255  node->_private = nodepriv;
257 
258  for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
259  iter = iter->next) {
260 
261  new_private_data((xmlNode *) iter, user_data);
262  }
263  }
264  break;
265 
266  case XML_TEXT_NODE:
267  case XML_DTD_NODE:
268  case XML_CDATA_SECTION_NODE:
269  return true;
270 
271  default:
272  CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
273  return true;
274  }
275 
276  if (pcmk__tracking_xml_changes(node, false)) {
278  }
279  return true;
280 }
281 
293 static bool
294 free_private_data(xmlNode *node, void *user_data)
295 {
296  CRM_CHECK(node != NULL, return true);
297 
298  if (node->_private == NULL) {
299  return true;
300  }
301 
302  if (node->type == XML_DOCUMENT_NODE) {
303  reset_xml_private_data((xml_doc_private_t *) node->_private);
304 
305  } else {
306  xml_node_private_t *nodepriv = node->_private;
307 
309 
310  for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
311  iter = iter->next) {
312 
313  free_private_data((xmlNode *) iter, user_data);
314  }
315  }
316  free(node->_private);
317  node->_private = NULL;
318  return true;
319 }
320 
327 void
329 {
330  pcmk__xml_tree_foreach(xml, new_private_data, NULL);
331 }
332 
339 void
341 {
342  pcmk__xml_tree_foreach(xml, free_private_data, NULL);
343 }
344 
345 void
346 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
347 {
348  xml_accept_changes(xml);
349  crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
351  if(enforce_acls) {
352  if(acl_source == NULL) {
353  acl_source = xml;
354  }
356  pcmk__unpack_acl(acl_source, xml, user);
357  pcmk__apply_acl(xml);
358  }
359 }
360 
361 bool xml_tracking_changes(xmlNode * xml)
362 {
363  return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
364  && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
366 }
367 
368 bool xml_document_dirty(xmlNode *xml)
369 {
370  return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
371  && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
373 }
374 
384 int
385 pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
386 {
387  int position = 0;
388 
389  for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
390  xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
391 
392  if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
393  position++;
394  }
395  }
396 
397  return position;
398 }
399 
411 static bool
412 accept_attr_deletions(xmlNode *xml, void *user_data)
413 {
414  pcmk__xml_reset_node_flags(xml, NULL);
416  return true;
417 }
418 
427 xmlNode *
428 pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
429 {
430  CRM_CHECK(needle != NULL, return NULL);
431 
432  if (needle->type == XML_COMMENT_NODE) {
433  return pcmk__xc_match(haystack, needle, exact);
434 
435  } else {
436  const char *id = pcmk__xe_id(needle);
437  const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
438 
439  return pcmk__xe_first_child(haystack, (const char *) needle->name, attr,
440  id);
441  }
442 }
443 
444 void
445 xml_accept_changes(xmlNode * xml)
446 {
447  xmlNode *top = NULL;
448  xml_doc_private_t *docpriv = NULL;
449 
450  if(xml == NULL) {
451  return;
452  }
453 
454  crm_trace("Accepting changes to %p", xml);
455  docpriv = xml->doc->_private;
456  top = xmlDocGetRootElement(xml->doc);
457 
458  reset_xml_private_data(xml->doc->_private);
459 
460  if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
461  docpriv->flags = pcmk__xf_none;
462  return;
463  }
464 
465  docpriv->flags = pcmk__xf_none;
466  pcmk__xml_tree_foreach(top, accept_attr_deletions, NULL);
467 }
468 
478 xmlDoc *
480 {
481  xmlDoc *doc = xmlNewDoc(XML_VERSION);
482 
483  pcmk__mem_assert(doc);
484  pcmk__xml_new_private_data((xmlNode *) doc);
485  return doc;
486 }
487 
494 void
495 pcmk__xml_free_doc(xmlDoc *doc)
496 {
497  if (doc != NULL) {
498  pcmk__xml_free_private_data((xmlNode *) doc);
499  xmlFreeDoc(doc);
500  }
501 }
502 
519 bool
520 pcmk__xml_is_name_start_char(const char *utf8, int *len)
521 {
522  int c = 0;
523  int local_len = 0;
524 
525  if (len == NULL) {
526  len = &local_len;
527  }
528 
529  /* xmlGetUTF8Char() abuses the len argument. At call time, it must be set to
530  * "the minimum number of bytes present in the sequence... to assure the
531  * next character is completely contained within the sequence." It's similar
532  * to the "n" in the strn*() functions. However, this doesn't make any sense
533  * for null-terminated strings, and there's no value that indicates "keep
534  * going until '\0'." So we set it to 4, the max number of bytes in a UTF-8
535  * character.
536  *
537  * At return, it's set to the actual number of bytes in the char, or 0 on
538  * error.
539  */
540  *len = 4;
541 
542  // Note: xmlGetUTF8Char() assumes a 32-bit int
543  c = xmlGetUTF8Char((pcmkXmlStr) utf8, len);
544  if (c < 0) {
545  GString *buf = g_string_sized_new(32);
546 
547  for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
548  g_string_append_printf(buf, " 0x%.2X", utf8[i]);
549  }
550  crm_info("Invalid UTF-8 character (bytes:%s)",
551  (pcmk__str_empty(buf->str)? " <none>" : buf->str));
552  g_string_free(buf, TRUE);
553  return false;
554  }
555 
556  return (c == '_')
557  || (c == ':')
558  || ((c >= 'a') && (c <= 'z'))
559  || ((c >= 'A') && (c <= 'Z'))
560  || ((c >= 0xC0) && (c <= 0xD6))
561  || ((c >= 0xD8) && (c <= 0xF6))
562  || ((c >= 0xF8) && (c <= 0x2FF))
563  || ((c >= 0x370) && (c <= 0x37D))
564  || ((c >= 0x37F) && (c <= 0x1FFF))
565  || ((c >= 0x200C) && (c <= 0x200D))
566  || ((c >= 0x2070) && (c <= 0x218F))
567  || ((c >= 0x2C00) && (c <= 0x2FEF))
568  || ((c >= 0x3001) && (c <= 0xD7FF))
569  || ((c >= 0xF900) && (c <= 0xFDCF))
570  || ((c >= 0xFDF0) && (c <= 0xFFFD))
571  || ((c >= 0x10000) && (c <= 0xEFFFF));
572 }
573 
590 bool
591 pcmk__xml_is_name_char(const char *utf8, int *len)
592 {
593  int c = 0;
594  int local_len = 0;
595 
596  if (len == NULL) {
597  len = &local_len;
598  }
599 
600  // See comment regarding len in pcmk__xml_is_name_start_char()
601  *len = 4;
602 
603  // Note: xmlGetUTF8Char() assumes a 32-bit int
604  c = xmlGetUTF8Char((pcmkXmlStr) utf8, len);
605  if (c < 0) {
606  GString *buf = g_string_sized_new(32);
607 
608  for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
609  g_string_append_printf(buf, " 0x%.2X", utf8[i]);
610  }
611  crm_info("Invalid UTF-8 character (bytes:%s)",
612  (pcmk__str_empty(buf->str)? " <none>" : buf->str));
613  g_string_free(buf, TRUE);
614  return false;
615  }
616 
617  return ((c >= 'a') && (c <= 'z'))
618  || ((c >= 'A') && (c <= 'Z'))
619  || ((c >= '0') && (c <= '9'))
620  || (c == '_')
621  || (c == ':')
622  || (c == '-')
623  || (c == '.')
624  || (c == 0xB7)
625  || ((c >= 0xC0) && (c <= 0xD6))
626  || ((c >= 0xD8) && (c <= 0xF6))
627  || ((c >= 0xF8) && (c <= 0x2FF))
628  || ((c >= 0x300) && (c <= 0x36F))
629  || ((c >= 0x370) && (c <= 0x37D))
630  || ((c >= 0x37F) && (c <= 0x1FFF))
631  || ((c >= 0x200C) && (c <= 0x200D))
632  || ((c >= 0x203F) && (c <= 0x2040))
633  || ((c >= 0x2070) && (c <= 0x218F))
634  || ((c >= 0x2C00) && (c <= 0x2FEF))
635  || ((c >= 0x3001) && (c <= 0xD7FF))
636  || ((c >= 0xF900) && (c <= 0xFDCF))
637  || ((c >= 0xFDF0) && (c <= 0xFFFD))
638  || ((c >= 0x10000) && (c <= 0xEFFFF));
639 }
640 
653 void
655 {
656  bool valid = true;
657  int len = 0;
658 
659  // If id is empty or NULL, there's no way to make it a valid XML ID
660  pcmk__assert(!pcmk__str_empty(id));
661 
662  /* @TODO Suppose there are two strings and each has an invalid ID character
663  * in the same position. The strings are otherwise identical. Both strings
664  * will be sanitized to the same valid ID, which is incorrect.
665  *
666  * The caller is responsible for ensuring the sanitized ID does not already
667  * exist in a given XML document before using it, if uniqueness is desired.
668  */
669  valid = pcmk__xml_is_name_start_char(id, &len);
670  CRM_CHECK(len > 0, return); // UTF-8 encoding error
671  if (!valid) {
672  *id = '_';
673  for (int i = 1; i < len; i++) {
674  id[i] = '.';
675  }
676  }
677 
678  for (id += len; *id != '\0'; id += len) {
679  valid = pcmk__xml_is_name_char(id, &len);
680  CRM_CHECK(len > 0, return); // UTF-8 encoding error
681  if (!valid) {
682  for (int i = 0; i < len; i++) {
683  id[i] = '.';
684  }
685  }
686  }
687 }
688 
695 void
696 pcmk__xml_free_node(xmlNode *xml)
697 {
699  xmlUnlinkNode(xml);
700  xmlFreeNode(xml);
701 }
702 
714 static void
715 free_xml_with_position(xmlNode *node, int position)
716 {
717  xmlDoc *doc = NULL;
718  xml_node_private_t *nodepriv = NULL;
719 
720  if (node == NULL) {
721  return;
722  }
723  doc = node->doc;
724  nodepriv = node->_private;
725 
726  if ((doc != NULL) && (xmlDocGetRootElement(doc) == node)) {
727  /* @TODO Should we check ACLs first? Otherwise it seems like we could
728  * free the root element without write permission.
729  */
730  pcmk__xml_free_doc(doc);
731  return;
732  }
733 
734  if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
735  GString *xpath = NULL;
736 
737  pcmk__if_tracing({}, return);
738  xpath = pcmk__element_xpath(node);
739  qb_log_from_external_source(__func__, __FILE__,
740  "Cannot remove %s %x", LOG_TRACE,
741  __LINE__, 0, xpath->str, nodepriv->flags);
742  g_string_free(xpath, TRUE);
743  return;
744  }
745 
746  if ((doc != NULL) && pcmk__tracking_xml_changes(node, false)
747  && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
748 
749  xml_doc_private_t *docpriv = doc->_private;
750  GString *xpath = pcmk__element_xpath(node);
751 
752  if (xpath != NULL) {
753  pcmk__deleted_xml_t *deleted_obj = NULL;
754 
755  crm_trace("Deleting %s %p from %p", xpath->str, node, doc);
756 
757  deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
758  deleted_obj->path = g_string_free(xpath, FALSE);
759  deleted_obj->position = -1;
760 
761  // Record the position only for XML comments for now
762  if (node->type == XML_COMMENT_NODE) {
763  if (position >= 0) {
764  deleted_obj->position = position;
765 
766  } else {
767  deleted_obj->position = pcmk__xml_position(node,
768  pcmk__xf_skip);
769  }
770  }
771 
772  docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
773  deleted_obj);
775  }
776  }
777  pcmk__xml_free_node(node);
778 }
779 
788 void
789 pcmk__xml_free(xmlNode *xml)
790 {
791  free_xml_with_position(xml, -1);
792 }
793 
804 xmlNode *
805 pcmk__xml_copy(xmlNode *parent, xmlNode *src)
806 {
807  xmlNode *copy = NULL;
808 
809  if (src == NULL) {
810  return NULL;
811  }
812 
813  if (parent == NULL) {
814  xmlDoc *doc = NULL;
815 
816  // The copy will be the root element of a new document
817  pcmk__assert(src->type == XML_ELEMENT_NODE);
818 
819  doc = pcmk__xml_new_doc();
820  copy = xmlDocCopyNode(src, doc, 1);
821  pcmk__mem_assert(copy);
822 
823  xmlDocSetRootElement(doc, copy);
824 
825  } else {
826  copy = xmlDocCopyNode(src, parent->doc, 1);
827  pcmk__mem_assert(copy);
828 
829  xmlAddChild(parent, copy);
830  }
831 
833  return copy;
834 }
835 
842 void
843 pcmk__strip_xml_text(xmlNode *xml)
844 {
845  xmlNode *iter = xml->children;
846 
847  while (iter) {
848  xmlNode *next = iter->next;
849 
850  switch (iter->type) {
851  case XML_TEXT_NODE:
852  pcmk__xml_free_node(iter);
853  break;
854 
855  case XML_ELEMENT_NODE:
856  /* Search it */
857  pcmk__strip_xml_text(iter);
858  break;
859 
860  default:
861  /* Leave it */
862  break;
863  }
864 
865  iter = next;
866  }
867 }
868 
881 bool
883 {
884  if (text == NULL) {
885  return false;
886  }
887 
888  while (*text != '\0') {
889  switch (type) {
891  switch (*text) {
892  case '<':
893  case '>':
894  case '&':
895  return true;
896  case '\n':
897  case '\t':
898  break;
899  default:
900  if (g_ascii_iscntrl(*text)) {
901  return true;
902  }
903  break;
904  }
905  break;
906 
908  switch (*text) {
909  case '<':
910  case '>':
911  case '&':
912  case '"':
913  return true;
914  default:
915  if (g_ascii_iscntrl(*text)) {
916  return true;
917  }
918  break;
919  }
920  break;
921 
923  switch (*text) {
924  case '\n':
925  case '\r':
926  case '\t':
927  case '"':
928  return true;
929  default:
930  break;
931  }
932  break;
933 
934  default: // Invalid enum value
935  pcmk__assert(false);
936  break;
937  }
938 
939  text = g_utf8_next_char(text);
940  }
941  return false;
942 }
943 
963 gchar *
965 {
966  GString *copy = NULL;
967 
968  if (text == NULL) {
969  return NULL;
970  }
971  copy = g_string_sized_new(strlen(text));
972 
973  while (*text != '\0') {
974  // Don't escape any non-ASCII characters
975  if ((*text & 0x80) != 0) {
976  size_t bytes = g_utf8_next_char(text) - text;
977 
978  g_string_append_len(copy, text, bytes);
979  text += bytes;
980  continue;
981  }
982 
983  switch (type) {
985  switch (*text) {
986  case '<':
987  g_string_append(copy, PCMK__XML_ENTITY_LT);
988  break;
989  case '>':
990  g_string_append(copy, PCMK__XML_ENTITY_GT);
991  break;
992  case '&':
993  g_string_append(copy, PCMK__XML_ENTITY_AMP);
994  break;
995  case '\n':
996  case '\t':
997  g_string_append_c(copy, *text);
998  break;
999  default:
1000  if (g_ascii_iscntrl(*text)) {
1001  g_string_append_printf(copy, "&#x%.2X;", *text);
1002  } else {
1003  g_string_append_c(copy, *text);
1004  }
1005  break;
1006  }
1007  break;
1008 
1009  case pcmk__xml_escape_attr:
1010  switch (*text) {
1011  case '<':
1012  g_string_append(copy, PCMK__XML_ENTITY_LT);
1013  break;
1014  case '>':
1015  g_string_append(copy, PCMK__XML_ENTITY_GT);
1016  break;
1017  case '&':
1018  g_string_append(copy, PCMK__XML_ENTITY_AMP);
1019  break;
1020  case '"':
1021  g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1022  break;
1023  default:
1024  if (g_ascii_iscntrl(*text)) {
1025  g_string_append_printf(copy, "&#x%.2X;", *text);
1026  } else {
1027  g_string_append_c(copy, *text);
1028  }
1029  break;
1030  }
1031  break;
1032 
1034  switch (*text) {
1035  case '"':
1036  g_string_append(copy, "\\\"");
1037  break;
1038  case '\n':
1039  g_string_append(copy, "\\n");
1040  break;
1041  case '\r':
1042  g_string_append(copy, "\\r");
1043  break;
1044  case '\t':
1045  g_string_append(copy, "\\t");
1046  break;
1047  default:
1048  g_string_append_c(copy, *text);
1049  break;
1050  }
1051  break;
1052 
1053  default: // Invalid enum value
1054  pcmk__assert(false);
1055  break;
1056  }
1057 
1058  text = g_utf8_next_char(text);
1059  }
1060  return g_string_free(copy, FALSE);
1061 }
1062 
1070 static void
1071 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
1072 {
1073  for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
1074  pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
1075  }
1076 }
1077 
1092 static void
1093 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
1094  const char *old_value)
1095 {
1096  xml_doc_private_t *docpriv = new_xml->doc->_private;
1097  xmlAttr *attr = NULL;
1098  xml_node_private_t *nodepriv;
1099 
1100  /* Restore the old value (without setting dirty flag recursively upwards or
1101  * checking ACLs)
1102  */
1104  crm_xml_add(new_xml, attr_name, old_value);
1106 
1107  // Reset flags (so the attribute doesn't appear as newly created)
1108  attr = xmlHasProp(new_xml, (pcmkXmlStr) attr_name);
1109  nodepriv = attr->_private;
1110  nodepriv->flags = 0;
1111 
1112  // Check ACLs and mark restored value for later removal
1113  pcmk__xa_remove(attr, false);
1114 
1115  crm_trace("XML attribute %s=%s was removed from %s",
1116  attr_name, old_value, element);
1117 }
1118 
1119 /*
1120  * \internal
1121  * \brief Check ACLs for a changed XML attribute
1122  */
1123 static void
1124 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
1125  const char *old_value)
1126 {
1127  xml_doc_private_t *docpriv = new_xml->doc->_private;
1128  char *vcopy = crm_element_value_copy(new_xml, attr_name);
1129 
1130  crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
1131  attr_name, old_value, vcopy, element);
1132 
1133  // Restore the original value (without checking ACLs)
1135  crm_xml_add(new_xml, attr_name, old_value);
1137 
1138  // Change it back to the new value, to check ACLs
1139  crm_xml_add(new_xml, attr_name, vcopy);
1140  free(vcopy);
1141 }
1142 
1154 static void
1155 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
1156  xmlAttr *new_attr, int p_old, int p_new)
1157 {
1158  xml_node_private_t *nodepriv = new_attr->_private;
1159 
1160  crm_trace("XML attribute %s moved from position %d to %d in %s",
1161  old_attr->name, p_old, p_new, element);
1162 
1163  // Mark document, element, and all element's parents as changed
1164  pcmk__mark_xml_node_dirty(new_xml);
1165 
1166  // Mark attribute as changed
1168 
1169  nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1171 }
1172 
1180 static void
1181 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
1182 {
1183  xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
1184 
1185  while (attr_iter != NULL) {
1186  const char *name = (const char *) attr_iter->name;
1187  xmlAttr *old_attr = attr_iter;
1188  xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
1189  const char *old_value = pcmk__xml_attr_value(attr_iter);
1190 
1191  attr_iter = attr_iter->next;
1192  if (new_attr == NULL) {
1193  mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
1194  old_value);
1195 
1196  } else {
1197  xml_node_private_t *nodepriv = new_attr->_private;
1198  int new_pos = pcmk__xml_position((xmlNode*) new_attr,
1199  pcmk__xf_skip);
1200  int old_pos = pcmk__xml_position((xmlNode*) old_attr,
1201  pcmk__xf_skip);
1202  const char *new_value = crm_element_value(new_xml, name);
1203 
1204  // This attribute isn't new
1206 
1207  if (strcmp(new_value, old_value) != 0) {
1208  mark_attr_changed(new_xml, (const char *) old_xml->name, name,
1209  old_value);
1210 
1211  } else if ((old_pos != new_pos)
1212  && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
1213  mark_attr_moved(new_xml, (const char *) old_xml->name,
1214  old_attr, new_attr, old_pos, new_pos);
1215  }
1216  }
1217  }
1218 }
1219 
1229 static void
1230 mark_created_attrs(xmlNode *new_xml)
1231 {
1232  xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
1233 
1234  while (attr_iter != NULL) {
1235  xmlAttr *new_attr = attr_iter;
1236  xml_node_private_t *nodepriv = attr_iter->_private;
1237 
1238  attr_iter = attr_iter->next;
1239  if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
1240  const char *attr_name = (const char *) new_attr->name;
1241 
1242  crm_trace("Created new attribute %s=%s in %s",
1243  attr_name, pcmk__xml_attr_value(new_attr),
1244  new_xml->name);
1245 
1246  /* Check ACLs (we can't use the remove-then-create trick because it
1247  * would modify the attribute position).
1248  */
1249  if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
1250  pcmk__mark_xml_attr_dirty(new_attr);
1251  } else {
1252  // Creation was not allowed, so remove the attribute
1253  pcmk__xa_remove(new_attr, true);
1254  }
1255  }
1256  }
1257 }
1258 
1266 static void
1267 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
1268 {
1269  set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
1270  xml_diff_old_attrs(old_xml, new_xml);
1271  mark_created_attrs(new_xml);
1272 }
1273 
1286 static void
1287 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
1288 {
1289  // Re-create the child element so we can check ACLs
1290  xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1291 
1292  // Clear flags on new child and its children
1294 
1295  // Check whether ACLs allow the deletion
1296  pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
1297 
1298  // Remove the child again (which will track it in document's deleted_objs)
1299  free_xml_with_position(candidate,
1300  pcmk__xml_position(old_child, pcmk__xf_skip));
1301 
1302  if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
1303  pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
1304  pcmk__xf_skip);
1305  }
1306 }
1307 
1308 static void
1309 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
1310  int p_old, int p_new)
1311 {
1312  xml_node_private_t *nodepriv = new_child->_private;
1313 
1314  crm_trace("Child element %s with "
1315  PCMK_XA_ID "='%s' moved from position %d to %d under %s",
1316  new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
1317  p_old, p_new, new_parent->name);
1318  pcmk__mark_xml_node_dirty(new_parent);
1320 
1321  if (p_old > p_new) {
1322  nodepriv = old_child->_private;
1323  } else {
1324  nodepriv = new_child->_private;
1325  }
1327 }
1328 
1329 // Given original and new XML, mark new XML portions that have changed
1330 static void
1331 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
1332 {
1333  xmlNode *old_child = NULL;
1334  xmlNode *new_child = NULL;
1335  xml_node_private_t *nodepriv = NULL;
1336 
1337  CRM_CHECK(new_xml != NULL, return);
1338  if (old_xml == NULL) {
1339  mark_xml_tree_dirty_created(new_xml);
1340  pcmk__apply_creation_acl(new_xml, check_top);
1341  return;
1342  }
1343 
1344  nodepriv = new_xml->_private;
1345  CRM_CHECK(nodepriv != NULL, return);
1346 
1347  if(nodepriv->flags & pcmk__xf_processed) {
1348  /* Avoid re-comparing nodes */
1349  return;
1350  }
1352 
1353  xml_diff_attrs(old_xml, new_xml);
1354 
1355  // Check for differences in the original children
1356  for (old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1357  old_child = pcmk__xml_next(old_child)) {
1358 
1359  new_child = pcmk__xml_match(new_xml, old_child, true);
1360 
1361  if (new_child != NULL) {
1362  mark_xml_changes(old_child, new_child, true);
1363 
1364  } else {
1365  mark_child_deleted(old_child, new_xml);
1366  }
1367  }
1368 
1369  // Check for moved or created children
1370  new_child = pcmk__xml_first_child(new_xml);
1371  while (new_child != NULL) {
1372  xmlNode *next = pcmk__xml_next(new_child);
1373 
1374  old_child = pcmk__xml_match(old_xml, new_child, true);
1375 
1376  if (old_child == NULL) {
1377  // This is a newly created child
1378  nodepriv = new_child->_private;
1380 
1381  // May free new_child
1382  mark_xml_changes(old_child, new_child, true);
1383 
1384  } else {
1385  /* Check for movement, we already checked for differences */
1386  int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
1387  int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
1388 
1389  if(p_old != p_new) {
1390  mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
1391  }
1392  }
1393 
1394  new_child = next;
1395  }
1396 }
1397 
1398 void
1399 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
1400 {
1402  xml_calculate_changes(old_xml, new_xml);
1403 }
1404 
1405 // Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
1406 void
1407 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
1408 {
1409  CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
1410  && pcmk__xe_is(old_xml, (const char *) new_xml->name)
1411  && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
1412  pcmk__str_none),
1413  return);
1414 
1415  if(xml_tracking_changes(new_xml) == FALSE) {
1416  xml_track_changes(new_xml, NULL, NULL, FALSE);
1417  }
1418 
1419  mark_xml_changes(old_xml, new_xml, FALSE);
1420 }
1421 
1429 void
1431 {
1432  // @TODO Try to find a better caller than crm_log_preinit()
1433  static bool initialized = false;
1434 
1435  if (!initialized) {
1436  initialized = true;
1437 
1438  /* Double the buffer size when the buffer needs to grow. The default
1439  * allocator XML_BUFFER_ALLOC_EXACT was found to cause poor performance
1440  * due to the number of reallocs.
1441  */
1442  xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
1443 
1444  // Load schemas into the cache
1446  }
1447 }
1448 
1455 void
1457 {
1459  xmlCleanupParser();
1460 }
1461 
1462 char *
1464 {
1465  static const char *base = NULL;
1466  char *ret = NULL;
1467 
1468  if (base == NULL) {
1470  }
1471  if (pcmk__str_empty(base)) {
1472  base = PCMK_SCHEMA_DIR;
1473  }
1474 
1475  switch (ns) {
1478  ret = strdup(base);
1479  break;
1482  ret = crm_strdup_printf("%s/base", base);
1483  break;
1484  default:
1485  crm_err("XML artefact family specified as %u not recognized", ns);
1486  }
1487  return ret;
1488 }
1489 
1490 static char *
1491 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
1492 {
1493  char *ret = NULL;
1494 
1495  switch (ns) {
1498  if (pcmk__ends_with(filespec, ".rng")) {
1499  ret = crm_strdup_printf("%s/%s", path, filespec);
1500  } else {
1501  ret = crm_strdup_printf("%s/%s.rng", path, filespec);
1502  }
1503  break;
1506  if (pcmk__ends_with(filespec, ".xsl")) {
1507  ret = crm_strdup_printf("%s/%s", path, filespec);
1508  } else {
1509  ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
1510  }
1511  break;
1512  default:
1513  crm_err("XML artefact family specified as %u not recognized", ns);
1514  }
1515 
1516  return ret;
1517 }
1518 
1519 char *
1520 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
1521 {
1522  struct stat sb;
1523  char *base = pcmk__xml_artefact_root(ns);
1524  char *ret = NULL;
1525 
1526  ret = find_artefact(ns, base, filespec);
1527  free(base);
1528 
1529  if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
1530  const char *remote_schema_dir = pcmk__remote_schema_dir();
1531 
1532  free(ret);
1533  ret = find_artefact(ns, remote_schema_dir, filespec);
1534  }
1535 
1536  return ret;
1537 }
1538 
1539 // Deprecated functions kept only for backward API compatibility
1540 // LCOV_EXCL_START
1541 
1542 #include <crm/common/xml_compat.h>
1543 
1544 xmlNode *
1545 copy_xml(xmlNode *src)
1546 {
1547  xmlDoc *doc = pcmk__xml_new_doc();
1548  xmlNode *copy = NULL;
1549 
1550  copy = xmlDocCopyNode(src, doc, 1);
1551  pcmk__mem_assert(copy);
1552 
1553  xmlDocSetRootElement(doc, copy);
1555  return copy;
1556 }
1557 
1558 void
1560 {
1561  pcmk__xml_init();
1562 }
1563 
1564 void
1566 {
1568 }
1569 
1570 void
1572 {
1573  pcmk__xml_free_node(xml);
1574 }
1575 
1576 void
1577 free_xml(xmlNode *child)
1578 {
1579  pcmk__xml_free(child);
1580 }
1581 
1582 void
1584 {
1585  char *c;
1586 
1587  for (c = id; *c; ++c) {
1588  switch (*c) {
1589  case ':':
1590  case '#':
1591  *c = '.';
1592  }
1593  }
1594 }
1595 
1596 // LCOV_EXCL_STOP
1597 // End deprecated API
#define LOG_TRACE
Definition: logging.h:38
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
Definition: xml.c:69
#define XML_VERSION
libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
Definition: xml.c:31
void pcmk__xml_free_private_data(xmlNode *xml)
Definition: xml.c:340
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition: xml.c:1399
A dumping ground.
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml_element.c:42
#define PCMK__XML_ENTITY_LT
char data[0]
Definition: cpg.c:58
#define pcmk__if_tracing(if_action, else_action)
bool pcmk__xml_tree_foreach(xmlNode *xml, bool(*fn)(xmlNode *, void *), void *user_data)
Definition: xml.c:47
void pcmk__free_acls(GList *acls)
Definition: acl.c:43
const char * name
Definition: cib.c:26
enum pcmk_ipc_server type
Definition: cpg.c:51
void pcmk__xml_sanitize_id(char *id)
Definition: xml.c:654
void xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source, bool enforce_acls)
Definition: xml.c:346
G_GNUC_INTERNAL void pcmk__mark_xml_attr_dirty(xmlAttr *a)
Definition: xml_attr.c:75
xmlDoc * pcmk__xml_new_doc(void)
Definition: xml.c:479
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition: xml.c:1407
#define PCMK__XML_DOC_PRIVATE_MAGIC
void crm_xml_sanitize_id(char *id)
Definition: xml.c:1583
void crm_xml_init(void)
Definition: xml.c:1559
void pcmk__xml_new_private_data(xmlNode *xml)
Definition: xml.c:328
void pcmk_free_xml_subtree(xmlNode *xml)
Definition: xml.c:1571
void pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
Definition: acl.c:554
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:196
bool pcmk__xml_is_name_start_char(const char *utf8, int *len)
Definition: xml.c:520
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
bool pcmk__ends_with(const char *s, const char *match)
Definition: strings.c:610
const char * pcmk__env_option(const char *option)
Definition: options.c:1075
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
Deprecated Pacemaker XML API.
G_GNUC_INTERNAL bool pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data)
Definition: xml_attr.c:87
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
void pcmk__schema_init(void)
Definition: schemas.c:453
gchar * pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
Definition: xml.c:964
#define PCMK__ENV_SCHEMA_DIRECTORY
int pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:385
xmlNode * pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
Definition: xml.c:428
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:1463
void pcmk__strip_xml_text(xmlNode *xml)
Definition: xml.c:843
xmlNode * copy_xml(xmlNode *src)
Definition: xml.c:1545
pcmk__xml_escape_type
Definition: xml_internal.h:252
void pcmk__xml_free_doc(xmlDoc *doc)
Definition: xml.c:495
#define PCMK__XML_ENTITY_GT
void pcmk__xml_cleanup(void)
Definition: xml.c:1456
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: xml_element.c:1466
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
void free_xml(xmlNode *child)
Definition: xml.c:1577
#define crm_trace(fmt, args...)
Definition: logging.h:372
void pcmk__mark_xml_node_dirty(xmlNode *xml)
Definition: xml.c:107
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:96
void pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
Definition: xml.c:84
Wrappers for and extensions to libxml2.
void pcmk__schema_cleanup(void)
Definition: schemas.c:615
#define PCMK_XA_ID
Definition: xml_names.h:301
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:1520
void crm_xml_cleanup(void)
Definition: xml.c:1565
void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
Definition: acl.c:272
void pcmk__xml_init(void)
Definition: xml.c:1430
const xmlChar * pcmkXmlStr
Definition: xml.h:41
#define PCMK__XML_NODE_PRIVATE_MAGIC
#define pcmk__assert(expr)
xml_private_flags
Definition: xml_internal.h:404
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
Definition: xml.c:882
pcmk__xml_artefact_ns
Definition: xml_internal.h:197
const char * path
Definition: cib.c:28
#define crm_err(fmt, args...)
Definition: logging.h:359
const char * pcmk__remote_schema_dir(void)
Definition: schemas.c:1448
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:655
void pcmk__apply_acl(xmlNode *xml)
Definition: acl.c:214
void xml_accept_changes(xmlNode *xml)
Definition: xml.c:445
#define pcmk__mem_assert(ptr)
#define PCMK__XML_ENTITY_QUOT
bool pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
Definition: xml.c:125
bool pcmk__xml_is_name_char(const char *utf8, int *len)
Definition: xml.c:591
GString * pcmk__element_xpath(const xmlNode *xml)
Definition: xpath.c:256
bool xml_tracking_changes(xmlNode *xml)
Definition: xml.c:361
#define PCMK_SCHEMA_DIR
Definition: config.h:469
const char * parent
Definition: cib.c:27
#define pcmk__set_xml_flags(xml_priv, flags_to_set)
G_GNUC_INTERNAL xmlNode * pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
Definition: xml_comment.c:50
#define pcmk__clear_xml_flags(xml_priv, flags_to_clear)
#define PCMK__XML_ENTITY_AMP
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
#define crm_info(fmt, args...)
Definition: logging.h:367
G_GNUC_INTERNAL int pcmk__xa_remove(xmlAttr *attr, bool force)
Definition: xml_attr.c:45
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml_element.c:379
uint64_t flags
Definition: remote.c:211
void pcmk__xml_free_node(xmlNode *xml)
Definition: xml.c:696
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:368
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1