This source file includes following definitions.
- pcmk__xml_element_type_text
- pcmk__xml_tree_foreach
- pcmk__xml_set_parent_flags
- pcmk__xml_doc_set_flags
- pcmk__xml_doc_all_flags_set
- pcmk__mark_xml_node_dirty
- pcmk__xml_reset_node_flags
- mark_xml_dirty_created
- mark_xml_tree_dirty_created
- free_deleted_object
- reset_xml_private_data
- new_private_data
- free_private_data
- pcmk__xml_new_private_data
- pcmk__xml_free_private_data
- pcmk__xml_position
- commit_attr_deletions
- pcmk__xml_commit_changes
- pcmk__xml_new_doc
- pcmk__xml_free_doc
- pcmk__xml_is_name_start_char
- pcmk__xml_is_name_char
- pcmk__xml_sanitize_id
- pcmk__xml_free_node
- free_xml_with_position
- pcmk__xml_free
- pcmk__xml_copy
- pcmk__strip_xml_text
- pcmk__xml_needs_escape
- pcmk__xml_escape
- mark_attr_deleted
- mark_attr_changed
- mark_attr_moved
- xml_diff_old_attrs
- mark_created_attrs
- xml_diff_attrs
- mark_child_deleted
- mark_child_moved
- new_comment_matches
- new_element_matches
- new_child_matches
- find_matching_children
- pcmk__xml_mark_changes
- pcmk__xml_artefact_root
- find_artefact
- pcmk__xml_artefact_path
- copy_xml
- crm_xml_init
- crm_xml_cleanup
- pcmk_free_xml_subtree
- free_xml
- crm_xml_sanitize_id
- xml_tracking_changes
- xml_document_dirty
- xml_accept_changes
- xml_track_changes
- xml_calculate_changes
- xml_calculate_significant_changes
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdarg.h>
13 #include <stdint.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19
20 #include <glib.h>
21 #include <libxml/tree.h>
22 #include <libxml/xmlstring.h>
23
24 #include <crm/crm.h>
25 #include <crm/common/xml.h>
26 #include <crm/common/xml_internal.h>
27 #include "crmcommon_private.h"
28
29
30 #define XML_VERSION ((const xmlChar *) "1.0")
31
32
33
34
35
36
37
38
39
40 const char *
41 pcmk__xml_element_type_text(xmlElementType type)
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
67 if ((type < XML_ELEMENT_NODE) || (type > XML_XINCLUDE_END)) {
68 return "unrecognized type";
69 }
70 return element_type_names[type];
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 bool
87 pcmk__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
108 void
109 pcmk__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
120
121
122
123
124
125
126
127 void
128 pcmk__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
137
138
139
140
141
142
143
144
145
146 bool
147 pcmk__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
158 void
159 pcmk__mark_xml_node_dirty(xmlNode *xml)
160 {
161 if (xml == NULL) {
162 return;
163 }
164 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_dirty);
165 pcmk__xml_set_parent_flags(xml, pcmk__xf_dirty);
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179 bool
180 pcmk__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
190
191
192
193
194
195
196
197
198
199
200
201 static bool
202 mark_xml_dirty_created(xmlNode *xml, void *user_data)
203 {
204 xml_node_private_t *nodepriv = xml->_private;
205
206 if (nodepriv != NULL) {
207 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
208 }
209 return true;
210 }
211
212
213
214
215
216
217
218
219
220 static void
221 mark_xml_tree_dirty_created(xmlNode *xml)
222 {
223 pcmk__assert(xml != NULL);
224
225 if (!pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking)) {
226
227 return;
228 }
229
230
231 pcmk__mark_xml_node_dirty(xml);
232
233 pcmk__xml_tree_foreach(xml, mark_xml_dirty_created, NULL);
234 }
235
236
237 static void
238 free_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
249 static void
250 reset_xml_private_data(xml_doc_private_t *docpriv)
251 {
252 if (docpriv != NULL) {
253 pcmk__assert(docpriv->check == PCMK__XML_DOC_PRIVATE_MAGIC);
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
269
270
271
272
273
274
275
276
277
278
279
280 static bool
281 new_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 =
297 pcmk__assert_alloc(1, sizeof(xml_doc_private_t));
298
299 docpriv->check = PCMK__XML_DOC_PRIVATE_MAGIC;
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 =
309 pcmk__assert_alloc(1, sizeof(xml_node_private_t));
310
311 nodepriv->check = PCMK__XML_NODE_PRIVATE_MAGIC;
312 node->_private = nodepriv;
313 if (tracking) {
314 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
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) {
336 pcmk__mark_xml_node_dirty(node);
337 }
338 return true;
339 }
340
341
342
343
344
345
346
347
348
349
350
351
352 static bool
353 free_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
367 pcmk__assert(nodepriv->check == PCMK__XML_NODE_PRIVATE_MAGIC);
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
380
381
382
383
384
385
386 void
387 pcmk__xml_new_private_data(xmlNode *xml)
388 {
389 pcmk__xml_tree_foreach(xml, new_private_data, NULL);
390 }
391
392
393
394
395
396
397
398 void
399 pcmk__xml_free_private_data(xmlNode *xml)
400 {
401 pcmk__xml_tree_foreach(xml, free_private_data, NULL);
402 }
403
404
405
406
407
408
409
410
411
412
413 int
414 pcmk__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
429
430
431
432
433
434
435
436
437
438
439
440 static bool
441 commit_attr_deletions(xmlNode *xml, void *user_data)
442 {
443 pcmk__xml_reset_node_flags(xml, NULL);
444 pcmk__xe_remove_matching_attrs(xml, true, pcmk__marked_as_deleted, NULL);
445 return true;
446 }
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467 void
468 pcmk__xml_commit_changes(xmlDoc *doc)
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
489
490
491
492
493
494
495
496
497
498 xmlDoc *
499 pcmk__xml_new_doc(void)
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
508
509
510
511
512
513
514 void
515 pcmk__xml_free_doc(xmlDoc *doc)
516 {
517 if (doc != NULL) {
518 pcmk__xml_free_private_data((xmlNode *) doc);
519 xmlFreeDoc(doc);
520 }
521 }
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539 bool
540 pcmk__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
550
551
552
553
554
555
556
557
558
559
560 *len = 4;
561
562
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
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 bool
611 pcmk__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
621 *len = 4;
622
623
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
661
662
663
664
665
666
667
668
669
670
671
672
673 void
674 pcmk__xml_sanitize_id(char *id)
675 {
676 bool valid = true;
677 int len = 0;
678
679
680 pcmk__assert(!pcmk__str_empty(id));
681
682
683
684
685
686
687
688
689 valid = pcmk__xml_is_name_start_char(id, &len);
690 CRM_CHECK(len > 0, return);
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);
701 if (!valid) {
702 for (int i = 0; i < len; i++) {
703 id[i] = '.';
704 }
705 }
706 }
707 }
708
709
710
711
712
713
714
715 void
716 pcmk__xml_free_node(xmlNode *xml)
717 {
718 pcmk__xml_free_private_data(xml);
719 xmlUnlinkNode(xml);
720 xmlFreeNode(xml);
721 }
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736 static int
737 free_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
750
751
752 pcmk__xml_free_doc(doc);
753 return pcmk_rc_ok;
754 }
755
756 if (!pcmk__check_acl(node, NULL, pcmk__xf_acl_write)) {
757 pcmk__if_tracing(
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
772 if (pcmk__xml_doc_all_flags_set(node->doc, pcmk__xf_tracking)
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
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,
794 pcmk__xf_skip);
795 }
796 }
797
798 docpriv->deleted_objs = g_list_append(docpriv->deleted_objs,
799 deleted_obj);
800 pcmk__xml_doc_set_flags(node->doc, pcmk__xf_dirty);
801 }
802 }
803 pcmk__xml_free_node(node);
804 return pcmk_rc_ok;
805 }
806
807
808
809
810
811
812
813
814
815 void
816 pcmk__xml_free(xmlNode *xml)
817 {
818 free_xml_with_position(xml, -1);
819 }
820
821
822
823
824
825
826
827
828
829
830
831 xmlNode *
832 pcmk__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
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
859 pcmk__xml_new_private_data(copy);
860 return copy;
861 }
862
863
864
865
866
867
868
869 void
870 pcmk__strip_xml_text(xmlNode *xml)
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:
879 pcmk__xml_free_node(iter);
880 break;
881
882 case XML_ELEMENT_NODE:
883
884 pcmk__strip_xml_text(iter);
885 break;
886
887 default:
888
889 break;
890 }
891
892 iter = next;
893 }
894 }
895
896
897
898
899
900
901
902
903
904
905
906
907
908 bool
909 pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type)
910 {
911 if (text == NULL) {
912 return false;
913 }
914
915 while (*text != '\0') {
916 switch (type) {
917 case pcmk__xml_escape_text:
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
934 case pcmk__xml_escape_attr:
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
949 case pcmk__xml_escape_attr_pretty:
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:
962 pcmk__assert(false);
963 break;
964 }
965
966 text = g_utf8_next_char(text);
967 }
968 return false;
969 }
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990 gchar *
991 pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type)
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
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) {
1011 case pcmk__xml_escape_text:
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
1036 case pcmk__xml_escape_attr:
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
1060 case pcmk__xml_escape_attr_pretty:
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:
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
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104 static void
1105 mark_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
1113
1114
1115 pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1116 crm_xml_add(new_xml, attr_name, old_value);
1117 pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1118
1119
1120 attr = xmlHasProp(new_xml, (const xmlChar *) attr_name);
1121 nodepriv = attr->_private;
1122 nodepriv->flags = 0;
1123
1124
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
1133
1134
1135 static void
1136 mark_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
1146 pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
1147 crm_xml_add(new_xml, attr_name, old_value);
1148 pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
1149
1150
1151 crm_xml_add(new_xml, attr_name, vcopy);
1152 free(vcopy);
1153 }
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166 static void
1167 mark_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
1176 pcmk__mark_xml_node_dirty(new_xml);
1177
1178
1179 pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
1180
1181 nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
1182 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1183 }
1184
1185
1186
1187
1188
1189
1190
1191
1192 static void
1193 xml_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,
1211 pcmk__xf_skip);
1212 int old_pos = pcmk__xml_position((xmlNode*) old_attr,
1213 pcmk__xf_skip);
1214 const char *new_value = crm_element_value(new_xml, name);
1215
1216
1217 pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
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,
1225 pcmk__xf_ignore_attr_pos
1226 |pcmk__xf_tracking)) {
1227
1228
1229
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
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247 static void
1248 mark_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
1265
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
1271 pcmk__xa_remove(new_attr, true);
1272 }
1273 }
1274 }
1275 }
1276
1277
1278
1279
1280
1281
1282
1283
1284 static void
1285 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
1286 {
1287
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
1292 pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
1293 }
1294
1295 xml_diff_old_attrs(old_xml, new_xml);
1296 mark_created_attrs(new_xml);
1297 }
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318 static void
1319 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
1320 {
1321 int pos = pcmk__xml_position(old_child, pcmk__xf_skip);
1322
1323
1324 xmlNode *candidate = pcmk__xml_copy(new_parent, old_child);
1325
1326
1327 pcmk__xml_tree_foreach(candidate, pcmk__xml_reset_node_flags, NULL);
1328
1329
1330 pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
1331
1332
1333
1334
1335 if (free_xml_with_position(candidate, pos) != pcmk_rc_ok) {
1336
1337 pcmk__xml_free_node(candidate);
1338 }
1339
1340 pcmk__set_xml_flags((xml_node_private_t *) old_child->_private,
1341 pcmk__xf_skip);
1342 }
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353 static void
1354 mark_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);
1365 pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
1366
1367
1368
1369
1370
1371
1372 if (old_pos > new_pos) {
1373 nodepriv = old_child->_private;
1374 }
1375 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1376 }
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394 static bool
1395 new_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
1401
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
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436 static bool
1437 new_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),
1441 pcmk__str_none);
1442 }
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464 static bool
1465 new_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
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491 static void
1492 find_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
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
1511
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
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536 void
1537 pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml)
1538 {
1539
1540
1541
1542
1543
1544
1545
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
1552 pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_tracking);
1553 xml_diff_attrs(old_xml, new_xml);
1554
1555 find_matching_children(old_xml, new_xml);
1556
1557
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
1570 mark_child_deleted(old_child, new_xml);
1571 continue;
1572 }
1573
1574
1575
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
1584 continue;
1585 }
1586
1587 pcmk__xml_mark_changes(old_child, new_child);
1588 }
1589
1590
1591
1592
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
1607
1608
1609
1610
1611
1612
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
1626 pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
1627 mark_xml_tree_dirty_created(new_child);
1628
1629
1630 pcmk__apply_creation_acl(new_child, true);
1631 }
1632 }
1633
1634 char *
1635 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
1636 {
1637 static const char *base = NULL;
1638 char *ret = NULL;
1639
1640 if (base == NULL) {
1641 base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
1642 }
1643 if (pcmk__str_empty(base)) {
1644 base = PCMK_SCHEMA_DIR;
1645 }
1646
1647 switch (ns) {
1648 case pcmk__xml_artefact_ns_legacy_rng:
1649 case pcmk__xml_artefact_ns_legacy_xslt:
1650 ret = strdup(base);
1651 break;
1652 case pcmk__xml_artefact_ns_base_rng:
1653 case pcmk__xml_artefact_ns_base_xslt:
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
1662 static char *
1663 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
1664 {
1665 char *ret = NULL;
1666
1667 switch (ns) {
1668 case pcmk__xml_artefact_ns_legacy_rng:
1669 case pcmk__xml_artefact_ns_base_rng:
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;
1676 case pcmk__xml_artefact_ns_legacy_xslt:
1677 case pcmk__xml_artefact_ns_base_xslt:
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
1691 char *
1692 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
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
1712
1713
1714 #include <libxml/parser.h>
1715
1716 #include <crm/common/xml_compat.h>
1717
1718 xmlNode *
1719 copy_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);
1728 pcmk__xml_new_private_data(copy);
1729 return copy;
1730 }
1731
1732 void
1733 crm_xml_init(void)
1734 {
1735 pcmk__schema_init();
1736 }
1737
1738 void
1739 crm_xml_cleanup(void)
1740 {
1741 pcmk__schema_cleanup();
1742 xmlCleanupParser();
1743 }
1744
1745 void
1746 pcmk_free_xml_subtree(xmlNode *xml)
1747 {
1748 pcmk__xml_free_node(xml);
1749 }
1750
1751 void
1752 free_xml(xmlNode *child)
1753 {
1754 pcmk__xml_free(child);
1755 }
1756
1757 void
1758 crm_xml_sanitize_id(char *id)
1759 {
1760 char *c;
1761
1762 for (c = id; *c; ++c) {
1763 switch (*c) {
1764 case ':':
1765 case '#':
1766 *c = '.';
1767 }
1768 }
1769 }
1770
1771 bool
1772 xml_tracking_changes(xmlNode *xml)
1773 {
1774 return (xml != NULL)
1775 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_tracking);
1776 }
1777
1778 bool
1779 xml_document_dirty(xmlNode *xml)
1780 {
1781 return (xml != NULL)
1782 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_dirty);
1783 }
1784
1785 void
1786 xml_accept_changes(xmlNode *xml)
1787 {
1788 if (xml != NULL) {
1789 pcmk__xml_commit_changes(xml->doc);
1790 }
1791 }
1792
1793 void
1794 xml_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);
1804 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_tracking);
1805 if (enforce_acls) {
1806 if (acl_source == NULL) {
1807 acl_source = xml;
1808 }
1809 pcmk__xml_doc_set_flags(xml->doc, pcmk__xf_acl_enabled);
1810 pcmk__unpack_acl(acl_source, xml, user);
1811 pcmk__apply_acl(xml);
1812 }
1813 }
1814
1815 void
1816 xml_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),
1821 pcmk__str_none),
1822 return);
1823
1824 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
1825
1826 pcmk__xml_commit_changes(new_xml->doc);
1827 }
1828
1829 pcmk__xml_mark_changes(old_xml, new_xml);
1830 }
1831
1832 void
1833 xml_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),
1838 pcmk__str_none),
1839 return);
1840
1841
1842
1843
1844
1845
1846
1847 pcmk__xml_doc_set_flags(new_xml->doc, pcmk__xf_ignore_attr_pos);
1848
1849 if (!pcmk__xml_doc_all_flags_set(new_xml->doc, pcmk__xf_tracking)) {
1850
1851 pcmk__xml_commit_changes(new_xml->doc);
1852 }
1853
1854 pcmk__xml_mark_changes(old_xml, new_xml);
1855 }
1856
1857
1858