pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
xml.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2025 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/tree.h> // xmlNode, etc.
22#include <libxml/xmlstring.h> // xmlChar, xmlGetUTF8Char()
23
24#include <crm/crm.h>
25#include <crm/common/xml.h>
26#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
27#include "crmcommon_private.h"
28
30#define XML_VERSION ((const xmlChar *) "1.0")
31
40const char *
42{
43 static const char *const element_type_names[] = {
44 [XML_ELEMENT_NODE] = "element",
45 [XML_ATTRIBUTE_NODE] = "attribute",
46 [XML_TEXT_NODE] = "text",
47 [XML_CDATA_SECTION_NODE] = "CDATA section",
48 [XML_ENTITY_REF_NODE] = "entity reference",
49 [XML_ENTITY_NODE] = "entity",
50 [XML_PI_NODE] = "PI",
51 [XML_COMMENT_NODE] = "comment",
52 [XML_DOCUMENT_NODE] = "document",
53 [XML_DOCUMENT_TYPE_NODE] = "document type",
54 [XML_DOCUMENT_FRAG_NODE] = "document fragment",
55 [XML_NOTATION_NODE] = "notation",
56 [XML_HTML_DOCUMENT_NODE] = "HTML document",
57 [XML_DTD_NODE] = "DTD",
58 [XML_ELEMENT_DECL] = "element declaration",
59 [XML_ATTRIBUTE_DECL] = "attribute declaration",
60 [XML_ENTITY_DECL] = "entity declaration",
61 [XML_NAMESPACE_DECL] = "namespace declaration",
62 [XML_XINCLUDE_START] = "XInclude start",
63 [XML_XINCLUDE_END] = "XInclude end",
64 };
65
66 // Assumes the numeric values of the indices are in ascending order
67 if ((type < XML_ELEMENT_NODE) || (type > XML_XINCLUDE_END)) {
68 return "unrecognized type";
69 }
70 return element_type_names[type];
71}
72
86bool
87pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
88 void *user_data)
89{
90 if (xml == NULL) {
91 return true;
92 }
93
94 if (!fn(xml, user_data)) {
95 return false;
96 }
97
98 for (xml = pcmk__xml_first_child(xml); xml != NULL;
99 xml = pcmk__xml_next(xml)) {
100
101 if (!pcmk__xml_tree_foreach(xml, fn, user_data)) {
102 return false;
103 }
104 }
105 return true;
106}
107
108void
109pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
110{
111 for (; xml != NULL; xml = xml->parent) {
112 xml_node_private_t *nodepriv = xml->_private;
113
114 if (nodepriv != NULL) {
115 pcmk__set_xml_flags(nodepriv, flags);
116 }
117 }
118}
119
127void
128pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags)
129{
130 if (doc != NULL) {
131 xml_doc_private_t *docpriv = doc->_private;
132
133 pcmk__set_xml_flags(docpriv, flags);
134 }
135}
136
146bool
147pcmk__xml_doc_all_flags_set(const xmlDoc *doc, uint32_t flags)
148{
149 if (doc != NULL) {
150 xml_doc_private_t *docpriv = doc->_private;
151
152 return (docpriv != NULL) && pcmk_all_flags_set(docpriv->flags, flags);
153 }
154 return false;
155}
156
157// Mark document, element, and all element's parents as changed
158void
160{
161 if (xml == NULL) {
162 return;
163 }
166}
167
179bool
180pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
181{
182 xml_node_private_t *nodepriv = xml->_private;
183
184 if (nodepriv != NULL) {
185 nodepriv->flags = pcmk__xf_none;
186 }
187 return true;
188}
189
201static bool
202mark_xml_dirty_created(xmlNode *xml, void *user_data)
203{
204 xml_node_private_t *nodepriv = xml->_private;
205
206 if (nodepriv != NULL) {
208 }
209 return true;
210}
211
220static void
221mark_xml_tree_dirty_created(xmlNode *xml)
222{
223 pcmk__assert(xml != NULL);
224
226 // Tracking is disabled for entire document
227 return;
228 }
229
230 // Mark all parents and document dirty
232
233 pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
234}
235
236// Free an XML object previously marked as deleted
237static void
238free_deleted_object(void *data)
239{
240 if(data) {
241 pcmk__deleted_xml_t *deleted_obj = data;
242
243 g_free(deleted_obj->path);
244 free(deleted_obj);
245 }
246}
247
248// Free and NULL user, ACLs, and deleted objects in an XML node's private data
249static void
250reset_xml_private_data(xml_doc_private_t *docpriv)
251{
252 if (docpriv != NULL) {
254
255 pcmk__str_update(&(docpriv->acl_user), NULL);
256
257 if (docpriv->acls != NULL) {
258 pcmk__free_acls(docpriv->acls);
259 docpriv->acls = NULL;
260 }
261
262 if(docpriv->deleted_objs) {
263 g_list_free_full(docpriv->deleted_objs, free_deleted_object);
264 docpriv->deleted_objs = NULL;
265 }
266 }
267}
268
280static bool
281new_private_data(xmlNode *node, void *user_data)
282{
283 bool tracking = false;
284
285 CRM_CHECK(node != NULL, return true);
286
287 if (node->_private != NULL) {
288 return true;
289 }
290
291 tracking = pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking);
292
293 switch (node->type) {
294 case XML_DOCUMENT_NODE:
295 {
296 xml_doc_private_t *docpriv =
298
300 node->_private = docpriv;
301 }
302 break;
303
304 case XML_ELEMENT_NODE:
305 case XML_ATTRIBUTE_NODE:
306 case XML_COMMENT_NODE:
307 {
308 xml_node_private_t *nodepriv =
310
312 node->_private = nodepriv;
313 if (tracking) {
315 }
316
317 for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
318 iter = iter->next) {
319
320 new_private_data((xmlNode *) iter, user_data);
321 }
322 }
323 break;
324
325 case XML_TEXT_NODE:
326 case XML_DTD_NODE:
327 case XML_CDATA_SECTION_NODE:
328 return true;
329
330 default:
331 CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
332 return true;
333 }
334
335 if (tracking) {
337 }
338 return true;
339}
340
352static bool
353free_private_data(xmlNode *node, void *user_data)
354{
355 CRM_CHECK(node != NULL, return true);
356
357 if (node->_private == NULL) {
358 return true;
359 }
360
361 if (node->type == XML_DOCUMENT_NODE) {
362 reset_xml_private_data((xml_doc_private_t *) node->_private);
363
364 } else {
365 xml_node_private_t *nodepriv = node->_private;
366
368
369 for (xmlAttr *iter = pcmk__xe_first_attr(node); iter != NULL;
370 iter = iter->next) {
371
372 free_private_data((xmlNode *) iter, user_data);
373 }
374 }
375 free(node->_private);
376 node->_private = NULL;
377 return true;
378}
379
386void
388{
389 pcmk__xml_tree_foreach(xml, new_private_data, NULL);
390}
391
398void
400{
401 pcmk__xml_tree_foreach(xml, free_private_data, NULL);
402}
403
413int
414pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set)
415{
416 int position = 0;
417
418 for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
419 xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
420
421 if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
422 position++;
423 }
424 }
425
426 return position;
427}
428
440static bool
441commit_attr_deletions(xmlNode *xml, void *user_data)
442{
445 return true;
446}
447
467void
469{
470 xml_doc_private_t *docpriv = NULL;
471
472 if (doc == NULL) {
473 return;
474 }
475
476 docpriv = doc->_private;
477 if (docpriv == NULL) {
478 return;
479 }
480
481 if (pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
482 pcmk__xml_tree_foreach(xmlDocGetRootElement(doc), commit_attr_deletions,
483 NULL);
484 }
485 reset_xml_private_data(docpriv);
486 docpriv->flags = pcmk__xf_none;
487}
488
498xmlDoc *
500{
501 xmlDoc *doc = xmlNewDoc(XML_VERSION);
502
503 pcmk__mem_assert(doc);
504 pcmk__xml_new_private_data((xmlNode *) doc);
505 return doc;
506}
507
514void
516{
517 if (doc != NULL) {
518 pcmk__xml_free_private_data((xmlNode *) doc);
519 xmlFreeDoc(doc);
520 }
521}
522
539bool
540pcmk__xml_is_name_start_char(const char *utf8, int *len)
541{
542 int c = 0;
543 int local_len = 0;
544
545 if (len == NULL) {
546 len = &local_len;
547 }
548
549 /* xmlGetUTF8Char() abuses the len argument. At call time, it must be set to
550 * "the minimum number of bytes present in the sequence... to assure the
551 * next character is completely contained within the sequence." It's similar
552 * to the "n" in the strn*() functions. However, this doesn't make any sense
553 * for null-terminated strings, and there's no value that indicates "keep
554 * going until '\0'." So we set it to 4, the max number of bytes in a UTF-8
555 * character.
556 *
557 * At return, it's set to the actual number of bytes in the char, or 0 on
558 * error.
559 */
560 *len = 4;
561
562 // Note: xmlGetUTF8Char() assumes a 32-bit int
563 c = xmlGetUTF8Char((const xmlChar *) utf8, len);
564 if (c < 0) {
565 GString *buf = g_string_sized_new(32);
566
567 for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
568 g_string_append_printf(buf, " 0x%.2X", utf8[i]);
569 }
570 crm_info("Invalid UTF-8 character (bytes:%s)",
571 (pcmk__str_empty(buf->str)? " <none>" : buf->str));
572 g_string_free(buf, TRUE);
573 return false;
574 }
575
576 return (c == '_')
577 || (c == ':')
578 || ((c >= 'a') && (c <= 'z'))
579 || ((c >= 'A') && (c <= 'Z'))
580 || ((c >= 0xC0) && (c <= 0xD6))
581 || ((c >= 0xD8) && (c <= 0xF6))
582 || ((c >= 0xF8) && (c <= 0x2FF))
583 || ((c >= 0x370) && (c <= 0x37D))
584 || ((c >= 0x37F) && (c <= 0x1FFF))
585 || ((c >= 0x200C) && (c <= 0x200D))
586 || ((c >= 0x2070) && (c <= 0x218F))
587 || ((c >= 0x2C00) && (c <= 0x2FEF))
588 || ((c >= 0x3001) && (c <= 0xD7FF))
589 || ((c >= 0xF900) && (c <= 0xFDCF))
590 || ((c >= 0xFDF0) && (c <= 0xFFFD))
591 || ((c >= 0x10000) && (c <= 0xEFFFF));
592}
593
610bool
611pcmk__xml_is_name_char(const char *utf8, int *len)
612{
613 int c = 0;
614 int local_len = 0;
615
616 if (len == NULL) {
617 len = &local_len;
618 }
619
620 // See comment regarding len in pcmk__xml_is_name_start_char()
621 *len = 4;
622
623 // Note: xmlGetUTF8Char() assumes a 32-bit int
624 c = xmlGetUTF8Char((const xmlChar *) utf8, len);
625 if (c < 0) {
626 GString *buf = g_string_sized_new(32);
627
628 for (int i = 0; (i < 4) && (utf8[i] != '\0'); i++) {
629 g_string_append_printf(buf, " 0x%.2X", utf8[i]);
630 }
631 crm_info("Invalid UTF-8 character (bytes:%s)",
632 (pcmk__str_empty(buf->str)? " <none>" : buf->str));
633 g_string_free(buf, TRUE);
634 return false;
635 }
636
637 return ((c >= 'a') && (c <= 'z'))
638 || ((c >= 'A') && (c <= 'Z'))
639 || ((c >= '0') && (c <= '9'))
640 || (c == '_')
641 || (c == ':')
642 || (c == '-')
643 || (c == '.')
644 || (c == 0xB7)
645 || ((c >= 0xC0) && (c <= 0xD6))
646 || ((c >= 0xD8) && (c <= 0xF6))
647 || ((c >= 0xF8) && (c <= 0x2FF))
648 || ((c >= 0x300) && (c <= 0x36F))
649 || ((c >= 0x370) && (c <= 0x37D))
650 || ((c >= 0x37F) && (c <= 0x1FFF))
651 || ((c >= 0x200C) && (c <= 0x200D))
652 || ((c >= 0x203F) && (c <= 0x2040))
653 || ((c >= 0x2070) && (c <= 0x218F))
654 || ((c >= 0x2C00) && (c <= 0x2FEF))
655 || ((c >= 0x3001) && (c <= 0xD7FF))
656 || ((c >= 0xF900) && (c <= 0xFDCF))
657 || ((c >= 0xFDF0) && (c <= 0xFFFD))
658 || ((c >= 0x10000) && (c <= 0xEFFFF));
659}
660
673void
675{
676 bool valid = true;
677 int len = 0;
678
679 // If id is empty or NULL, there's no way to make it a valid XML ID
680 pcmk__assert(!pcmk__str_empty(id));
681
682 /* @TODO Suppose there are two strings and each has an invalid ID character
683 * in the same position. The strings are otherwise identical. Both strings
684 * will be sanitized to the same valid ID, which is incorrect.
685 *
686 * The caller is responsible for ensuring the sanitized ID does not already
687 * exist in a given XML document before using it, if uniqueness is desired.
688 */
689 valid = pcmk__xml_is_name_start_char(id, &len);
690 CRM_CHECK(len > 0, return); // UTF-8 encoding error
691 if (!valid) {
692 *id = '_';
693 for (int i = 1; i < len; i++) {
694 id[i] = '.';
695 }
696 }
697
698 for (id += len; *id != '\0'; id += len) {
699 valid = pcmk__xml_is_name_char(id, &len);
700 CRM_CHECK(len > 0, return); // UTF-8 encoding error
701 if (!valid) {
702 for (int i = 0; i < len; i++) {
703 id[i] = '.';
704 }
705 }
706 }
707}
708
715void
717{
719 xmlUnlinkNode(xml);
720 xmlFreeNode(xml);
721}
722
736static int
737free_xml_with_position(xmlNode *node, int position)
738{
739 xmlDoc *doc = NULL;
740 xml_node_private_t *nodepriv = NULL;
741
742 if (node == NULL) {
743 return pcmk_rc_ok;
744 }
745 doc = node->doc;
746 nodepriv = node->_private;
747
748 if ((doc != NULL) && (xmlDocGetRootElement(doc) == node)) {
749 /* @TODO Should we check ACLs first? Otherwise it seems like we could
750 * free the root element without write permission.
751 */
753 return pcmk_rc_ok;
754 }
755
756 if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
758 {
759 GString *xpath = pcmk__element_xpath(node);
760
761 qb_log_from_external_source(__func__, __FILE__,
762 "Cannot remove %s %x", LOG_TRACE,
763 __LINE__, 0, xpath->str,
764 nodepriv->flags);
765 g_string_free(xpath, TRUE);
766 },
767 {}
768 );
769 return EACCES;
770 }
771
773 && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
774
775 xml_doc_private_t *docpriv = doc->_private;
776 GString *xpath = pcmk__element_xpath(node);
777
778 if (xpath != NULL) {
779 pcmk__deleted_xml_t *deleted_obj = NULL;
780
781 crm_trace("Deleting %s %p from %p", xpath->str, node, doc);
782
783 deleted_obj = pcmk__assert_alloc(1, sizeof(pcmk__deleted_xml_t));
784 deleted_obj->path = g_string_free(xpath, FALSE);
785 deleted_obj->position = -1;
786
787 // Record the position only for XML comments for now
788 if (node->type == XML_COMMENT_NODE) {
789 if (position >= 0) {
790 deleted_obj->position = position;
791
792 } else {
793 deleted_obj->position = pcmk__xml_position(node,
795 }
796 }
797
798 docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
799 deleted_obj);
801 }
802 }
804 return pcmk_rc_ok;
805}
806
815void
816pcmk__xml_free(xmlNode *xml)
817{
818 free_xml_with_position(xml, -1);
819}
820
831xmlNode *
832pcmk__xml_copy(xmlNode *parent, xmlNode *src)
833{
834 xmlNode *copy = NULL;
835
836 if (src == NULL) {
837 return NULL;
838 }
839
840 if (parent == NULL) {
841 xmlDoc *doc = NULL;
842
843 // The copy will be the root element of a new document
844 pcmk__assert(src->type == XML_ELEMENT_NODE);
845
846 doc = pcmk__xml_new_doc();
847 copy = xmlDocCopyNode(src, doc, 1);
848 pcmk__mem_assert(copy);
849
850 xmlDocSetRootElement(doc, copy);
851
852 } else {
853 copy = xmlDocCopyNode(src, parent->doc, 1);
854 pcmk__mem_assert(copy);
855
856 xmlAddChild(parent, copy);
857 }
858
860 return copy;
861}
862
869void
871{
872 xmlNode *iter = xml->children;
873
874 while (iter) {
875 xmlNode *next = iter->next;
876
877 switch (iter->type) {
878 case XML_TEXT_NODE:
880 break;
881
882 case XML_ELEMENT_NODE:
883 /* Search it */
885 break;
886
887 default:
888 /* Leave it */
889 break;
890 }
891
892 iter = next;
893 }
894}
895
908bool
910{
911 if (text == NULL) {
912 return false;
913 }
914
915 while (*text != '\0') {
916 switch (type) {
918 switch (*text) {
919 case '<':
920 case '>':
921 case '&':
922 return true;
923 case '\n':
924 case '\t':
925 break;
926 default:
927 if (g_ascii_iscntrl(*text)) {
928 return true;
929 }
930 break;
931 }
932 break;
933
935 switch (*text) {
936 case '<':
937 case '>':
938 case '&':
939 case '"':
940 return true;
941 default:
942 if (g_ascii_iscntrl(*text)) {
943 return true;
944 }
945 break;
946 }
947 break;
948
950 switch (*text) {
951 case '\n':
952 case '\r':
953 case '\t':
954 case '"':
955 return true;
956 default:
957 break;
958 }
959 break;
960
961 default: // Invalid enum value
962 pcmk__assert(false);
963 break;
964 }
965
966 text = g_utf8_next_char(text);
967 }
968 return false;
969}
970
990gchar *
992{
993 GString *copy = NULL;
994
995 if (text == NULL) {
996 return NULL;
997 }
998 copy = g_string_sized_new(strlen(text));
999
1000 while (*text != '\0') {
1001 // Don't escape any non-ASCII characters
1002 if ((*text & 0x80) != 0) {
1003 size_t bytes = g_utf8_next_char(text) - text;
1004
1005 g_string_append_len(copy, text, bytes);
1006 text += bytes;
1007 continue;
1008 }
1009
1010 switch (type) {
1012 switch (*text) {
1013 case '<':
1014 g_string_append(copy, PCMK__XML_ENTITY_LT);
1015 break;
1016 case '>':
1017 g_string_append(copy, PCMK__XML_ENTITY_GT);
1018 break;
1019 case '&':
1020 g_string_append(copy, PCMK__XML_ENTITY_AMP);
1021 break;
1022 case '\n':
1023 case '\t':
1024 g_string_append_c(copy, *text);
1025 break;
1026 default:
1027 if (g_ascii_iscntrl(*text)) {
1028 g_string_append_printf(copy, "&#x%.2X;", *text);
1029 } else {
1030 g_string_append_c(copy, *text);
1031 }
1032 break;
1033 }
1034 break;
1035
1037 switch (*text) {
1038 case '<':
1039 g_string_append(copy, PCMK__XML_ENTITY_LT);
1040 break;
1041 case '>':
1042 g_string_append(copy, PCMK__XML_ENTITY_GT);
1043 break;
1044 case '&':
1045 g_string_append(copy, PCMK__XML_ENTITY_AMP);
1046 break;
1047 case '"':
1048 g_string_append(copy, PCMK__XML_ENTITY_QUOT);
1049 break;
1050 default:
1051 if (g_ascii_iscntrl(*text)) {
1052 g_string_append_printf(copy, "&#x%.2X;", *text);
1053 } else {
1054 g_string_append_c(copy, *text);
1055 }
1056 break;
1057 }
1058 break;
1059
1061 switch (*text) {
1062 case '"':
1063 g_string_append(copy, "\\\"");
1064 break;
1065 case '\n':
1066 g_string_append(copy, "\\n");
1067 break;
1068 case '\r':
1069 g_string_append(copy, "\\r");
1070 break;
1071 case '\t':
1072 g_string_append(copy, "\\t");
1073 break;
1074 default:
1075 g_string_append_c(copy, *text);
1076 break;
1077 }
1078 break;
1079
1080 default: // Invalid enum value
1081 pcmk__assert(false);
1082 break;
1083 }
1084
1085 text = g_utf8_next_char(text);
1086 }
1087 return g_string_free(copy, FALSE);
1088}
1089
1104static void
1105mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
1106 const char *old_value)
1107{
1108 xml_doc_private_t *docpriv = new_xml->doc->_private;
1109 xmlAttr *attr = NULL;
1110 xml_node_private_t *nodepriv;
1111
1112 /* Restore the old value (without setting dirty flag recursively upwards or
1113 * checking ACLs)
1114 */
1116 crm_xml_add(new_xml, attr_name, old_value);
1118
1119 // Reset flags (so the attribute doesn't appear as newly created)
1120 attr = xmlHasProp(new_xml, (const xmlChar *) attr_name);
1121 nodepriv = attr->_private;
1122 nodepriv->flags = 0;
1123
1124 // Check ACLs and mark restored value for later removal
1125 pcmk__xa_remove(attr, false);
1126
1127 crm_trace("XML attribute %s=%s was removed from %s",
1128 attr_name, old_value, element);
1129}
1130
1131/*
1132 * \internal
1133 * \brief Check ACLs for a changed XML attribute
1134 */
1135static void
1136mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
1137 const char *old_value)
1138{
1139 xml_doc_private_t *docpriv = new_xml->doc->_private;
1140 char *vcopy = crm_element_value_copy(new_xml, attr_name);
1141
1142 crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
1143 attr_name, old_value, vcopy, element);
1144
1145 // Restore the original value (without checking ACLs)
1147 crm_xml_add(new_xml, attr_name, old_value);
1149
1150 // Change it back to the new value, to check ACLs
1151 crm_xml_add(new_xml, attr_name, vcopy);
1152 free(vcopy);
1153}
1154
1166static void
1167mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
1168 xmlAttr *new_attr, int p_old, int p_new)
1169{
1170 xml_node_private_t *nodepriv = new_attr->_private;
1171
1172 crm_trace("XML attribute %s moved from position %d to %d in %s",
1173 old_attr->name, p_old, p_new, element);
1174
1175 // Mark document, element, and all element's parents as changed
1177
1178 // Mark attribute as changed
1180
1181 nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1183}
1184
1192static void
1193xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
1194{
1195 xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
1196
1197 while (attr_iter != NULL) {
1198 const char *name = (const char *) attr_iter->name;
1199 xmlAttr *old_attr = attr_iter;
1200 xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
1201 const char *old_value = pcmk__xml_attr_value(attr_iter);
1202
1203 attr_iter = attr_iter->next;
1204 if (new_attr == NULL) {
1205 mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
1206 old_value);
1207
1208 } else {
1209 xml_node_private_t *nodepriv = new_attr->_private;
1210 int new_pos = pcmk__xml_position((xmlNode*) new_attr,
1212 int old_pos = pcmk__xml_position((xmlNode*) old_attr,
1214 const char *new_value = crm_element_value(new_xml, name);
1215
1216 // This attribute isn't new
1218
1219 if (strcmp(new_value, old_value) != 0) {
1220 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
1221 old_value);
1222
1223 } else if ((old_pos != new_pos)
1224 && !pcmk__xml_doc_all_flags_set(new_xml->doc,
1227 /* pcmk__xf_tracking is always set by pcmk__xml_mark_changes()
1228 * before this function is called, so only the
1229 * pcmk__xf_ignore_attr_pos check is truly relevant.
1230 */
1231 mark_attr_moved(new_xml, (const char *) old_xml->name,
1232 old_attr, new_attr, old_pos, new_pos);
1233 }
1234 }
1235 }
1236}
1237
1247static void
1248mark_created_attrs(xmlNode *new_xml)
1249{
1250 xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
1251
1252 while (attr_iter != NULL) {
1253 xmlAttr *new_attr = attr_iter;
1254 xml_node_private_t *nodepriv = attr_iter->_private;
1255
1256 attr_iter = attr_iter->next;
1257 if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
1258 const char *attr_name = (const char *) new_attr->name;
1259
1260 crm_trace("Created new attribute %s=%s in %s",
1261 attr_name, pcmk__xml_attr_value(new_attr),
1262 new_xml->name);
1263
1264 /* Check ACLs (we can't use the remove-then-create trick because it
1265 * would modify the attribute position).
1266 */
1267 if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
1268 pcmk__mark_xml_attr_dirty(new_attr);
1269 } else {
1270 // Creation was not allowed, so remove the attribute
1271 pcmk__xa_remove(new_attr, true);
1272 }
1273 }
1274 }
1275}
1276
1284static void
1285xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
1286{
1287 // Cleared later if attributes are not really new
1288 for (xmlAttr *attr = pcmk__xe_first_attr(new_xml); attr != NULL;
1289 attr = attr->next) {
1290 xml_node_private_t *nodepriv = attr->_private;
1291
1293 }
1294
1295 xml_diff_old_attrs(old_xml, new_xml);
1296 mark_created_attrs(new_xml);
1297}
1298
1318static void
1319mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
1320{
1321 int pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1322
1323 // Re-create the child element so we can check ACLs
1324 xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1325
1326 // Clear flags on new child and its children
1328
1329 // free_xml_with_position() will check whether ACLs allow the deletion
1330 pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
1331
1332 /* Try to remove the child again (which will track it in document's
1333 * deleted_objs on success)
1334 */
1335 if (free_xml_with_position(candidate, pos) != pcmk_rc_ok) {
1336 // ACLs denied deletion in free_xml_with_position. Free candidate here.
1337 pcmk__xml_free_node(candidate);
1338 }
1339
1340 pcmk__set_xml_flags((xml_node_private_t *) old_child->_private,
1342}
1343
1353static void
1354mark_child_moved(xmlNode *old_child, xmlNode *new_child, int old_pos,
1355 int new_pos)
1356{
1357 const char *id_s = pcmk__s(pcmk__xe_id(new_child), "<no id>");
1358 xmlNode *new_parent = new_child->parent;
1359 xml_node_private_t *nodepriv = new_child->_private;
1360
1361 crm_trace("Child element %s with " PCMK_XA_ID "='%s' moved from position "
1362 "%d to %d under %s",
1363 new_child->name, id_s, old_pos, new_pos, new_parent->name);
1364 pcmk__mark_xml_node_dirty(new_parent);
1366
1367 /* @TODO Figure out and document why we skip the old child in future
1368 * position calculations if the old position is higher, and skip the new
1369 * child in future position calculations if the new position is higher. This
1370 * goes back to d028b52, and there's no explanation in the commit message.
1371 */
1372 if (old_pos > new_pos) {
1373 nodepriv = old_child->_private;
1374 }
1376}
1377
1394static bool
1395new_comment_matches(const xmlNode *old_comment, const xmlNode *new_comment)
1396{
1397 xml_node_private_t *nodepriv = new_comment->_private;
1398
1399 if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
1400 /* @TODO Should we also return false if old_comment has pcmk__xf_skip
1401 * set? This preserves existing behavior at time of writing.
1402 */
1403 return false;
1404 }
1405 if (pcmk__xml_position(old_comment, pcmk__xf_skip)
1406 != pcmk__xml_position(new_comment, pcmk__xf_skip)) {
1407 return false;
1408 }
1409 return pcmk__xc_matches(old_comment, new_comment);
1410}
1411
1436static bool
1437new_element_matches(const xmlNode *old_element, const xmlNode *new_element)
1438{
1439 return pcmk__xe_is(new_element, (const char *) old_element->name)
1440 && pcmk__str_eq(pcmk__xe_id(old_element), pcmk__xe_id(new_element),
1442}
1443
1464static bool
1465new_child_matches(const xmlNode *old_child, const xmlNode *new_child)
1466{
1467 if (old_child->type != new_child->type) {
1468 return false;
1469 }
1470
1471 switch (old_child->type) {
1472 case XML_COMMENT_NODE:
1473 return new_comment_matches(old_child, new_child);
1474 case XML_ELEMENT_NODE:
1475 return new_element_matches(old_child, new_child);
1476 default:
1477 return false;
1478 }
1479}
1480
1491static void
1492find_matching_children(xmlNode *old_xml, xmlNode *new_xml)
1493{
1494 for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1495 old_child = pcmk__xml_next(old_child)) {
1496
1497 xml_node_private_t *old_nodepriv = old_child->_private;
1498
1499 if ((old_nodepriv == NULL) || (old_nodepriv->match != NULL)) {
1500 // Can't process, or we already found a match for this old child
1501 continue;
1502 }
1503
1504 for (xmlNode *new_child = pcmk__xml_first_child(new_xml);
1505 new_child != NULL; new_child = pcmk__xml_next(new_child)) {
1506
1507 xml_node_private_t *new_nodepriv = new_child->_private;
1508
1509 if ((new_nodepriv == NULL) || (new_nodepriv->match != NULL)) {
1510 /* Can't process, or this new child already matched some old
1511 * child
1512 */
1513 continue;
1514 }
1515
1516 if (new_child_matches(old_child, new_child)) {
1517 old_nodepriv->match = new_child;
1518 new_nodepriv->match = old_child;
1519 break;
1520 }
1521 }
1522 }
1523}
1524
1536void
1537pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml)
1538{
1539 /* This function may set the xml_node_private_t:match member on children of
1540 * old_xml and new_xml, but it clears that member before returning.
1541 *
1542 * @TODO Ensure we handle (for example, by copying) or reject user-created
1543 * XML that is missing xml_node_private_t at top level or in any children.
1544 * Similarly, check handling of node types for which we don't create private
1545 * data. For now, we'll skip them in the loops below.
1546 */
1547 CRM_CHECK((old_xml != NULL) && (new_xml != NULL), return);
1548 if ((old_xml->_private == NULL) || (new_xml->_private == NULL)) {
1549 return;
1550 }
1551
1553 xml_diff_attrs(old_xml, new_xml);
1554
1555 find_matching_children(old_xml, new_xml);
1556
1557 // Process matches (changed children) and deletions
1558 for (xmlNode *old_child = pcmk__xml_first_child(old_xml); old_child != NULL;
1559 old_child = pcmk__xml_next(old_child)) {
1560
1561 xml_node_private_t *nodepriv = old_child->_private;
1562 xmlNode *new_child = NULL;
1563
1564 if (nodepriv == NULL) {
1565 continue;
1566 }
1567
1568 if (nodepriv->match == NULL) {
1569 // No match in new XML means the old child was deleted
1570 mark_child_deleted(old_child, new_xml);
1571 continue;
1572 }
1573
1574 /* Fetch the match and clear old_child->_private's match member.
1575 * new_child->_private's match member is handled in the new_xml loop.
1576 */
1577 new_child = nodepriv->match;
1578 nodepriv->match = NULL;
1579
1580 pcmk__assert(old_child->type == new_child->type);
1581
1582 if (old_child->type == XML_COMMENT_NODE) {
1583 // Comments match only if their positions and contents match
1584 continue;
1585 }
1586
1587 pcmk__xml_mark_changes(old_child, new_child);
1588 }
1589
1590 /* Mark unmatched new children as created, and mark matched new children as
1591 * moved if their positions changed. Grab the next new child in advance,
1592 * since new_child may get freed in the loop body.
1593 */
1594 for (xmlNode *new_child = pcmk__xml_first_child(new_xml),
1595 *next = pcmk__xml_next(new_child);
1596 new_child != NULL;
1597 new_child = next, next = pcmk__xml_next(new_child)) {
1598
1599 xml_node_private_t *nodepriv = new_child->_private;
1600
1601 if (nodepriv == NULL) {
1602 continue;
1603 }
1604
1605 if (nodepriv->match != NULL) {
1606 /* Fetch the match and clear new_child->_private's match member. Any
1607 * changes were marked in the old_xml loop. Mark the move.
1608 *
1609 * We might be able to mark the move earlier, when we mark changes
1610 * for matches in the old_xml loop, consolidating both actions. We'd
1611 * have to think about whether the timing of setting the
1612 * pcmk__xf_skip flag makes any difference.
1613 */
1614 xmlNode *old_child = nodepriv->match;
1615 int old_pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1616 int new_pos = pcmk__xml_position(new_child, pcmk__xf_skip);
1617
1618 if (old_pos != new_pos) {
1619 mark_child_moved(old_child, new_child, old_pos, new_pos);
1620 }
1621 nodepriv->match = NULL;
1622 continue;
1623 }
1624
1625 // No match in old XML means the new child is newly created
1627 mark_xml_tree_dirty_created(new_child);
1628
1629 // Check whether creation was allowed (may free new_child)
1630 pcmk__apply_creation_acl(new_child, true);
1631 }
1632}
1633
1634char *
1636{
1637 static const char *base = NULL;
1638 char *ret = NULL;
1639
1640 if (base == NULL) {
1642 }
1643 if (pcmk__str_empty(base)) {
1644 base = PCMK_SCHEMA_DIR;
1645 }
1646
1647 switch (ns) {
1650 ret = strdup(base);
1651 break;
1654 ret = crm_strdup_printf("%s/base", base);
1655 break;
1656 default:
1657 crm_err("XML artefact family specified as %u not recognized", ns);
1658 }
1659 return ret;
1660}
1661
1662static char *
1663find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
1664{
1665 char *ret = NULL;
1666
1667 switch (ns) {
1670 if (pcmk__ends_with(filespec, ".rng")) {
1671 ret = crm_strdup_printf("%s/%s", path, filespec);
1672 } else {
1673 ret = crm_strdup_printf("%s/%s.rng", path, filespec);
1674 }
1675 break;
1678 if (pcmk__ends_with(filespec, ".xsl")) {
1679 ret = crm_strdup_printf("%s/%s", path, filespec);
1680 } else {
1681 ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
1682 }
1683 break;
1684 default:
1685 crm_err("XML artefact family specified as %u not recognized", ns);
1686 }
1687
1688 return ret;
1689}
1690
1691char *
1693{
1694 struct stat sb;
1695 char *base = pcmk__xml_artefact_root(ns);
1696 char *ret = NULL;
1697
1698 ret = find_artefact(ns, base, filespec);
1699 free(base);
1700
1701 if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
1702 const char *remote_schema_dir = pcmk__remote_schema_dir();
1703
1704 free(ret);
1705 ret = find_artefact(ns, remote_schema_dir, filespec);
1706 }
1707
1708 return ret;
1709}
1710
1711// Deprecated functions kept only for backward API compatibility
1712// LCOV_EXCL_START
1713
1714#include <libxml/parser.h> // xmlCleanupParser()
1715
1716#include <crm/common/xml_compat.h>
1717
1718xmlNode *
1719copy_xml(xmlNode *src)
1720{
1721 xmlDoc *doc = pcmk__xml_new_doc();
1722 xmlNode *copy = NULL;
1723
1724 copy = xmlDocCopyNode(src, doc, 1);
1725 pcmk__mem_assert(copy);
1726
1727 xmlDocSetRootElement(doc, copy);
1729 return copy;
1730}
1731
1732void
1734{
1736}
1737
1738void
1740{
1742 xmlCleanupParser();
1743}
1744
1745void
1747{
1749}
1750
1751void
1752free_xml(xmlNode *child)
1753{
1754 pcmk__xml_free(child);
1755}
1756
1757void
1759{
1760 char *c;
1761
1762 for (c = id; *c; ++c) {
1763 switch (*c) {
1764 case ':':
1765 case '#':
1766 *c = '.';
1767 }
1768 }
1769}
1770
1771bool
1773{
1774 return (xml != NULL)
1776}
1777
1778bool
1780{
1781 return (xml != NULL)
1783}
1784
1785void
1787{
1788 if (xml != NULL) {
1789 pcmk__xml_commit_changes(xml->doc);
1790 }
1791}
1792
1793void
1794xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source,
1795 bool enforce_acls)
1796{
1797 if (xml == NULL) {
1798 return;
1799 }
1800
1801 pcmk__xml_commit_changes(xml->doc);
1802 crm_trace("Tracking changes%s to %p",
1803 (enforce_acls? " with ACLs" : ""), xml);
1805 if (enforce_acls) {
1806 if (acl_source == NULL) {
1807 acl_source = xml;
1808 }
1810 pcmk__unpack_acl(acl_source, xml, user);
1811 pcmk__apply_acl(xml);
1812 }
1813}
1814
1815void
1816xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
1817{
1818 CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
1819 && pcmk__xe_is(old_xml, (const char *) new_xml->name)
1820 && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
1822 return);
1823
1824 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
1825 // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
1826 pcmk__xml_commit_changes(new_xml->doc);
1827 }
1828
1829 pcmk__xml_mark_changes(old_xml, new_xml);
1830}
1831
1832void
1833xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
1834{
1835 CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
1836 && pcmk__xe_is(old_xml, (const char *) new_xml->name)
1837 && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
1839 return);
1840
1841 /* BUG: If pcmk__xf_tracking is not set for new_xml when this function is
1842 * called, then we unset pcmk__xf_ignore_attr_pos via
1843 * pcmk__xml_commit_changes(). Since this function is about to be
1844 * deprecated, it's not worth fixing this and changing the user-facing
1845 * behavior.
1846 */
1848
1849 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
1850 // Ensure tracking has a clean start (pcmk__xml_mark_changes() enables)
1851 pcmk__xml_commit_changes(new_xml->doc);
1852 }
1853
1854 pcmk__xml_mark_changes(old_xml, new_xml);
1855}
1856
1857// LCOV_EXCL_STOP
1858// End deprecated API
void pcmk__free_acls(GList *acls)
Definition acl.c:45
void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
Definition acl.c:315
void pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
Definition acl.c:612
void pcmk__apply_acl(xmlNode *xml)
Definition acl.c:216
bool pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode)
Definition acl.c:748
const char * parent
Definition cib.c:27
const char * path
Definition cib.c:28
const char * name
Definition cib.c:26
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
uint64_t flags
Definition remote.c:3
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition util.h:80
#define PCMK_SCHEMA_DIR
Definition config.h:460
char data[0]
Definition cpg.c:10
enum pcmk_ipc_server type
Definition cpg.c:3
A dumping ground.
G_GNUC_INTERNAL int pcmk__xa_remove(xmlAttr *attr, bool force)
Definition xml_attr.c:45
G_GNUC_INTERNAL bool pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data)
Definition xml_attr.c:89
#define pcmk__set_xml_flags(xml_priv, flags_to_set)
#define PCMK__XML_ENTITY_QUOT
#define pcmk__clear_xml_flags(xml_priv, flags_to_clear)
G_GNUC_INTERNAL bool pcmk__xc_matches(const xmlNode *comment1, const xmlNode *comment2)
Definition xml_comment.c:54
#define PCMK__XML_NODE_PRIVATE_MAGIC
G_GNUC_INTERNAL void pcmk__mark_xml_attr_dirty(xmlAttr *a)
Definition xml_attr.c:77
#define PCMK__XML_ENTITY_GT
#define PCMK__XML_ENTITY_AMP
#define PCMK__XML_ENTITY_LT
#define PCMK__XML_DOC_PRIVATE_MAGIC
#define crm_info(fmt, args...)
Definition logging.h:365
#define CRM_LOG_ASSERT(expr)
Definition logging.h:196
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define crm_trace(fmt, args...)
Definition logging.h:370
#define LOG_TRACE
Definition logging.h:38
#define pcmk__if_tracing(if_action, else_action)
#define PCMK__ENV_SCHEMA_DIRECTORY
const char * pcmk__env_option(const char *option)
Definition options.c:1085
@ pcmk_rc_ok
Definition results.h:159
#define pcmk__assert(expr)
#define pcmk__mem_assert(ptr)
const char * pcmk__remote_schema_dir(void)
Definition schemas.c:1547
void pcmk__schema_cleanup(void)
Definition schemas.c:712
void pcmk__schema_init(void)
Definition schemas.c:550
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
void pcmk__str_update(char **str, const char *value)
Definition strings.c:1280
@ pcmk__str_none
bool pcmk__ends_with(const char *s, const char *match)
Definition strings.c:610
int position
Position of the deleted node among its siblings.
gchar * path
XPath expression identifying the deleted node.
GList * deleted_objs
XML nodes marked as deleted (list of pcmk__deleted_xml_t)
char * acl_user
User affected by acls (for logging)
GList * acls
ACLs to check requested changes against (list of xml_acl_t)
uint32_t check
Magic number for checking integrity.
uint32_t flags
Group of enum pcmk__xml_flags
uint32_t check
Magic number for checking integrity.
uint32_t flags
Group of enum pcmk__xml_flags
xmlNode * match
Pointer to matching node (defined by caller)
int pcmk__xml_position(const xmlNode *xml, enum pcmk__xml_flags ignore_if_set)
Definition xml.c:414
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition xml.c:832
#define XML_VERSION
libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
Definition xml.c:30
void pcmk__xml_free_private_data(xmlNode *xml)
Definition xml.c:399
xmlDoc * pcmk__xml_new_doc(void)
Definition xml.c:499
void pcmk__xml_sanitize_id(char *id)
Definition xml.c:674
void pcmk__xml_free_node(xmlNode *xml)
Definition xml.c:716
bool pcmk__xml_doc_all_flags_set(const xmlDoc *doc, uint32_t flags)
Definition xml.c:147
void pcmk__xml_commit_changes(xmlDoc *doc)
Definition xml.c:468
void xml_accept_changes(xmlNode *xml)
Definition xml.c:1786
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition xml.c:1635
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
Definition xml.c:909
void crm_xml_init(void)
Definition xml.c:1733
xmlNode * copy_xml(xmlNode *src)
Definition xml.c:1719
bool pcmk__xml_tree_foreach(xmlNode *xml, bool(*fn)(xmlNode *, void *), void *user_data)
Definition xml.c:87
gchar * pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
Definition xml.c:991
void pcmk__strip_xml_text(xmlNode *xml)
Definition xml.c:870
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition xml.c:1816
void pcmk__xml_new_private_data(xmlNode *xml)
Definition xml.c:387
void pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags)
Definition xml.c:128
bool xml_tracking_changes(xmlNode *xml)
Definition xml.c:1772
void crm_xml_cleanup(void)
Definition xml.c:1739
bool xml_document_dirty(xmlNode *xml)
Definition xml.c:1779
void xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source, bool enforce_acls)
Definition xml.c:1794
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition xml.c:1833
void free_xml(xmlNode *child)
Definition xml.c:1752
void crm_xml_sanitize_id(char *id)
Definition xml.c:1758
bool pcmk__xml_is_name_start_char(const char *utf8, int *len)
Definition xml.c:540
void pcmk__mark_xml_node_dirty(xmlNode *xml)
Definition xml.c:159
void pcmk__xml_free(xmlNode *xml)
Definition xml.c:816
void pcmk__xml_free_doc(xmlDoc *doc)
Definition xml.c:515
void pcmk__xml_set_parent_flags(xmlNode *xml, uint64_t flags)
Definition xml.c:109
bool pcmk__xml_is_name_char(const char *utf8, int *len)
Definition xml.c:611
void pcmk_free_xml_subtree(xmlNode *xml)
Definition xml.c:1746
void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition xml.c:1537
bool pcmk__xml_reset_node_flags(xmlNode *xml, void *user_data)
Definition xml.c:180
const char * pcmk__xml_element_type_text(xmlElementType type)
Definition xml.c:41
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition xml.c:1692
Wrappers for and extensions to libxml2.
Deprecated Pacemaker XML API.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool force, bool(*match)(xmlAttrPtr, void *), void *user_data)
pcmk__xml_escape_type
@ pcmk__xml_escape_attr_pretty
@ pcmk__xml_escape_attr
@ pcmk__xml_escape_text
pcmk__xml_flags
@ pcmk__xf_acl_enabled
ACLs are enabled (set for document only)
@ pcmk__xf_created
Node was created.
@ pcmk__xf_ignore_attr_pos
Ignore attribute moves within an element (set for document only)
@ pcmk__xf_dirty
@ pcmk__xf_skip
Skip counting this node when getting a node's position among siblings.
@ pcmk__xf_acl_write
ACL write permission (implies read permission in most or all contexts)
@ pcmk__xf_tracking
Tracking is enabled (set for document only)
@ pcmk__xf_none
This flag has no effect.
@ pcmk__xf_moved
Node was moved.
pcmk__xml_artefact_ns
@ pcmk__xml_artefact_ns_legacy_xslt
@ pcmk__xml_artefact_ns_legacy_rng
@ pcmk__xml_artefact_ns_base_rng
@ pcmk__xml_artefact_ns_base_xslt
#define PCMK_XA_ID
Definition xml_names.h:301
GString * pcmk__element_xpath(const xmlNode *xml)
Definition xpath.c:281