1 /*
2 * Copyright 2004-2021 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <time.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <stdarg.h>
19 #include <bzlib.h>
20
21 #include <libxml/parser.h>
22 #include <libxml/tree.h>
23 #include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
24
25 #include <crm/crm.h>
26 #include <crm/msg_xml.h>
27 #include <crm/common/iso8601_internal.h>
28 #include <crm/common/xml.h>
29 #include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
30 #include "crmcommon_private.h"
31
32 // Define this as 1 in development to get insanely verbose trace messages
33 #ifndef XML_PARSER_DEBUG
34 #define XML_PARSER_DEBUG 0
35 #endif
36
37
38 /* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
39 * by libxml2, which is potentially ambiguous and dangerous. We should drop it
40 * when we can break backward compatibility with configurations that might be
41 * relying on it (i.e. pacemaker 3.0.0).
42 *
43 * It might be a good idea to have a transitional period where we first try
44 * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
45 * it, logging a warning if it succeeds.
46 */
47 #define PCMK__XML_PARSE_OPTS (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
48
49 #define CHUNK_SIZE 1024
50
51 bool
pcmk__tracking_xml_changes(xmlNode * xml,bool lazy)52 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
53 {
54 if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
55 return FALSE;
56 } else if (!pcmk_is_set(((xml_private_t *)xml->doc->_private)->flags,
57 xpf_tracking)) {
58 return FALSE;
59 } else if (lazy && !pcmk_is_set(((xml_private_t *)xml->doc->_private)->flags,
60 xpf_lazy)) {
61 return FALSE;
62 }
63 return TRUE;
64 }
65
66 #define buffer_print(buffer, max, offset, fmt, args...) do { \
67 int rc = (max); \
68 if(buffer) { \
69 rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
70 } \
71 if(buffer && rc < 0) { \
72 crm_perror(LOG_ERR, "snprintf failed at offset %d", offset); \
73 (buffer)[(offset)] = 0; \
74 break; \
75 } else if(rc >= ((max) - (offset))) { \
76 char *tmp = NULL; \
77 (max) = QB_MAX(CHUNK_SIZE, (max) * 2); \
78 tmp = pcmk__realloc((buffer), (max)); \
79 CRM_ASSERT(tmp); \
80 (buffer) = tmp; \
81 } else { \
82 offset += rc; \
83 break; \
84 } \
85 } while(1);
86
87 static void
insert_prefix(int options,char ** buffer,int * offset,int * max,int depth)88 insert_prefix(int options, char **buffer, int *offset, int *max, int depth)
89 {
90 if (options & xml_log_option_formatted) {
91 size_t spaces = 2 * depth;
92
93 if ((*buffer) == NULL || spaces >= ((*max) - (*offset))) {
94 (*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
95 (*buffer) = pcmk__realloc((*buffer), (*max));
96 }
97 memset((*buffer) + (*offset), ' ', spaces);
98 (*offset) += spaces;
99 }
100 }
101
102 static void
set_parent_flag(xmlNode * xml,long flag)103 set_parent_flag(xmlNode *xml, long flag)
104 {
105
106 for(; xml; xml = xml->parent) {
107 xml_private_t *p = xml->_private;
108
109 if(p == NULL) {
110 /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
111 } else {
112 pcmk__set_xml_flags(p, flag);
113 }
114 }
115 }
116
117 void
pcmk__set_xml_doc_flag(xmlNode * xml,enum xml_private_flags flag)118 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
119 {
120
121 if(xml && xml->doc && xml->doc->_private){
122 /* During calls to xmlDocCopyNode(), xml->doc may be unset */
123 xml_private_t *p = xml->doc->_private;
124
125 pcmk__set_xml_flags(p, flag);
126 }
127 }
128
129 // Mark document, element, and all element's parents as changed
130 static void
mark_xml_node_dirty(xmlNode * xml)131 mark_xml_node_dirty(xmlNode *xml)
132 {
133 pcmk__set_xml_doc_flag(xml, xpf_dirty);
134 set_parent_flag(xml, xpf_dirty);
135 }
136
137 // Clear flags on XML node and its children
138 static void
reset_xml_node_flags(xmlNode * xml)139 reset_xml_node_flags(xmlNode *xml)
140 {
141 xmlNode *cIter = NULL;
142 xml_private_t *p = xml->_private;
143
144 if (p) {
145 p->flags = 0;
146 }
147
148 for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
149 cIter = pcmk__xml_next(cIter)) {
150 reset_xml_node_flags(cIter);
151 }
152 }
153
154 // Set xpf_created flag on XML node and any children
155 void
pcmk__mark_xml_created(xmlNode * xml)156 pcmk__mark_xml_created(xmlNode *xml)
157 {
158 xmlNode *cIter = NULL;
159 xml_private_t *p = xml->_private;
160
161 if (p && pcmk__tracking_xml_changes(xml, FALSE)) {
162 if (!pcmk_is_set(p->flags, xpf_created)) {
163 pcmk__set_xml_flags(p, xpf_created);
164 mark_xml_node_dirty(xml);
165 }
166 for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
167 cIter = pcmk__xml_next(cIter)) {
168 pcmk__mark_xml_created(cIter);
169 }
170 }
171 }
172
173 void
pcmk__mark_xml_attr_dirty(xmlAttr * a)174 pcmk__mark_xml_attr_dirty(xmlAttr *a)
175 {
176 xmlNode *parent = a->parent;
177 xml_private_t *p = NULL;
178
179 p = a->_private;
180 pcmk__set_xml_flags(p, xpf_dirty|xpf_modified);
181 pcmk__clear_xml_flags(p, xpf_deleted);
182 mark_xml_node_dirty(parent);
183 }
184
185 #define XML_PRIVATE_MAGIC (long) 0x81726354
186
187 // Free an XML object previously marked as deleted
188 static void
free_deleted_object(void * data)189 free_deleted_object(void *data)
190 {
191 if(data) {
192 pcmk__deleted_xml_t *deleted_obj = data;
193
194 free(deleted_obj->path);
195 free(deleted_obj);
196 }
197 }
198
199 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
200 static void
reset_xml_private_data(xml_private_t * p)201 reset_xml_private_data(xml_private_t *p)
202 {
203 if(p) {
204 CRM_ASSERT(p->check == XML_PRIVATE_MAGIC);
205
206 free(p->user);
207 p->user = NULL;
208
209 if(p->acls) {
210 pcmk__free_acls(p->acls);
211 p->acls = NULL;
212 }
213
214 if(p->deleted_objs) {
215 g_list_free_full(p->deleted_objs, free_deleted_object);
216 p->deleted_objs = NULL;
217 }
218 }
219 }
220
221 // Free all private data associated with an XML node
222 static void
free_private_data(xmlNode * node)223 free_private_data(xmlNode *node)
224 {
225 /* need to explicitly avoid our custom _private field cleanup when
226 called from internal XSLT cleanup (xsltApplyStylesheetInternal
227 -> xsltFreeTransformContext -> xsltFreeRVTs -> xmlFreeDoc)
228 onto result tree fragments, represented as standalone documents
229 with otherwise infeasible space-prefixed name (xsltInternals.h:
230 XSLT_MARK_RES_TREE_FRAG) and carrying it's own load at _private
231 field -- later assert on the XML_PRIVATE_MAGIC would explode */
232 if (node->type != XML_DOCUMENT_NODE || node->name == NULL
233 || node->name[0] != ' ') {
234 reset_xml_private_data(node->_private);
235 free(node->_private);
236 }
237 }
238
239 // Allocate and initialize private data for an XML node
240 static void
new_private_data(xmlNode * node)241 new_private_data(xmlNode *node)
242 {
243 xml_private_t *p = NULL;
244
245 switch(node->type) {
246 case XML_ELEMENT_NODE:
247 case XML_DOCUMENT_NODE:
248 case XML_ATTRIBUTE_NODE:
249 case XML_COMMENT_NODE:
250 p = calloc(1, sizeof(xml_private_t));
251 p->check = XML_PRIVATE_MAGIC;
252 /* Flags will be reset if necessary when tracking is enabled */
253 pcmk__set_xml_flags(p, xpf_dirty|xpf_created);
254 node->_private = p;
255 break;
256 case XML_TEXT_NODE:
257 case XML_DTD_NODE:
258 case XML_CDATA_SECTION_NODE:
259 break;
260 default:
261 /* Ignore */
262 crm_trace("Ignoring %p %d", node, node->type);
263 CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
264 break;
265 }
266
267 if(p && pcmk__tracking_xml_changes(node, FALSE)) {
268 /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
269 * not hooked up at the point we are called
270 */
271 mark_xml_node_dirty(node);
272 }
273 }
274
275 void
xml_track_changes(xmlNode * xml,const char * user,xmlNode * acl_source,bool enforce_acls)276 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
277 {
278 xml_accept_changes(xml);
279 crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
280 pcmk__set_xml_doc_flag(xml, xpf_tracking);
281 if(enforce_acls) {
282 if(acl_source == NULL) {
283 acl_source = xml;
284 }
285 pcmk__set_xml_doc_flag(xml, xpf_acl_enabled);
286 pcmk__unpack_acl(acl_source, xml, user);
287 pcmk__apply_acl(xml);
288 }
289 }
290
xml_tracking_changes(xmlNode * xml)291 bool xml_tracking_changes(xmlNode * xml)
292 {
293 return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
294 && pcmk_is_set(((xml_private_t *)(xml->doc->_private))->flags,
295 xpf_tracking);
296 }
297
xml_document_dirty(xmlNode * xml)298 bool xml_document_dirty(xmlNode *xml)
299 {
300 return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
301 && pcmk_is_set(((xml_private_t *)(xml->doc->_private))->flags,
302 xpf_dirty);
303 }
304
305 /*!
306 * \internal
307 * \brief Return ordinal position of an XML node among its siblings
308 *
309 * \param[in] xml XML node to check
310 * \param[in] ignore_if_set Don't count siblings with this flag set
311 *
312 * \return Ordinal position of \p xml (starting with 0)
313 */
314 int
pcmk__xml_position(xmlNode * xml,enum xml_private_flags ignore_if_set)315 pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set)
316 {
317 int position = 0;
318 xmlNode *cIter = NULL;
319
320 for(cIter = xml; cIter->prev; cIter = cIter->prev) {
321 xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
322
323 if (!pcmk_is_set(p->flags, ignore_if_set)) {
324 position++;
325 }
326 }
327
328 return position;
329 }
330
331 // This also clears attribute's flags if not marked as deleted
332 static bool
marked_as_deleted(xmlAttrPtr a,void * user_data)333 marked_as_deleted(xmlAttrPtr a, void *user_data)
334 {
335 xml_private_t *p = a->_private;
336
337 if (pcmk_is_set(p->flags, xpf_deleted)) {
338 return true;
339 }
340 p->flags = xpf_none;
341 return false;
342 }
343
344 // Remove all attributes marked as deleted from an XML node
345 static void
accept_attr_deletions(xmlNode * xml)346 accept_attr_deletions(xmlNode *xml)
347 {
348 // Clear XML node's flags
349 ((xml_private_t *) xml->_private)->flags = xpf_none;
350
351 // Remove this XML node's attributes that were marked as deleted
352 pcmk__xe_remove_matching_attrs(xml, marked_as_deleted, NULL);
353
354 // Recursively do the same for this XML node's children
355 for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL;
356 cIter = pcmk__xml_next(cIter)) {
357 accept_attr_deletions(cIter);
358 }
359 }
360
361 /*!
362 * \internal
363 * \brief Find first child XML node matching another given XML node
364 *
365 * \param[in] haystack XML whose children should be checked
366 * \param[in] needle XML to match (comment content or element name and ID)
367 * \param[in] exact If true and needle is a comment, position must match
368 */
369 xmlNode *
pcmk__xml_match(xmlNode * haystack,xmlNode * needle,bool exact)370 pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact)
371 {
372 CRM_CHECK(needle != NULL, return NULL);
373
374 if (needle->type == XML_COMMENT_NODE) {
375 return pcmk__xc_match(haystack, needle, exact);
376
377 } else {
378 const char *id = ID(needle);
379 const char *attr = (id == NULL)? NULL : XML_ATTR_ID;
380
381 return pcmk__xe_match(haystack, crm_element_name(needle), attr, id);
382 }
383 }
384
385 void
xml_log_changes(uint8_t log_level,const char * function,xmlNode * xml)386 xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
387 {
388 GList *gIter = NULL;
389 xml_private_t *doc = NULL;
390
391 if (log_level == LOG_NEVER) {
392 return;
393 }
394
395 CRM_ASSERT(xml);
396 CRM_ASSERT(xml->doc);
397
398 doc = xml->doc->_private;
399 if (!pcmk_is_set(doc->flags, xpf_dirty)) {
400 return;
401 }
402
403 for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
404 pcmk__deleted_xml_t *deleted_obj = gIter->data;
405
406 if (deleted_obj->position >= 0) {
407 do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)",
408 deleted_obj->path, deleted_obj->position);
409
410 } else {
411 do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s",
412 deleted_obj->path);
413 }
414 }
415
416 log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
417 xml_log_option_formatted|xml_log_option_dirty_add);
418 }
419
420 void
xml_accept_changes(xmlNode * xml)421 xml_accept_changes(xmlNode * xml)
422 {
423 xmlNode *top = NULL;
424 xml_private_t *doc = NULL;
425
426 if(xml == NULL) {
427 return;
428 }
429
430 crm_trace("Accepting changes to %p", xml);
431 doc = xml->doc->_private;
432 top = xmlDocGetRootElement(xml->doc);
433
434 reset_xml_private_data(xml->doc->_private);
435
436 if (!pcmk_is_set(doc->flags, xpf_dirty)) {
437 doc->flags = xpf_none;
438 return;
439 }
440
441 doc->flags = xpf_none;
442 accept_attr_deletions(top);
443 }
444
445 xmlNode *
find_xml_node(xmlNode * root,const char * search_path,gboolean must_find)446 find_xml_node(xmlNode * root, const char *search_path, gboolean must_find)
447 {
448 xmlNode *a_child = NULL;
449 const char *name = "NULL";
450
451 if (root != NULL) {
452 name = crm_element_name(root);
453 }
454
455 if (search_path == NULL) {
456 crm_warn("Will never find <NULL>");
457 return NULL;
458 }
459
460 for (a_child = pcmk__xml_first_child(root); a_child != NULL;
461 a_child = pcmk__xml_next(a_child)) {
462 if (strcmp((const char *)a_child->name, search_path) == 0) {
463 /* crm_trace("returning node (%s).", crm_element_name(a_child)); */
464 return a_child;
465 }
466 }
467
468 if (must_find) {
469 crm_warn("Could not find %s in %s.", search_path, name);
470 } else if (root != NULL) {
471 crm_trace("Could not find %s in %s.", search_path, name);
472 } else {
473 crm_trace("Could not find %s in <NULL>.", search_path);
474 }
475
476 return NULL;
477 }
478
479 #define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \
480 (v), pcmk__str_none)
481
482 /*!
483 * \internal
484 * \brief Find first XML child element matching given criteria
485 *
486 * \param[in] parent XML element to search
487 * \param[in] node_name If not NULL, only match children of this type
488 * \param[in] attr_n If not NULL, only match children with an attribute
489 * of this name and a value of \p attr_v
490 * \param[in] attr_v If \p attr_n and this are not NULL, only match children
491 * with an attribute named \p attr_n and this value
492 *
493 * \return Matching XML child element, or NULL if none found
494 */
495 xmlNode *
pcmk__xe_match(xmlNode * parent,const char * node_name,const char * attr_n,const char * attr_v)496 pcmk__xe_match(xmlNode *parent, const char *node_name,
497 const char *attr_n, const char *attr_v)
498 {
499 /* ensure attr_v specified when attr_n is */
500 CRM_CHECK(attr_n == NULL || attr_v != NULL, return NULL);
501
502 for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
503 child = pcmk__xml_next(child)) {
504 if (pcmk__str_eq(node_name, (const char *) (child->name),
505 pcmk__str_null_matches)
506 && ((attr_n == NULL) || attr_matches(child, attr_n, attr_v))) {
507 return child;
508 }
509 }
510 crm_trace("XML child node <%s%s%s%s%s> not found in %s",
511 (node_name? node_name : "(any)"),
512 (attr_n? " " : ""),
513 (attr_n? attr_n : ""),
514 (attr_n? "=" : ""),
515 (attr_n? attr_v : ""),
516 crm_element_name(parent));
517 return NULL;
518 }
519
520 void
copy_in_properties(xmlNode * target,xmlNode * src)521 copy_in_properties(xmlNode * target, xmlNode * src)
522 {
523 if (src == NULL) {
524 crm_warn("No node to copy properties from");
525
526 } else if (target == NULL) {
527 crm_err("No node to copy properties into");
528
529 } else {
530 for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
531 const char *p_name = (const char *) a->name;
532 const char *p_value = pcmk__xml_attr_value(a);
533
534 expand_plus_plus(target, p_name, p_value);
535 }
536 }
537
538 return;
539 }
540
541 void
fix_plus_plus_recursive(xmlNode * target)542 fix_plus_plus_recursive(xmlNode * target)
543 {
544 /* TODO: Remove recursion and use xpath searches for value++ */
545 xmlNode *child = NULL;
546
547 for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
548 const char *p_name = (const char *) a->name;
549 const char *p_value = pcmk__xml_attr_value(a);
550
551 expand_plus_plus(target, p_name, p_value);
552 }
553 for (child = pcmk__xml_first_child(target); child != NULL;
554 child = pcmk__xml_next(child)) {
555 fix_plus_plus_recursive(child);
556 }
557 }
558
559 void
expand_plus_plus(xmlNode * target,const char * name,const char * value)560 expand_plus_plus(xmlNode * target, const char *name, const char *value)
561 {
562 int offset = 1;
563 int name_len = 0;
564 int int_value = 0;
565 int value_len = 0;
566
567 const char *old_value = NULL;
568
569 if (value == NULL || name == NULL) {
570 return;
571 }
572
573 old_value = crm_element_value(target, name);
574
575 if (old_value == NULL) {
576 /* if no previous value, set unexpanded */
577 goto set_unexpanded;
578
579 } else if (strstr(value, name) != value) {
580 goto set_unexpanded;
581 }
582
583 name_len = strlen(name);
584 value_len = strlen(value);
585 if (value_len < (name_len + 2)
586 || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
587 goto set_unexpanded;
588 }
589
590 /* if we are expanding ourselves,
591 * then no previous value was set and leave int_value as 0
592 */
593 if (old_value != value) {
594 int_value = char2score(old_value);
595 }
596
597 if (value[name_len + 1] != '+') {
598 const char *offset_s = value + (name_len + 2);
599
600 offset = char2score(offset_s);
601 }
602 int_value += offset;
603
604 if (int_value > INFINITY) {
605 int_value = (int)INFINITY;
606 }
607
608 crm_xml_add_int(target, name, int_value);
609 return;
610
611 set_unexpanded:
612 if (old_value == value) {
613 /* the old value is already set, nothing to do */
614 return;
615 }
616 crm_xml_add(target, name, value);
617 return;
618 }
619
620 /*!
621 * \internal
622 * \brief Remove an XML element's attributes that match some criteria
623 *
624 * \param[in,out] element XML element to modify
625 * \param[in] match If not NULL, only remove attributes for which
626 * this function returns true
627 * \param[in] user_data Data to pass to \p match
628 */
629 void
pcmk__xe_remove_matching_attrs(xmlNode * element,bool (* match)(xmlAttrPtr,void *),void * user_data)630 pcmk__xe_remove_matching_attrs(xmlNode *element,
631 bool (*match)(xmlAttrPtr, void *),
632 void *user_data)
633 {
634 xmlAttrPtr next = NULL;
635
636 for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
637 next = a->next; // Grab now because attribute might get removed
638 if ((match == NULL) || match(a, user_data)) {
639 if (!pcmk__check_acl(element, NULL, xpf_acl_write)) {
640 crm_trace("ACLs prevent removal of attributes (%s and "
641 "possibly others) from %s element",
642 (const char *) a->name, (const char *) element->name);
643 return; // ACLs apply to element, not particular attributes
644 }
645
646 if (pcmk__tracking_xml_changes(element, false)) {
647 // Leave (marked for removal) until after diff is calculated
648 set_parent_flag(element, xpf_dirty);
649 pcmk__set_xml_flags((xml_private_t *) a->_private, xpf_deleted);
650 } else {
651 xmlRemoveProp(a);
652 }
653 }
654 }
655 }
656
657 xmlDoc *
getDocPtr(xmlNode * node)658 getDocPtr(xmlNode * node)
659 {
660 xmlDoc *doc = NULL;
661
662 CRM_CHECK(node != NULL, return NULL);
663
664 doc = node->doc;
665 if (doc == NULL) {
666 doc = xmlNewDoc((pcmkXmlStr) "1.0");
667 xmlDocSetRootElement(doc, node);
668 xmlSetTreeDoc(node, doc);
669 }
670 return doc;
671 }
672
673 xmlNode *
add_node_copy(xmlNode * parent,xmlNode * src_node)674 add_node_copy(xmlNode * parent, xmlNode * src_node)
675 {
676 xmlNode *child = NULL;
677 xmlDoc *doc = getDocPtr(parent);
678
679 CRM_CHECK(src_node != NULL, return NULL);
680
681 child = xmlDocCopyNode(src_node, doc, 1);
682 xmlAddChild(parent, child);
683 pcmk__mark_xml_created(child);
684 return child;
685 }
686
687 int
add_node_nocopy(xmlNode * parent,const char * name,xmlNode * child)688 add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
689 {
690 add_node_copy(parent, child);
691 free_xml(child);
692 return 1;
693 }
694
695 xmlNode *
create_xml_node(xmlNode * parent,const char * name)696 create_xml_node(xmlNode * parent, const char *name)
697 {
698 xmlDoc *doc = NULL;
699 xmlNode *node = NULL;
700
701 if (pcmk__str_empty(name)) {
702 CRM_CHECK(name != NULL && name[0] == 0, return NULL);
703 return NULL;
704 }
705
706 if (parent == NULL) {
707 doc = xmlNewDoc((pcmkXmlStr) "1.0");
708 node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
709 xmlDocSetRootElement(doc, node);
710
711 } else {
712 doc = getDocPtr(parent);
713 node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
714 xmlAddChild(parent, node);
715 }
716 pcmk__mark_xml_created(node);
717 return node;
718 }
719
720 xmlNode *
pcmk_create_xml_text_node(xmlNode * parent,const char * name,const char * content)721 pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
722 {
723 xmlNode *node = create_xml_node(parent, name);
724
725 if (node != NULL) {
726 xmlNodeSetContent(node, (pcmkXmlStr) content);
727 }
728
729 return node;
730 }
731
732 xmlNode *
pcmk_create_html_node(xmlNode * parent,const char * element_name,const char * id,const char * class_name,const char * text)733 pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
734 const char *class_name, const char *text)
735 {
736 xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
737
738 if (class_name != NULL) {
739 crm_xml_add(node, "class", class_name);
740 }
741
742 if (id != NULL) {
743 crm_xml_add(node, "id", id);
744 }
745
746 return node;
747 }
748
749 /*!
750 * Free an XML element and all of its children, removing it from its parent
751 *
752 * \param[in] xml XML element to free
753 */
754 void
pcmk_free_xml_subtree(xmlNode * xml)755 pcmk_free_xml_subtree(xmlNode *xml)
756 {
757 xmlUnlinkNode(xml); // Detaches from parent and siblings
758 xmlFreeNode(xml); // Frees
759 }
760
761 static void
free_xml_with_position(xmlNode * child,int position)762 free_xml_with_position(xmlNode * child, int position)
763 {
764 if (child != NULL) {
765 xmlNode *top = NULL;
766 xmlDoc *doc = child->doc;
767 xml_private_t *p = child->_private;
768
769 if (doc != NULL) {
770 top = xmlDocGetRootElement(doc);
771 }
772
773 if (doc != NULL && top == child) {
774 /* Free everything */
775 xmlFreeDoc(doc);
776
777 } else if (pcmk__check_acl(child, NULL, xpf_acl_write) == FALSE) {
778 int offset = 0;
779 char buffer[PCMK__BUFFER_SIZE];
780
781 pcmk__element_xpath(NULL, child, buffer, offset, sizeof(buffer));
782 crm_trace("Cannot remove %s %x", buffer, p->flags);
783 return;
784
785 } else {
786 if (doc && pcmk__tracking_xml_changes(child, FALSE)
787 && !pcmk_is_set(p->flags, xpf_created)) {
788 int offset = 0;
789 char buffer[PCMK__BUFFER_SIZE];
790
791 if (pcmk__element_xpath(NULL, child, buffer, offset,
792 sizeof(buffer)) > 0) {
793 pcmk__deleted_xml_t *deleted_obj = NULL;
794
795 crm_trace("Deleting %s %p from %p", buffer, child, doc);
796
797 deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t));
798 deleted_obj->path = strdup(buffer);
799
800 deleted_obj->position = -1;
801 /* Record the "position" only for XML comments for now */
802 if (child->type == XML_COMMENT_NODE) {
803 if (position >= 0) {
804 deleted_obj->position = position;
805
806 } else {
807 deleted_obj->position = pcmk__xml_position(child, xpf_skip);
808 }
809 }
810
811 p = doc->_private;
812 p->deleted_objs = g_list_append(p->deleted_objs, deleted_obj);
813 pcmk__set_xml_doc_flag(child, xpf_dirty);
814 }
815 }
816 pcmk_free_xml_subtree(child);
817 }
818 }
819 }
820
821
822 void
free_xml(xmlNode * child)823 free_xml(xmlNode * child)
824 {
825 free_xml_with_position(child, -1);
826 }
827
828 xmlNode *
copy_xml(xmlNode * src)829 copy_xml(xmlNode * src)
830 {
831 xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
832 xmlNode *copy = xmlDocCopyNode(src, doc, 1);
833
834 xmlDocSetRootElement(doc, copy);
835 xmlSetTreeDoc(copy, doc);
836 return copy;
837 }
838
839 static void
840 log_xmllib_err(void *ctx, const char *fmt, ...)
841 G_GNUC_PRINTF(2, 3);
842
843 // Log an XML library error
844 static void
log_xmllib_err(void * ctx,const char * fmt,...)845 log_xmllib_err(void *ctx, const char *fmt, ...)
846 {
847 va_list ap;
848 static struct qb_log_callsite *xml_error_cs = NULL;
849
850 if (xml_error_cs == NULL) {
851 xml_error_cs = qb_log_callsite_get(
852 __func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
853 }
854
855 va_start(ap, fmt);
856 if (xml_error_cs && xml_error_cs->targets) {
857 PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
858 crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error",
859 TRUE, TRUE),
860 "XML Error: ", fmt, ap);
861 } else {
862 PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
863 }
864 va_end(ap);
865 }
866
867 xmlNode *
string2xml(const char * input)868 string2xml(const char *input)
869 {
870 xmlNode *xml = NULL;
871 xmlDocPtr output = NULL;
872 xmlParserCtxtPtr ctxt = NULL;
873 xmlErrorPtr last_error = NULL;
874
875 if (input == NULL) {
876 crm_err("Can't parse NULL input");
877 return NULL;
878 }
879
880 /* create a parser context */
881 ctxt = xmlNewParserCtxt();
882 CRM_CHECK(ctxt != NULL, return NULL);
883
884 xmlCtxtResetLastError(ctxt);
885 xmlSetGenericErrorFunc(ctxt, log_xmllib_err);
886 output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
887 PCMK__XML_PARSE_OPTS);
888 if (output) {
889 xml = xmlDocGetRootElement(output);
890 }
891 last_error = xmlCtxtGetLastError(ctxt);
892 if (last_error && last_error->code != XML_ERR_OK) {
893 /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
894 /*
895 * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
896 * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
897 */
898 crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
899 last_error->domain, last_error->level, last_error->code, last_error->message);
900
901 if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
902 CRM_LOG_ASSERT("Cannot parse an empty string");
903
904 } else if (last_error->code != XML_ERR_DOCUMENT_END) {
905 crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
906 input);
907 if (xml != NULL) {
908 crm_log_xml_err(xml, "Partial");
909 }
910
911 } else {
912 int len = strlen(input);
913 int lpc = 0;
914
915 while(lpc < len) {
916 crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
917 lpc += 80;
918 }
919
920 CRM_LOG_ASSERT("String parsing error");
921 }
922 }
923
924 xmlFreeParserCtxt(ctxt);
925 return xml;
926 }
927
928 xmlNode *
stdin2xml(void)929 stdin2xml(void)
930 {
931 size_t data_length = 0;
932 size_t read_chars = 0;
933
934 char *xml_buffer = NULL;
935 xmlNode *xml_obj = NULL;
936
937 do {
938 xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE);
939 read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE,
940 stdin);
941 data_length += read_chars;
942 } while (read_chars == PCMK__BUFFER_SIZE);
943
944 if (data_length == 0) {
945 crm_warn("No XML supplied on stdin");
946 free(xml_buffer);
947 return NULL;
948 }
949
950 xml_buffer[data_length] = '\0';
951 xml_obj = string2xml(xml_buffer);
952 free(xml_buffer);
953
954 crm_log_xml_trace(xml_obj, "Created fragment");
955 return xml_obj;
956 }
957
958 static char *
decompress_file(const char * filename)959 decompress_file(const char *filename)
960 {
961 char *buffer = NULL;
962 int rc = 0;
963 size_t length = 0, read_len = 0;
964 BZFILE *bz_file = NULL;
965 FILE *input = fopen(filename, "r");
966
967 if (input == NULL) {
968 crm_perror(LOG_ERR, "Could not open %s for reading", filename);
969 return NULL;
970 }
971
972 bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
973 if (rc != BZ_OK) {
974 crm_err("Could not prepare to read compressed %s: %s "
975 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
976 BZ2_bzReadClose(&rc, bz_file);
977 return NULL;
978 }
979
980 rc = BZ_OK;
981 // cppcheck seems not to understand the abort-logic in pcmk__realloc
982 // cppcheck-suppress memleak
983 while (rc == BZ_OK) {
984 buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
985 read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
986
987 crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
988
989 if (rc == BZ_OK || rc == BZ_STREAM_END) {
990 length += read_len;
991 }
992 }
993
994 buffer[length] = '\0';
995
996 if (rc != BZ_STREAM_END) {
997 crm_err("Could not read compressed %s: %s "
998 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
999 free(buffer);
1000 buffer = NULL;
1001 }
1002
1003 BZ2_bzReadClose(&rc, bz_file);
1004 fclose(input);
1005 return buffer;
1006 }
1007
1008 /*!
1009 * \internal
1010 * \brief Remove XML text nodes from specified XML and all its children
1011 *
1012 * \param[in,out] xml XML to strip text from
1013 */
1014 void
pcmk__strip_xml_text(xmlNode * xml)1015 pcmk__strip_xml_text(xmlNode *xml)
1016 {
1017 xmlNode *iter = xml->children;
1018
1019 while (iter) {
1020 xmlNode *next = iter->next;
1021
1022 switch (iter->type) {
1023 case XML_TEXT_NODE:
1024 /* Remove it */
1025 pcmk_free_xml_subtree(iter);
1026 break;
1027
1028 case XML_ELEMENT_NODE:
1029 /* Search it */
1030 pcmk__strip_xml_text(iter);
1031 break;
1032
1033 default:
1034 /* Leave it */
1035 break;
1036 }
1037
1038 iter = next;
1039 }
1040 }
1041
1042 xmlNode *
filename2xml(const char * filename)1043 filename2xml(const char *filename)
1044 {
1045 xmlNode *xml = NULL;
1046 xmlDocPtr output = NULL;
1047 gboolean uncompressed = TRUE;
1048 xmlParserCtxtPtr ctxt = NULL;
1049 xmlErrorPtr last_error = NULL;
1050
1051 /* create a parser context */
1052 ctxt = xmlNewParserCtxt();
1053 CRM_CHECK(ctxt != NULL, return NULL);
1054
1055 xmlCtxtResetLastError(ctxt);
1056 xmlSetGenericErrorFunc(ctxt, log_xmllib_err);
1057
1058 if (filename) {
1059 uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
1060 }
1061
1062 if (filename == NULL || !strcmp(filename, "-")) {
1063 /* STDIN_FILENO == fileno(stdin) */
1064 output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
1065 PCMK__XML_PARSE_OPTS);
1066
1067 } else if (uncompressed) {
1068 output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS);
1069
1070 } else {
1071 char *input = decompress_file(filename);
1072
1073 output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
1074 PCMK__XML_PARSE_OPTS);
1075 free(input);
1076 }
1077
1078 if (output && (xml = xmlDocGetRootElement(output))) {
1079 pcmk__strip_xml_text(xml);
1080 }
1081
1082 last_error = xmlCtxtGetLastError(ctxt);
1083 if (last_error && last_error->code != XML_ERR_OK) {
1084 /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
1085 /*
1086 * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
1087 * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
1088 */
1089 crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
1090 last_error->domain, last_error->level, last_error->code, last_error->message);
1091
1092 if (last_error && last_error->code != XML_ERR_OK) {
1093 crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
1094 if (xml != NULL) {
1095 crm_log_xml_err(xml, "Partial");
1096 }
1097 }
1098 }
1099
1100 xmlFreeParserCtxt(ctxt);
1101 return xml;
1102 }
1103
1104 /*!
1105 * \internal
1106 * \brief Add a "last written" attribute to an XML element, set to current time
1107 *
1108 * \param[in] xe XML element to add attribute to
1109 *
1110 * \return Value that was set, or NULL on error
1111 */
1112 const char *
pcmk__xe_add_last_written(xmlNode * xe)1113 pcmk__xe_add_last_written(xmlNode *xe)
1114 {
1115 const char *now_str = pcmk__epoch2str(NULL);
1116
1117 return crm_xml_add(xe, XML_CIB_ATTR_WRITTEN,
1118 now_str ? now_str : "Could not determine current time");
1119 }
1120
1121 /*!
1122 * \brief Sanitize a string so it is usable as an XML ID
1123 *
1124 * \param[in,out] id String to sanitize
1125 */
1126 void
crm_xml_sanitize_id(char * id)1127 crm_xml_sanitize_id(char *id)
1128 {
1129 char *c;
1130
1131 for (c = id; *c; ++c) {
1132 /* @TODO Sanitize more comprehensively */
1133 switch (*c) {
1134 case ':':
1135 case '#':
1136 *c = '.';
1137 }
1138 }
1139 }
1140
1141 /*!
1142 * \brief Set the ID of an XML element using a format
1143 *
1144 * \param[in,out] xml XML element
1145 * \param[in] fmt printf-style format
1146 * \param[in] ... any arguments required by format
1147 */
1148 void
crm_xml_set_id(xmlNode * xml,const char * format,...)1149 crm_xml_set_id(xmlNode *xml, const char *format, ...)
1150 {
1151 va_list ap;
1152 int len = 0;
1153 char *id = NULL;
1154
1155 /* equivalent to crm_strdup_printf() */
1156 va_start(ap, format);
1157 len = vasprintf(&id, format, ap);
1158 va_end(ap);
1159 CRM_ASSERT(len > 0);
1160
1161 crm_xml_sanitize_id(id);
1162 crm_xml_add(xml, XML_ATTR_ID, id);
1163 free(id);
1164 }
1165
1166 /*!
1167 * \internal
1168 * \brief Write XML to a file stream
1169 *
1170 * \param[in] xml_node XML to write
1171 * \param[in] filename Name of file being written (for logging only)
1172 * \param[in] stream Open file stream corresponding to filename
1173 * \param[in] compress Whether to compress XML before writing
1174 * \param[out] nbytes Number of bytes written
1175 *
1176 * \return Standard Pacemaker return code
1177 */
1178 static int
write_xml_stream(xmlNode * xml_node,const char * filename,FILE * stream,bool compress,unsigned int * nbytes)1179 write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream,
1180 bool compress, unsigned int *nbytes)
1181 {
1182 int rc = pcmk_rc_ok;
1183 char *buffer = NULL;
1184
1185 *nbytes = 0;
1186 crm_log_xml_trace(xml_node, "writing");
1187
1188 buffer = dump_xml_formatted(xml_node);
1189 CRM_CHECK(buffer && strlen(buffer),
1190 crm_log_xml_warn(xml_node, "formatting failed");
1191 rc = pcmk_rc_error;
1192 goto bail);
1193
1194 if (compress) {
1195 unsigned int in = 0;
1196 BZFILE *bz_file = NULL;
1197
1198 rc = BZ_OK;
1199 bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
1200 if (rc != BZ_OK) {
1201 crm_warn("Not compressing %s: could not prepare file stream: %s "
1202 CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
1203 } else {
1204 BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
1205 if (rc != BZ_OK) {
1206 crm_warn("Not compressing %s: could not compress data: %s "
1207 CRM_XS " bzerror=%d errno=%d",
1208 filename, bz2_strerror(rc), rc, errno);
1209 }
1210 }
1211
1212 if (rc == BZ_OK) {
1213 BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
1214 if (rc != BZ_OK) {
1215 crm_warn("Not compressing %s: could not write compressed data: %s "
1216 CRM_XS " bzerror=%d errno=%d",
1217 filename, bz2_strerror(rc), rc, errno);
1218 *nbytes = 0; // retry without compression
1219 } else {
1220 crm_trace("Compressed XML for %s from %u bytes to %u",
1221 filename, in, *nbytes);
1222 }
1223 }
1224 rc = pcmk_rc_ok; // Either true, or we'll retry without compression
1225 }
1226
1227 if (*nbytes == 0) {
1228 rc = fprintf(stream, "%s", buffer);
1229 if (rc < 0) {
1230 rc = errno;
1231 crm_perror(LOG_ERR, "writing %s", filename);
1232 } else {
1233 *nbytes = (unsigned int) rc;
1234 rc = pcmk_rc_ok;
1235 }
1236 }
1237
1238 bail:
1239
1240 if (fflush(stream) != 0) {
1241 rc = errno;
1242 crm_perror(LOG_ERR, "flushing %s", filename);
1243 }
1244
1245 /* Don't report error if the file does not support synchronization */
1246 if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) {
1247 rc = errno;
1248 crm_perror(LOG_ERR, "synchronizing %s", filename);
1249 }
1250
1251 fclose(stream);
1252
1253 crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
1254 free(buffer);
1255
1256 return rc;
1257 }
1258
1259 /*!
1260 * \brief Write XML to a file descriptor
1261 *
1262 * \param[in] xml_node XML to write
1263 * \param[in] filename Name of file being written (for logging only)
1264 * \param[in] fd Open file descriptor corresponding to filename
1265 * \param[in] compress Whether to compress XML before writing
1266 *
1267 * \return Number of bytes written on success, -errno otherwise
1268 */
1269 int
write_xml_fd(xmlNode * xml_node,const char * filename,int fd,gboolean compress)1270 write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
1271 {
1272 FILE *stream = NULL;
1273 unsigned int nbytes = 0;
1274 int rc = pcmk_rc_ok;
1275
1276 CRM_CHECK(xml_node && (fd > 0), return -EINVAL);
1277 stream = fdopen(fd, "w");
1278 if (stream == NULL) {
1279 return -errno;
1280 }
1281 rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1282 if (rc != pcmk_rc_ok) {
1283 return pcmk_rc2legacy(rc);
1284 }
1285 return (int) nbytes;
1286 }
1287
1288 /*!
1289 * \brief Write XML to a file
1290 *
1291 * \param[in] xml_node XML to write
1292 * \param[in] filename Name of file to write
1293 * \param[in] compress Whether to compress XML before writing
1294 *
1295 * \return Number of bytes written on success, -errno otherwise
1296 */
1297 int
write_xml_file(xmlNode * xml_node,const char * filename,gboolean compress)1298 write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
1299 {
1300 FILE *stream = NULL;
1301 unsigned int nbytes = 0;
1302 int rc = pcmk_rc_ok;
1303
1304 CRM_CHECK(xml_node && filename, return -EINVAL);
1305 stream = fopen(filename, "w");
1306 if (stream == NULL) {
1307 return -errno;
1308 }
1309 rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes);
1310 if (rc != pcmk_rc_ok) {
1311 return pcmk_rc2legacy(rc);
1312 }
1313 return (int) nbytes;
1314 }
1315
1316 // Replace a portion of a dynamically allocated string (reallocating memory)
1317 static char *
replace_text(char * text,int start,int * length,const char * replace)1318 replace_text(char *text, int start, int *length, const char *replace)
1319 {
1320 int lpc;
1321 int offset = strlen(replace) - 1; /* We have space for 1 char already */
1322
1323 *length += offset;
1324 text = pcmk__realloc(text, *length);
1325
1326 for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
1327 text[lpc] = text[lpc - offset];
1328 }
1329
1330 memcpy(text + start, replace, offset + 1);
1331 return text;
1332 }
1333
1334 char *
crm_xml_escape(const char * text)1335 crm_xml_escape(const char *text)
1336 {
1337 int index;
1338 int changes = 0;
1339 int length = 1 + strlen(text);
1340 char *copy = strdup(text);
1341
1342 /*
1343 * When xmlCtxtReadDoc() parses < and friends in a
1344 * value, it converts them to their human readable
1345 * form.
1346 *
1347 * If one uses xmlNodeDump() to convert it back to a
1348 * string, all is well, because special characters are
1349 * converted back to their escape sequences.
1350 *
1351 * However xmlNodeDump() is randomly dog slow, even with the same
1352 * input. So we need to replicate the escaping in our custom
1353 * version so that the result can be re-parsed by xmlCtxtReadDoc()
1354 * when necessary.
1355 */
1356
1357 for (index = 0; index < length; index++) {
1358 switch (copy[index]) {
1359 case 0:
1360 break;
1361 case '<':
1362 copy = replace_text(copy, index, &length, "<");
1363 changes++;
1364 break;
1365 case '>':
1366 copy = replace_text(copy, index, &length, ">");
1367 changes++;
1368 break;
1369 case '"':
1370 copy = replace_text(copy, index, &length, """);
1371 changes++;
1372 break;
1373 case '\'':
1374 copy = replace_text(copy, index, &length, "'");
1375 changes++;
1376 break;
1377 case '&':
1378 copy = replace_text(copy, index, &length, "&");
1379 changes++;
1380 break;
1381 case '\t':
1382 /* Might as well just expand to a few spaces... */
1383 copy = replace_text(copy, index, &length, " ");
1384 changes++;
1385 break;
1386 case '\n':
1387 /* crm_trace("Convert: \\%.3o", copy[index]); */
1388 copy = replace_text(copy, index, &length, "\\n");
1389 changes++;
1390 break;
1391 case '\r':
1392 copy = replace_text(copy, index, &length, "\\r");
1393 changes++;
1394 break;
1395 /* For debugging...
1396 case '\\':
1397 crm_trace("Passthrough: \\%c", copy[index+1]);
1398 break;
1399 */
1400 default:
1401 /* Check for and replace non-printing characters with their octal equivalent */
1402 if(copy[index] < ' ' || copy[index] > '~') {
1403 char *replace = crm_strdup_printf("\\%.3o", copy[index]);
1404
1405 /* crm_trace("Convert to octal: \\%.3o", copy[index]); */
1406 copy = replace_text(copy, index, &length, replace);
1407 free(replace);
1408 changes++;
1409 }
1410 }
1411 }
1412
1413 if (changes) {
1414 crm_trace("Dumped '%s'", copy);
1415 }
1416 return copy;
1417 }
1418
1419 static inline void
dump_xml_attr(xmlAttrPtr attr,int options,char ** buffer,int * offset,int * max)1420 dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
1421 {
1422 char *p_value = NULL;
1423 const char *p_name = NULL;
1424 xml_private_t *p = NULL;
1425
1426 CRM_ASSERT(buffer != NULL);
1427 if (attr == NULL || attr->children == NULL) {
1428 return;
1429 }
1430
1431 p = attr->_private;
1432 if (p && pcmk_is_set(p->flags, xpf_deleted)) {
1433 return;
1434 }
1435
1436 p_name = (const char *)attr->name;
1437 p_value = crm_xml_escape((const char *)attr->children->content);
1438 buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
1439 free(p_value);
1440 }
1441
1442 // Log an XML element (and any children) in a formatted way
1443 void
pcmk__xe_log(int log_level,const char * file,const char * function,int line,const char * prefix,xmlNode * data,int depth,int options)1444 pcmk__xe_log(int log_level, const char *file, const char *function, int line,
1445 const char *prefix, xmlNode *data, int depth, int options)
1446 {
1447 int max = 0;
1448 int offset = 0;
1449 const char *name = NULL;
1450 const char *hidden = NULL;
1451
1452 xmlNode *child = NULL;
1453
1454 if ((data == NULL) || (log_level == LOG_NEVER)) {
1455 return;
1456 }
1457
1458 name = crm_element_name(data);
1459
1460 if (pcmk_is_set(options, xml_log_option_open)) {
1461 char *buffer = NULL;
1462
1463 insert_prefix(options, &buffer, &offset, &max, depth);
1464
1465 if (data->type == XML_COMMENT_NODE) {
1466 buffer_print(buffer, max, offset, "<!--%s-->", data->content);
1467
1468 } else {
1469 buffer_print(buffer, max, offset, "<%s", name);
1470
1471 hidden = crm_element_value(data, "hidden");
1472 for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL;
1473 a = a->next) {
1474
1475 xml_private_t *p = a->_private;
1476 const char *p_name = (const char *) a->name;
1477 const char *p_value = pcmk__xml_attr_value(a);
1478 char *p_copy = NULL;
1479
1480 if (pcmk_is_set(p->flags, xpf_deleted)) {
1481 continue;
1482 } else if (pcmk_any_flags_set(options,
1483 xml_log_option_diff_plus
1484 |xml_log_option_diff_minus)
1485 && (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
1486 continue;
1487
1488 } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
1489 p_copy = strdup("*****");
1490
1491 } else {
1492 p_copy = crm_xml_escape(p_value);
1493 }
1494
1495 buffer_print(buffer, max, offset, " %s=\"%s\"", p_name, p_copy);
1496 free(p_copy);
1497 }
1498
1499 if(xml_has_children(data) == FALSE) {
1500 buffer_print(buffer, max, offset, "/>");
1501
1502 } else if (pcmk_is_set(options, xml_log_option_children)) {
1503 buffer_print(buffer, max, offset, ">");
1504
1505 } else {
1506 buffer_print(buffer, max, offset, "/>");
1507 }
1508 }
1509
1510 do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
1511 free(buffer);
1512 }
1513
1514 if(data->type == XML_COMMENT_NODE) {
1515 return;
1516
1517 } else if(xml_has_children(data) == FALSE) {
1518 return;
1519
1520 } else if (pcmk_is_set(options, xml_log_option_children)) {
1521 offset = 0;
1522 max = 0;
1523
1524 for (child = pcmk__xml_first_child(data); child != NULL;
1525 child = pcmk__xml_next(child)) {
1526 pcmk__xe_log(log_level, file, function, line, prefix, child,
1527 depth + 1,
1528 options|xml_log_option_open|xml_log_option_close);
1529 }
1530 }
1531
1532 if (pcmk_is_set(options, xml_log_option_close)) {
1533 char *buffer = NULL;
1534
1535 insert_prefix(options, &buffer, &offset, &max, depth);
1536 buffer_print(buffer, max, offset, "</%s>", name);
1537
1538 do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
1539 free(buffer);
1540 }
1541 }
1542
1543 // Log XML portions that have been marked as changed
1544 static void
log_xml_changes(int log_level,const char * file,const char * function,int line,const char * prefix,xmlNode * data,int depth,int options)1545 log_xml_changes(int log_level, const char *file, const char *function, int line,
1546 const char *prefix, xmlNode *data, int depth, int options)
1547 {
1548 xml_private_t *p;
1549 char *prefix_m = NULL;
1550 xmlNode *child = NULL;
1551
1552 if ((data == NULL) || (log_level == LOG_NEVER)) {
1553 return;
1554 }
1555
1556 p = data->_private;
1557
1558 prefix_m = strdup(prefix);
1559 prefix_m[1] = '+';
1560
1561 if (pcmk_all_flags_set(p->flags, xpf_dirty|xpf_created)) {
1562 /* Continue and log full subtree */
1563 pcmk__xe_log(log_level, file, function, line, prefix_m, data, depth,
1564 options|xml_log_option_open|xml_log_option_close
1565 |xml_log_option_children);
1566
1567 } else if (pcmk_is_set(p->flags, xpf_dirty)) {
1568 char *spaces = calloc(80, 1);
1569 int s_count = 0, s_max = 80;
1570 char *prefix_del = NULL;
1571 char *prefix_moved = NULL;
1572 const char *flags = prefix;
1573
1574 insert_prefix(options, &spaces, &s_count, &s_max, depth);
1575 prefix_del = strdup(prefix);
1576 prefix_del[0] = '-';
1577 prefix_del[1] = '-';
1578 prefix_moved = strdup(prefix);
1579 prefix_moved[1] = '~';
1580
1581 if (pcmk_is_set(p->flags, xpf_moved)) {
1582 flags = prefix_moved;
1583 } else {
1584 flags = prefix;
1585 }
1586
1587 pcmk__xe_log(log_level, file, function, line, flags, data, depth,
1588 options|xml_log_option_open);
1589
1590 for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1591 const char *aname = (const char*) a->name;
1592
1593 p = a->_private;
1594 if (pcmk_is_set(p->flags, xpf_deleted)) {
1595 const char *value = crm_element_value(data, aname);
1596 flags = prefix_del;
1597 do_crm_log_alias(log_level, file, function, line,
1598 "%s %s @%s=%s", flags, spaces, aname, value);
1599
1600 } else if (pcmk_is_set(p->flags, xpf_dirty)) {
1601 const char *value = crm_element_value(data, aname);
1602
1603 if (pcmk_is_set(p->flags, xpf_created)) {
1604 flags = prefix_m;
1605
1606 } else if (pcmk_is_set(p->flags, xpf_modified)) {
1607 flags = prefix;
1608
1609 } else if (pcmk_is_set(p->flags, xpf_moved)) {
1610 flags = prefix_moved;
1611
1612 } else {
1613 flags = prefix;
1614 }
1615 do_crm_log_alias(log_level, file, function, line,
1616 "%s %s @%s=%s", flags, spaces, aname, value);
1617 }
1618 }
1619 free(prefix_moved);
1620 free(prefix_del);
1621 free(spaces);
1622
1623 for (child = pcmk__xml_first_child(data); child != NULL;
1624 child = pcmk__xml_next(child)) {
1625 log_xml_changes(log_level, file, function, line, prefix, child,
1626 depth + 1, options);
1627 }
1628
1629 pcmk__xe_log(log_level, file, function, line, prefix, data, depth,
1630 options|xml_log_option_close);
1631
1632 } else {
1633 for (child = pcmk__xml_first_child(data); child != NULL;
1634 child = pcmk__xml_next(child)) {
1635 log_xml_changes(log_level, file, function, line, prefix, child,
1636 depth + 1, options);
1637 }
1638 }
1639
1640 free(prefix_m);
1641
1642 }
1643
1644 void
log_data_element(int log_level,const char * file,const char * function,int line,const char * prefix,xmlNode * data,int depth,int options)1645 log_data_element(int log_level, const char *file, const char *function, int line,
1646 const char *prefix, xmlNode * data, int depth, int options)
1647 {
1648 xmlNode *a_child = NULL;
1649
1650 char *prefix_m = NULL;
1651
1652 if (log_level == LOG_NEVER) {
1653 return;
1654 }
1655
1656 if (prefix == NULL) {
1657 prefix = "";
1658 }
1659
1660 /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
1661 if (data == NULL) {
1662 do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
1663 "No data to dump as XML");
1664 return;
1665 }
1666
1667 if (pcmk_is_set(options, xml_log_option_dirty_add)) {
1668 log_xml_changes(log_level, file, function, line, prefix, data, depth,
1669 options);
1670 return;
1671 }
1672
1673 if (pcmk_is_set(options, xml_log_option_formatted)) {
1674 if (pcmk_is_set(options, xml_log_option_diff_plus)
1675 && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1676 options |= xml_log_option_diff_all;
1677 prefix_m = strdup(prefix);
1678 prefix_m[1] = '+';
1679 prefix = prefix_m;
1680
1681 } else if (pcmk_is_set(options, xml_log_option_diff_minus)
1682 && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
1683 options |= xml_log_option_diff_all;
1684 prefix_m = strdup(prefix);
1685 prefix_m[1] = '-';
1686 prefix = prefix_m;
1687 }
1688 }
1689
1690 if (pcmk_is_set(options, xml_log_option_diff_short)
1691 && !pcmk_is_set(options, xml_log_option_diff_all)) {
1692 /* Still searching for the actual change */
1693 for (a_child = pcmk__xml_first_child(data); a_child != NULL;
1694 a_child = pcmk__xml_next(a_child)) {
1695 log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
1696 }
1697 } else {
1698 pcmk__xe_log(log_level, file, function, line, prefix, data, depth,
1699 options|xml_log_option_open|xml_log_option_close
1700 |xml_log_option_children);
1701 }
1702 free(prefix_m);
1703 }
1704
1705 static void
dump_filtered_xml(xmlNode * data,int options,char ** buffer,int * offset,int * max)1706 dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
1707 {
1708 for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1709 if (!pcmk__xa_filterable((const char *) (a->name))) {
1710 dump_xml_attr(a, options, buffer, offset, max);
1711 }
1712 }
1713 }
1714
1715 static void
dump_xml_element(xmlNode * data,int options,char ** buffer,int * offset,int * max,int depth)1716 dump_xml_element(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
1717 {
1718 const char *name = NULL;
1719
1720 CRM_ASSERT(max != NULL);
1721 CRM_ASSERT(offset != NULL);
1722 CRM_ASSERT(buffer != NULL);
1723
1724 if (data == NULL) {
1725 crm_trace("Nothing to dump");
1726 return;
1727 }
1728
1729 if (*buffer == NULL) {
1730 *offset = 0;
1731 *max = 0;
1732 }
1733
1734 name = crm_element_name(data);
1735 CRM_ASSERT(name != NULL);
1736
1737 insert_prefix(options, buffer, offset, max, depth);
1738 buffer_print(*buffer, *max, *offset, "<%s", name);
1739
1740 if (options & xml_log_option_filtered) {
1741 dump_filtered_xml(data, options, buffer, offset, max);
1742
1743 } else {
1744 for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) {
1745 dump_xml_attr(a, options, buffer, offset, max);
1746 }
1747 }
1748
1749 if (data->children == NULL) {
1750 buffer_print(*buffer, *max, *offset, "/>");
1751
1752 } else {
1753 buffer_print(*buffer, *max, *offset, ">");
1754 }
1755
1756 if (options & xml_log_option_formatted) {
1757 buffer_print(*buffer, *max, *offset, "\n");
1758 }
1759
1760 if (data->children) {
1761 xmlNode *xChild = NULL;
1762 for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
1763 pcmk__xml2text(xChild, options, buffer, offset, max, depth + 1);
1764 }
1765
1766 insert_prefix(options, buffer, offset, max, depth);
1767 buffer_print(*buffer, *max, *offset, "</%s>", name);
1768
1769 if (options & xml_log_option_formatted) {
1770 buffer_print(*buffer, *max, *offset, "\n");
1771 }
1772 }
1773 }
1774
1775 static void
dump_xml_text(xmlNode * data,int options,char ** buffer,int * offset,int * max,int depth)1776 dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
1777 {
1778 CRM_ASSERT(max != NULL);
1779 CRM_ASSERT(offset != NULL);
1780 CRM_ASSERT(buffer != NULL);
1781
1782 if (data == NULL) {
1783 crm_trace("Nothing to dump");
1784 return;
1785 }
1786
1787 if (*buffer == NULL) {
1788 *offset = 0;
1789 *max = 0;
1790 }
1791
1792 insert_prefix(options, buffer, offset, max, depth);
1793
1794 buffer_print(*buffer, *max, *offset, "%s", data->content);
1795
1796 if (options & xml_log_option_formatted) {
1797 buffer_print(*buffer, *max, *offset, "\n");
1798 }
1799 }
1800
1801 static void
dump_xml_cdata(xmlNode * data,int options,char ** buffer,int * offset,int * max,int depth)1802 dump_xml_cdata(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
1803 {
1804 CRM_ASSERT(max != NULL);
1805 CRM_ASSERT(offset != NULL);
1806 CRM_ASSERT(buffer != NULL);
1807
1808 if (data == NULL) {
1809 crm_trace("Nothing to dump");
1810 return;
1811 }
1812
1813 if (*buffer == NULL) {
1814 *offset = 0;
1815 *max = 0;
1816 }
1817
1818 insert_prefix(options, buffer, offset, max, depth);
1819
1820 buffer_print(*buffer, *max, *offset, "<![CDATA[");
1821 buffer_print(*buffer, *max, *offset, "%s", data->content);
1822 buffer_print(*buffer, *max, *offset, "]]>");
1823
1824 if (options & xml_log_option_formatted) {
1825 buffer_print(*buffer, *max, *offset, "\n");
1826 }
1827 }
1828
1829 static void
dump_xml_comment(xmlNode * data,int options,char ** buffer,int * offset,int * max,int depth)1830 dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
1831 {
1832 CRM_ASSERT(max != NULL);
1833 CRM_ASSERT(offset != NULL);
1834 CRM_ASSERT(buffer != NULL);
1835
1836 if (data == NULL) {
1837 crm_trace("Nothing to dump");
1838 return;
1839 }
1840
1841 if (*buffer == NULL) {
1842 *offset = 0;
1843 *max = 0;
1844 }
1845
1846 insert_prefix(options, buffer, offset, max, depth);
1847
1848 buffer_print(*buffer, *max, *offset, "<!--");
1849 buffer_print(*buffer, *max, *offset, "%s", data->content);
1850 buffer_print(*buffer, *max, *offset, "-->");
1851
1852 if (options & xml_log_option_formatted) {
1853 buffer_print(*buffer, *max, *offset, "\n");
1854 }
1855 }
1856
1857 #define PCMK__XMLDUMP_STATS 0
1858
1859 /*!
1860 * \internal
1861 * \brief Create a text representation of an XML object
1862 *
1863 * \param[in] data XML to convert
1864 * \param[in] options Group of enum xml_log_options flags
1865 * \param[in,out] buffer Buffer to store text in (may be reallocated)
1866 * \param[in,out] offset Current position of null terminator within \p buffer
1867 * \param[in,out] max Current size of \p buffer in bytes
1868 * \param[in] depth Current indentation level
1869 */
1870 void
pcmk__xml2text(xmlNode * data,int options,char ** buffer,int * offset,int * max,int depth)1871 pcmk__xml2text(xmlNode *data, int options, char **buffer, int *offset,
1872 int *max, int depth)
1873 {
1874 if(data == NULL) {
1875 *offset = 0;
1876 *max = 0;
1877 return;
1878 }
1879
1880 if (!pcmk_is_set(options, xml_log_option_filtered)
1881 && pcmk_is_set(options, xml_log_option_full_fledged)) {
1882 /* libxml's serialization reuse is a good idea, sadly we cannot
1883 apply it for the filtered cases (preceding filtering pass
1884 would preclude further reuse of such in-situ modified XML
1885 in generic context and is likely not a win performance-wise),
1886 and there's also a historically unstable throughput argument
1887 (likely stemming from memory allocation overhead, eventhough
1888 that shall be minimized with defaults preset in crm_xml_init) */
1889 #if (PCMK__XMLDUMP_STATS - 0)
1890 time_t next, new = time(NULL);
1891 #endif
1892 xmlDoc *doc;
1893 xmlOutputBuffer *xml_buffer;
1894
1895 doc = getDocPtr(data);
1896 /* doc will only be NULL if data is */
1897 CRM_CHECK(doc != NULL, return);
1898
1899 xml_buffer = xmlAllocOutputBuffer(NULL);
1900 CRM_ASSERT(xml_buffer != NULL);
1901
1902 /* XXX we could setup custom allocation scheme for the particular
1903 buffer, but it's subsumed with crm_xml_init that needs to
1904 be invoked prior to entering this function as such, since
1905 its other branch vitally depends on it -- what can be done
1906 about this all is to have a facade parsing functions that
1907 would 100% mark entering libxml code for us, since we don't
1908 do anything as crazy as swapping out the binary form of the
1909 parsed tree (but those would need to be strictly used as
1910 opposed to libxml's raw functions) */
1911
1912 xmlNodeDumpOutput(xml_buffer, doc, data, 0,
1913 (options & xml_log_option_formatted), NULL);
1914 /* attempt adding final NL - failing shouldn't be fatal here */
1915 (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n");
1916 if (xml_buffer->buffer != NULL) {
1917 buffer_print(*buffer, *max, *offset, "%s",
1918 (char *) xmlBufContent(xml_buffer->buffer));
1919 }
1920
1921 #if (PCMK__XMLDUMP_STATS - 0)
1922 next = time(NULL);
1923 if ((now + 1) < next) {
1924 crm_log_xml_trace(data, "Long time");
1925 crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
1926 }
1927 #endif
1928
1929 /* asserted allocation before so there should be something to remove */
1930 (void) xmlOutputBufferClose(xml_buffer);
1931 return;
1932 }
1933
1934 switch(data->type) {
1935 case XML_ELEMENT_NODE:
1936 /* Handle below */
1937 dump_xml_element(data, options, buffer, offset, max, depth);
1938 break;
1939 case XML_TEXT_NODE:
1940 /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
1941 if (options & xml_log_option_text) {
1942 dump_xml_text(data, options, buffer, offset, max, depth);
1943 }
1944 return;
1945 case XML_COMMENT_NODE:
1946 dump_xml_comment(data, options, buffer, offset, max, depth);
1947 break;
1948 case XML_CDATA_SECTION_NODE:
1949 dump_xml_cdata(data, options, buffer, offset, max, depth);
1950 break;
1951 default:
1952 crm_warn("Unhandled type: %d", data->type);
1953 return;
1954
1955 /*
1956 XML_ATTRIBUTE_NODE = 2
1957 XML_ENTITY_REF_NODE = 5
1958 XML_ENTITY_NODE = 6
1959 XML_PI_NODE = 7
1960 XML_DOCUMENT_NODE = 9
1961 XML_DOCUMENT_TYPE_NODE = 10
1962 XML_DOCUMENT_FRAG_NODE = 11
1963 XML_NOTATION_NODE = 12
1964 XML_HTML_DOCUMENT_NODE = 13
1965 XML_DTD_NODE = 14
1966 XML_ELEMENT_DECL = 15
1967 XML_ATTRIBUTE_DECL = 16
1968 XML_ENTITY_DECL = 17
1969 XML_NAMESPACE_DECL = 18
1970 XML_XINCLUDE_START = 19
1971 XML_XINCLUDE_END = 20
1972 XML_DOCB_DOCUMENT_NODE = 21
1973 */
1974 }
1975
1976 }
1977
1978 /*!
1979 * \internal
1980 * \brief Add a single character to a dynamically allocated buffer
1981 *
1982 * \param[in,out] buffer Buffer to store text in (may be reallocated)
1983 * \param[in,out] offset Current position of null terminator within \p buffer
1984 * \param[in,out] max Current size of \p buffer in bytes
1985 * \param[in] c Character to add to \p buffer
1986 */
1987 void
pcmk__buffer_add_char(char ** buffer,int * offset,int * max,char c)1988 pcmk__buffer_add_char(char **buffer, int *offset, int *max, char c)
1989 {
1990 buffer_print(*buffer, *max, *offset, "%c", c);
1991 }
1992
1993 char *
dump_xml_formatted_with_text(xmlNode * an_xml_node)1994 dump_xml_formatted_with_text(xmlNode * an_xml_node)
1995 {
1996 char *buffer = NULL;
1997 int offset = 0, max = 0;
1998
1999 pcmk__xml2text(an_xml_node,
2000 xml_log_option_formatted|xml_log_option_full_fledged,
2001 &buffer, &offset, &max, 0);
2002 return buffer;
2003 }
2004
2005 char *
dump_xml_formatted(xmlNode * an_xml_node)2006 dump_xml_formatted(xmlNode * an_xml_node)
2007 {
2008 char *buffer = NULL;
2009 int offset = 0, max = 0;
2010
2011 pcmk__xml2text(an_xml_node, xml_log_option_formatted, &buffer, &offset,
2012 &max, 0);
2013 return buffer;
2014 }
2015
2016 char *
dump_xml_unformatted(xmlNode * an_xml_node)2017 dump_xml_unformatted(xmlNode * an_xml_node)
2018 {
2019 char *buffer = NULL;
2020 int offset = 0, max = 0;
2021
2022 pcmk__xml2text(an_xml_node, 0, &buffer, &offset, &max, 0);
2023 return buffer;
2024 }
2025
2026 gboolean
xml_has_children(const xmlNode * xml_root)2027 xml_has_children(const xmlNode * xml_root)
2028 {
2029 if (xml_root != NULL && xml_root->children != NULL) {
2030 return TRUE;
2031 }
2032 return FALSE;
2033 }
2034
2035 void
xml_remove_prop(xmlNode * obj,const char * name)2036 xml_remove_prop(xmlNode * obj, const char *name)
2037 {
2038 if (pcmk__check_acl(obj, NULL, xpf_acl_write) == FALSE) {
2039 crm_trace("Cannot remove %s from %s", name, obj->name);
2040
2041 } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
2042 /* Leave in place (marked for removal) until after the diff is calculated */
2043 xml_private_t *p = NULL;
2044 xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
2045
2046 p = attr->_private;
2047 set_parent_flag(obj, xpf_dirty);
2048 pcmk__set_xml_flags(p, xpf_deleted);
2049 } else {
2050 xmlUnsetProp(obj, (pcmkXmlStr) name);
2051 }
2052 }
2053
2054 void
save_xml_to_file(xmlNode * xml,const char * desc,const char * filename)2055 save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
2056 {
2057 char *f = NULL;
2058
2059 if (filename == NULL) {
2060 char *uuid = crm_generate_uuid();
2061
2062 f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
2063 filename = f;
2064 free(uuid);
2065 }
2066
2067 crm_info("Saving %s to %s", desc, filename);
2068 write_xml_file(xml, filename, FALSE);
2069 free(f);
2070 }
2071
2072 /*!
2073 * \internal
2074 * \brief Set a flag on all attributes of an XML element
2075 *
2076 * \param[in,out] xml XML node to set flags on
2077 * \param[in] flag XML private flag to set
2078 */
2079 static void
set_attrs_flag(xmlNode * xml,enum xml_private_flags flag)2080 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
2081 {
2082 for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
2083 pcmk__set_xml_flags((xml_private_t *) (attr->_private), flag);
2084 }
2085 }
2086
2087 /*!
2088 * \internal
2089 * \brief Add an XML attribute to a node, marked as deleted
2090 *
2091 * When calculating XML changes, we need to know when an attribute has been
2092 * deleted. Add the attribute back to the new XML, so that we can check the
2093 * removal against ACLs, and mark it as deleted for later removal after
2094 * differences have been calculated.
2095 */
2096 static void
mark_attr_deleted(xmlNode * new_xml,const char * element,const char * attr_name,const char * old_value)2097 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
2098 const char *old_value)
2099 {
2100 xml_private_t *p = new_xml->doc->_private;
2101 xmlAttr *attr = NULL;
2102
2103 // Prevent the dirty flag being set recursively upwards
2104 pcmk__clear_xml_flags(p, xpf_tracking);
2105
2106 // Restore the old value (and the tracking flag)
2107 attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2108 pcmk__set_xml_flags(p, xpf_tracking);
2109
2110 // Reset flags (so the attribute doesn't appear as newly created)
2111 p = attr->_private;
2112 p->flags = 0;
2113
2114 // Check ACLs and mark restored value for later removal
2115 xml_remove_prop(new_xml, attr_name);
2116
2117 crm_trace("XML attribute %s=%s was removed from %s",
2118 attr_name, old_value, element);
2119 }
2120
2121 /*
2122 * \internal
2123 * \brief Check ACLs for a changed XML attribute
2124 */
2125 static void
mark_attr_changed(xmlNode * new_xml,const char * element,const char * attr_name,const char * old_value)2126 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
2127 const char *old_value)
2128 {
2129 char *vcopy = crm_element_value_copy(new_xml, attr_name);
2130
2131 crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
2132 attr_name, old_value, vcopy, element);
2133
2134 // Restore the original value
2135 xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
2136
2137 // Change it back to the new value, to check ACLs
2138 crm_xml_add(new_xml, attr_name, vcopy);
2139 free(vcopy);
2140 }
2141
2142 /*!
2143 * \internal
2144 * \brief Mark an XML attribute as having changed position
2145 */
2146 static void
mark_attr_moved(xmlNode * new_xml,const char * element,xmlAttr * old_attr,xmlAttr * new_attr,int p_old,int p_new)2147 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
2148 xmlAttr *new_attr, int p_old, int p_new)
2149 {
2150 xml_private_t *p = new_attr->_private;
2151
2152 crm_trace("XML attribute %s moved from position %d to %d in %s",
2153 old_attr->name, p_old, p_new, element);
2154
2155 // Mark document, element, and all element's parents as changed
2156 mark_xml_node_dirty(new_xml);
2157
2158 // Mark attribute as changed
2159 pcmk__set_xml_flags(p, xpf_dirty|xpf_moved);
2160
2161 p = (p_old > p_new)? old_attr->_private : new_attr->_private;
2162 pcmk__set_xml_flags(p, xpf_skip);
2163 }
2164
2165 /*!
2166 * \internal
2167 * \brief Calculate differences in all previously existing XML attributes
2168 */
2169 static void
xml_diff_old_attrs(xmlNode * old_xml,xmlNode * new_xml)2170 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
2171 {
2172 xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
2173
2174 while (attr_iter != NULL) {
2175 xmlAttr *old_attr = attr_iter;
2176 xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
2177 const char *name = (const char *) attr_iter->name;
2178 const char *old_value = crm_element_value(old_xml, name);
2179
2180 attr_iter = attr_iter->next;
2181 if (new_attr == NULL) {
2182 mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
2183 old_value);
2184
2185 } else {
2186 xml_private_t *p = new_attr->_private;
2187 int new_pos = pcmk__xml_position((xmlNode*) new_attr, xpf_skip);
2188 int old_pos = pcmk__xml_position((xmlNode*) old_attr, xpf_skip);
2189 const char *new_value = crm_element_value(new_xml, name);
2190
2191 // This attribute isn't new
2192 pcmk__clear_xml_flags(p, xpf_created);
2193
2194 if (strcmp(new_value, old_value) != 0) {
2195 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
2196 old_value);
2197
2198 } else if ((old_pos != new_pos)
2199 && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
2200 mark_attr_moved(new_xml, (const char *) old_xml->name,
2201 old_attr, new_attr, old_pos, new_pos);
2202 }
2203 }
2204 }
2205 }
2206
2207 /*!
2208 * \internal
2209 * \brief Check all attributes in new XML for creation
2210 */
2211 static void
mark_created_attrs(xmlNode * new_xml)2212 mark_created_attrs(xmlNode *new_xml)
2213 {
2214 xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
2215
2216 while (attr_iter != NULL) {
2217 xmlAttr *new_attr = attr_iter;
2218 xml_private_t *p = attr_iter->_private;
2219
2220 attr_iter = attr_iter->next;
2221 if (pcmk_is_set(p->flags, xpf_created)) {
2222 const char *attr_name = (const char *) new_attr->name;
2223
2224 crm_trace("Created new attribute %s=%s in %s",
2225 attr_name, crm_element_value(new_xml, attr_name),
2226 new_xml->name);
2227
2228 /* Check ACLs (we can't use the remove-then-create trick because it
2229 * would modify the attribute position).
2230 */
2231 if (pcmk__check_acl(new_xml, attr_name, xpf_acl_write)) {
2232 pcmk__mark_xml_attr_dirty(new_attr);
2233 } else {
2234 // Creation was not allowed, so remove the attribute
2235 xmlUnsetProp(new_xml, new_attr->name);
2236 }
2237 }
2238 }
2239 }
2240
2241 /*!
2242 * \internal
2243 * \brief Calculate differences in attributes between two XML nodes
2244 */
2245 static void
xml_diff_attrs(xmlNode * old_xml,xmlNode * new_xml)2246 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
2247 {
2248 set_attrs_flag(new_xml, xpf_created); // cleared later if not really new
2249 xml_diff_old_attrs(old_xml, new_xml);
2250 mark_created_attrs(new_xml);
2251 }
2252
2253 /*!
2254 * \internal
2255 * \brief Add an XML child element to a node, marked as deleted
2256 *
2257 * When calculating XML changes, we need to know when a child element has been
2258 * deleted. Add the child back to the new XML, so that we can check the removal
2259 * against ACLs, and mark it as deleted for later removal after differences have
2260 * been calculated.
2261 */
2262 static void
mark_child_deleted(xmlNode * old_child,xmlNode * new_parent)2263 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
2264 {
2265 // Re-create the child element so we can check ACLs
2266 xmlNode *candidate = add_node_copy(new_parent, old_child);
2267
2268 // Clear flags on new child and its children
2269 reset_xml_node_flags(candidate);
2270
2271 // Check whether ACLs allow the deletion
2272 pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
2273
2274 // Remove the child again (which will track it in document's deleted_objs)
2275 free_xml_with_position(candidate,
2276 pcmk__xml_position(old_child, xpf_skip));
2277
2278 if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
2279 pcmk__set_xml_flags((xml_private_t *) (old_child->_private), xpf_skip);
2280 }
2281 }
2282
2283 static void
mark_child_moved(xmlNode * old_child,xmlNode * new_parent,xmlNode * new_child,int p_old,int p_new)2284 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
2285 int p_old, int p_new)
2286 {
2287 xml_private_t *p = new_child->_private;
2288
2289 crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
2290 new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
2291 p_old, p_new, new_parent->name);
2292 mark_xml_node_dirty(new_parent);
2293 pcmk__set_xml_flags(p, xpf_moved);
2294
2295 if (p_old > p_new) {
2296 p = old_child->_private;
2297 } else {
2298 p = new_child->_private;
2299 }
2300 pcmk__set_xml_flags(p, xpf_skip);
2301 }
2302
2303 // Given original and new XML, mark new XML portions that have changed
2304 static void
mark_xml_changes(xmlNode * old_xml,xmlNode * new_xml,bool check_top)2305 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
2306 {
2307 xmlNode *cIter = NULL;
2308 xml_private_t *p = NULL;
2309
2310 CRM_CHECK(new_xml != NULL, return);
2311 if (old_xml == NULL) {
2312 pcmk__mark_xml_created(new_xml);
2313 pcmk__apply_creation_acl(new_xml, check_top);
2314 return;
2315 }
2316
2317 p = new_xml->_private;
2318 CRM_CHECK(p != NULL, return);
2319
2320 if(p->flags & xpf_processed) {
2321 /* Avoid re-comparing nodes */
2322 return;
2323 }
2324 pcmk__set_xml_flags(p, xpf_processed);
2325
2326 xml_diff_attrs(old_xml, new_xml);
2327
2328 // Check for differences in the original children
2329 for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
2330 xmlNode *old_child = cIter;
2331 xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
2332
2333 cIter = pcmk__xml_next(cIter);
2334 if(new_child) {
2335 mark_xml_changes(old_child, new_child, TRUE);
2336
2337 } else {
2338 mark_child_deleted(old_child, new_xml);
2339 }
2340 }
2341
2342 // Check for moved or created children
2343 for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
2344 xmlNode *new_child = cIter;
2345 xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
2346
2347 cIter = pcmk__xml_next(cIter);
2348 if(old_child == NULL) {
2349 // This is a newly created child
2350 p = new_child->_private;
2351 pcmk__set_xml_flags(p, xpf_skip);
2352 mark_xml_changes(old_child, new_child, TRUE);
2353
2354 } else {
2355 /* Check for movement, we already checked for differences */
2356 int p_new = pcmk__xml_position(new_child, xpf_skip);
2357 int p_old = pcmk__xml_position(old_child, xpf_skip);
2358
2359 if(p_old != p_new) {
2360 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
2361 }
2362 }
2363 }
2364 }
2365
2366 void
xml_calculate_significant_changes(xmlNode * old_xml,xmlNode * new_xml)2367 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
2368 {
2369 pcmk__set_xml_doc_flag(new_xml, xpf_lazy);
2370 xml_calculate_changes(old_xml, new_xml);
2371 }
2372
2373 void
xml_calculate_changes(xmlNode * old_xml,xmlNode * new_xml)2374 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
2375 {
2376 CRM_CHECK(pcmk__str_eq(crm_element_name(old_xml), crm_element_name(new_xml), pcmk__str_casei),
2377 return);
2378 CRM_CHECK(pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_casei), return);
2379
2380 if(xml_tracking_changes(new_xml) == FALSE) {
2381 xml_track_changes(new_xml, NULL, NULL, FALSE);
2382 }
2383
2384 mark_xml_changes(old_xml, new_xml, FALSE);
2385 }
2386
2387 gboolean
can_prune_leaf(xmlNode * xml_node)2388 can_prune_leaf(xmlNode * xml_node)
2389 {
2390 xmlNode *cIter = NULL;
2391 gboolean can_prune = TRUE;
2392 const char *name = crm_element_name(xml_node);
2393
2394 if (pcmk__strcase_any_of(name, XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF,
2395 XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, NULL)) {
2396 return FALSE;
2397 }
2398
2399 for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
2400 const char *p_name = (const char *) a->name;
2401
2402 if (strcmp(p_name, XML_ATTR_ID) == 0) {
2403 continue;
2404 }
2405 can_prune = FALSE;
2406 }
2407
2408 cIter = pcmk__xml_first_child(xml_node);
2409 while (cIter) {
2410 xmlNode *child = cIter;
2411
2412 cIter = pcmk__xml_next(cIter);
2413 if (can_prune_leaf(child)) {
2414 free_xml(child);
2415 } else {
2416 can_prune = FALSE;
2417 }
2418 }
2419 return can_prune;
2420 }
2421
2422 /*!
2423 * \internal
2424 * \brief Find a comment with matching content in specified XML
2425 *
2426 * \param[in] root XML to search
2427 * \param[in] search_comment Comment whose content should be searched for
2428 * \param[in] exact If true, comment must also be at same position
2429 */
2430 xmlNode *
pcmk__xc_match(xmlNode * root,xmlNode * search_comment,bool exact)2431 pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact)
2432 {
2433 xmlNode *a_child = NULL;
2434 int search_offset = pcmk__xml_position(search_comment, xpf_skip);
2435
2436 CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
2437
2438 for (a_child = pcmk__xml_first_child(root); a_child != NULL;
2439 a_child = pcmk__xml_next(a_child)) {
2440 if (exact) {
2441 int offset = pcmk__xml_position(a_child, xpf_skip);
2442 xml_private_t *p = a_child->_private;
2443
2444 if (offset < search_offset) {
2445 continue;
2446
2447 } else if (offset > search_offset) {
2448 return NULL;
2449 }
2450
2451 if (pcmk_is_set(p->flags, xpf_skip)) {
2452 continue;
2453 }
2454 }
2455
2456 if (a_child->type == XML_COMMENT_NODE
2457 && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
2458 return a_child;
2459
2460 } else if (exact) {
2461 return NULL;
2462 }
2463 }
2464
2465 return NULL;
2466 }
2467
2468 /*!
2469 * \internal
2470 * \brief Make one XML comment match another (in content)
2471 *
2472 * \param[in,out] parent If \p target is NULL and this is not, add or update
2473 * comment child of this XML node that matches \p update
2474 * \param[in,out] target If not NULL, update this XML comment node
2475 * \param[in] update Make comment content match this (must not be NULL)
2476 *
2477 * \note At least one of \parent and \target must be non-NULL
2478 */
2479 void
pcmk__xc_update(xmlNode * parent,xmlNode * target,xmlNode * update)2480 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
2481 {
2482 CRM_CHECK(update != NULL, return);
2483 CRM_CHECK(update->type == XML_COMMENT_NODE, return);
2484
2485 if (target == NULL) {
2486 target = pcmk__xc_match(parent, update, false);
2487 }
2488
2489 if (target == NULL) {
2490 add_node_copy(parent, update);
2491
2492 } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
2493 xmlFree(target->content);
2494 target->content = xmlStrdup(update->content);
2495 }
2496 }
2497
2498 /*!
2499 * \internal
2500 * \brief Make one XML tree match another (in children and attributes)
2501 *
2502 * \param[in,out] parent If \p target is NULL and this is not, add or update
2503 * child of this XML node that matches \p update
2504 * \param[in,out] target If not NULL, update this XML
2505 * \param[in] update Make the desired XML match this (must not be NULL)
2506 * \param[in] as_diff If true, expand "++" when making attributes match
2507 *
2508 * \note At least one of \parent and \target must be non-NULL
2509 */
2510 void
pcmk__xml_update(xmlNode * parent,xmlNode * target,xmlNode * update,bool as_diff)2511 pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
2512 bool as_diff)
2513 {
2514 xmlNode *a_child = NULL;
2515 const char *object_name = NULL,
2516 *object_href = NULL,
2517 *object_href_val = NULL;
2518
2519 #if XML_PARSER_DEBUG
2520 crm_log_xml_trace("update:", update);
2521 crm_log_xml_trace("target:", target);
2522 #endif
2523
2524 CRM_CHECK(update != NULL, return);
2525
2526 if (update->type == XML_COMMENT_NODE) {
2527 pcmk__xc_update(parent, target, update);
2528 return;
2529 }
2530
2531 object_name = crm_element_name(update);
2532 object_href_val = ID(update);
2533 if (object_href_val != NULL) {
2534 object_href = XML_ATTR_ID;
2535 } else {
2536 object_href_val = crm_element_value(update, XML_ATTR_IDREF);
2537 object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
2538 }
2539
2540 CRM_CHECK(object_name != NULL, return);
2541 CRM_CHECK(target != NULL || parent != NULL, return);
2542
2543 if (target == NULL) {
2544 target = pcmk__xe_match(parent, object_name,
2545 object_href, object_href_val);
2546 }
2547
2548 if (target == NULL) {
2549 target = create_xml_node(parent, object_name);
2550 CRM_CHECK(target != NULL, return);
2551 #if XML_PARSER_DEBUG
2552 crm_trace("Added <%s%s%s%s%s/>", crm_str(object_name),
2553 object_href ? " " : "",
2554 object_href ? object_href : "",
2555 object_href ? "=" : "",
2556 object_href ? object_href_val : "");
2557
2558 } else {
2559 crm_trace("Found node <%s%s%s%s%s/> to update", crm_str(object_name),
2560 object_href ? " " : "",
2561 object_href ? object_href : "",
2562 object_href ? "=" : "",
2563 object_href ? object_href_val : "");
2564 #endif
2565 }
2566
2567 CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(update),
2568 pcmk__str_casei),
2569 return);
2570
2571 if (as_diff == FALSE) {
2572 /* So that expand_plus_plus() gets called */
2573 copy_in_properties(target, update);
2574
2575 } else {
2576 /* No need for expand_plus_plus(), just raw speed */
2577 for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2578 a = a->next) {
2579 const char *p_value = pcmk__xml_attr_value(a);
2580
2581 /* Remove it first so the ordering of the update is preserved */
2582 xmlUnsetProp(target, a->name);
2583 xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
2584 }
2585 }
2586
2587 for (a_child = pcmk__xml_first_child(update); a_child != NULL;
2588 a_child = pcmk__xml_next(a_child)) {
2589 #if XML_PARSER_DEBUG
2590 crm_trace("Updating child <%s%s%s%s%s/>", crm_str(object_name),
2591 object_href ? " " : "",
2592 object_href ? object_href : "",
2593 object_href ? "=" : "",
2594 object_href ? object_href_val : "");
2595 #endif
2596 pcmk__xml_update(target, NULL, a_child, as_diff);
2597 }
2598
2599 #if XML_PARSER_DEBUG
2600 crm_trace("Finished with <%s%s%s%s%s/>", crm_str(object_name),
2601 object_href ? " " : "",
2602 object_href ? object_href : "",
2603 object_href ? "=" : "",
2604 object_href ? object_href_val : "");
2605 #endif
2606 }
2607
2608 gboolean
update_xml_child(xmlNode * child,xmlNode * to_update)2609 update_xml_child(xmlNode * child, xmlNode * to_update)
2610 {
2611 gboolean can_update = TRUE;
2612 xmlNode *child_of_child = NULL;
2613
2614 CRM_CHECK(child != NULL, return FALSE);
2615 CRM_CHECK(to_update != NULL, return FALSE);
2616
2617 if (!pcmk__str_eq(crm_element_name(to_update), crm_element_name(child), pcmk__str_none)) {
2618 can_update = FALSE;
2619
2620 } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) {
2621 can_update = FALSE;
2622
2623 } else if (can_update) {
2624 #if XML_PARSER_DEBUG
2625 crm_log_xml_trace(child, "Update match found...");
2626 #endif
2627 pcmk__xml_update(NULL, child, to_update, false);
2628 }
2629
2630 for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
2631 child_of_child = pcmk__xml_next(child_of_child)) {
2632 /* only update the first one */
2633 if (can_update) {
2634 break;
2635 }
2636 can_update = update_xml_child(child_of_child, to_update);
2637 }
2638
2639 return can_update;
2640 }
2641
2642 int
find_xml_children(xmlNode ** children,xmlNode * root,const char * tag,const char * field,const char * value,gboolean search_matches)2643 find_xml_children(xmlNode ** children, xmlNode * root,
2644 const char *tag, const char *field, const char *value, gboolean search_matches)
2645 {
2646 int match_found = 0;
2647
2648 CRM_CHECK(root != NULL, return FALSE);
2649 CRM_CHECK(children != NULL, return FALSE);
2650
2651 if (tag != NULL && !pcmk__str_eq(tag, crm_element_name(root), pcmk__str_casei)) {
2652
2653 } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
2654
2655 } else {
2656 if (*children == NULL) {
2657 *children = create_xml_node(NULL, __func__);
2658 }
2659 add_node_copy(*children, root);
2660 match_found = 1;
2661 }
2662
2663 if (search_matches || match_found == 0) {
2664 xmlNode *child = NULL;
2665
2666 for (child = pcmk__xml_first_child(root); child != NULL;
2667 child = pcmk__xml_next(child)) {
2668 match_found += find_xml_children(children, child, tag, field, value, search_matches);
2669 }
2670 }
2671
2672 return match_found;
2673 }
2674
2675 gboolean
replace_xml_child(xmlNode * parent,xmlNode * child,xmlNode * update,gboolean delete_only)2676 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
2677 {
2678 gboolean can_delete = FALSE;
2679 xmlNode *child_of_child = NULL;
2680
2681 const char *up_id = NULL;
2682 const char *child_id = NULL;
2683 const char *right_val = NULL;
2684
2685 CRM_CHECK(child != NULL, return FALSE);
2686 CRM_CHECK(update != NULL, return FALSE);
2687
2688 up_id = ID(update);
2689 child_id = ID(child);
2690
2691 if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
2692 can_delete = TRUE;
2693 }
2694 if (!pcmk__str_eq(crm_element_name(update), crm_element_name(child), pcmk__str_casei)) {
2695 can_delete = FALSE;
2696 }
2697 if (can_delete && delete_only) {
2698 for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
2699 a = a->next) {
2700 const char *p_name = (const char *) a->name;
2701 const char *p_value = pcmk__xml_attr_value(a);
2702
2703 right_val = crm_element_value(child, p_name);
2704 if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
2705 can_delete = FALSE;
2706 }
2707 }
2708 }
2709
2710 if (can_delete && parent != NULL) {
2711 crm_log_xml_trace(child, "Delete match found...");
2712 if (delete_only || update == NULL) {
2713 free_xml(child);
2714
2715 } else {
2716 xmlNode *tmp = copy_xml(update);
2717 xmlDoc *doc = tmp->doc;
2718 xmlNode *old = NULL;
2719
2720 xml_accept_changes(tmp);
2721 old = xmlReplaceNode(child, tmp);
2722
2723 if(xml_tracking_changes(tmp)) {
2724 /* Replaced sections may have included relevant ACLs */
2725 pcmk__apply_acl(tmp);
2726 }
2727
2728 xml_calculate_changes(old, tmp);
2729 xmlDocSetRootElement(doc, old);
2730 free_xml(old);
2731 }
2732 child = NULL;
2733 return TRUE;
2734
2735 } else if (can_delete) {
2736 crm_log_xml_debug(child, "Cannot delete the search root");
2737 can_delete = FALSE;
2738 }
2739
2740 child_of_child = pcmk__xml_first_child(child);
2741 while (child_of_child) {
2742 xmlNode *next = pcmk__xml_next(child_of_child);
2743
2744 can_delete = replace_xml_child(child, child_of_child, update, delete_only);
2745
2746 /* only delete the first one */
2747 if (can_delete) {
2748 child_of_child = NULL;
2749 } else {
2750 child_of_child = next;
2751 }
2752 }
2753
2754 return can_delete;
2755 }
2756
2757 xmlNode *
sorted_xml(xmlNode * input,xmlNode * parent,gboolean recursive)2758 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
2759 {
2760 xmlNode *child = NULL;
2761 GSList *nvpairs = NULL;
2762 xmlNode *result = NULL;
2763 const char *name = NULL;
2764
2765 CRM_CHECK(input != NULL, return NULL);
2766
2767 name = crm_element_name(input);
2768 CRM_CHECK(name != NULL, return NULL);
2769
2770 result = create_xml_node(parent, name);
2771 nvpairs = pcmk_xml_attrs2nvpairs(input);
2772 nvpairs = pcmk_sort_nvpairs(nvpairs);
2773 pcmk_nvpairs2xml_attrs(nvpairs, result);
2774 pcmk_free_nvpairs(nvpairs);
2775
2776 for (child = pcmk__xml_first_child(input); child != NULL;
2777 child = pcmk__xml_next(child)) {
2778
2779 if (recursive) {
2780 sorted_xml(child, result, recursive);
2781 } else {
2782 add_node_copy(result, child);
2783 }
2784 }
2785
2786 return result;
2787 }
2788
2789 xmlNode *
first_named_child(const xmlNode * parent,const char * name)2790 first_named_child(const xmlNode *parent, const char *name)
2791 {
2792 xmlNode *match = NULL;
2793
2794 for (match = pcmk__xe_first_child(parent); match != NULL;
2795 match = pcmk__xe_next(match)) {
2796 /*
2797 * name == NULL gives first child regardless of name; this is
2798 * semantically incorrect in this function, but may be necessary
2799 * due to prior use of xml_child_iter_filter
2800 */
2801 if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) {
2802 return match;
2803 }
2804 }
2805 return NULL;
2806 }
2807
2808 /*!
2809 * \brief Get next instance of same XML tag
2810 *
2811 * \param[in] sibling XML tag to start from
2812 *
2813 * \return Next sibling XML tag with same name
2814 */
2815 xmlNode *
crm_next_same_xml(const xmlNode * sibling)2816 crm_next_same_xml(const xmlNode *sibling)
2817 {
2818 xmlNode *match = pcmk__xe_next(sibling);
2819 const char *name = crm_element_name(sibling);
2820
2821 while (match != NULL) {
2822 if (!strcmp(crm_element_name(match), name)) {
2823 return match;
2824 }
2825 match = pcmk__xe_next(match);
2826 }
2827 return NULL;
2828 }
2829
2830 void
crm_xml_init(void)2831 crm_xml_init(void)
2832 {
2833 static bool init = TRUE;
2834
2835 if(init) {
2836 init = FALSE;
2837 /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
2838 * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
2839 * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
2840 * less than 1 second.
2841 */
2842 xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
2843
2844 /* Populate and free the _private field when nodes are created and destroyed */
2845 xmlDeregisterNodeDefault(free_private_data);
2846 xmlRegisterNodeDefault(new_private_data);
2847
2848 crm_schema_init();
2849 }
2850 }
2851
2852 void
crm_xml_cleanup(void)2853 crm_xml_cleanup(void)
2854 {
2855 crm_info("Cleaning up memory from libxml2");
2856 crm_schema_cleanup();
2857 xmlCleanupParser();
2858 }
2859
2860 #define XPATH_MAX 512
2861
2862 xmlNode *
expand_idref(xmlNode * input,xmlNode * top)2863 expand_idref(xmlNode * input, xmlNode * top)
2864 {
2865 const char *tag = NULL;
2866 const char *ref = NULL;
2867 xmlNode *result = input;
2868
2869 if (result == NULL) {
2870 return NULL;
2871
2872 } else if (top == NULL) {
2873 top = input;
2874 }
2875
2876 tag = crm_element_name(result);
2877 ref = crm_element_value(result, XML_ATTR_IDREF);
2878
2879 if (ref != NULL) {
2880 char *xpath_string = crm_strdup_printf("//%s[@id='%s']", tag, ref);
2881
2882 result = get_xpath_object(xpath_string, top, LOG_ERR);
2883 if (result == NULL) {
2884 char *nodePath = (char *)xmlGetNodePath(top);
2885
2886 crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
2887 crm_str(nodePath));
2888 free(nodePath);
2889 }
2890 free(xpath_string);
2891 }
2892 return result;
2893 }
2894
2895 void
crm_destroy_xml(gpointer data)2896 crm_destroy_xml(gpointer data)
2897 {
2898 free_xml(data);
2899 }
2900
2901 char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)2902 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
2903 {
2904 static const char *base = NULL;
2905 char *ret = NULL;
2906
2907 if (base == NULL) {
2908 base = getenv("PCMK_schema_directory");
2909 }
2910 if (pcmk__str_empty(base)) {
2911 base = CRM_SCHEMA_DIRECTORY;
2912 }
2913
2914 switch (ns) {
2915 case pcmk__xml_artefact_ns_legacy_rng:
2916 case pcmk__xml_artefact_ns_legacy_xslt:
2917 ret = strdup(base);
2918 break;
2919 case pcmk__xml_artefact_ns_base_rng:
2920 case pcmk__xml_artefact_ns_base_xslt:
2921 ret = crm_strdup_printf("%s/base", base);
2922 break;
2923 default:
2924 crm_err("XML artefact family specified as %u not recognized", ns);
2925 }
2926 return ret;
2927 }
2928
2929 char *
pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,const char * filespec)2930 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
2931 {
2932 char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
2933
2934 switch (ns) {
2935 case pcmk__xml_artefact_ns_legacy_rng:
2936 case pcmk__xml_artefact_ns_base_rng:
2937 ret = crm_strdup_printf("%s/%s.rng", base, filespec);
2938 break;
2939 case pcmk__xml_artefact_ns_legacy_xslt:
2940 case pcmk__xml_artefact_ns_base_xslt:
2941 ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
2942 break;
2943 default:
2944 crm_err("XML artefact family specified as %u not recognized", ns);
2945 }
2946 free(base);
2947
2948 return ret;
2949 }
2950
2951 void
pcmk__xe_set_propv(xmlNodePtr node,va_list pairs)2952 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
2953 {
2954 while (true) {
2955 const char *name, *value;
2956
2957 name = va_arg(pairs, const char *);
2958 if (name == NULL) {
2959 return;
2960 }
2961
2962 value = va_arg(pairs, const char *);
2963 if (value == NULL) {
2964 return;
2965 }
2966
2967 crm_xml_add(node, name, value);
2968 }
2969 }
2970
2971 void
pcmk__xe_set_props(xmlNodePtr node,...)2972 pcmk__xe_set_props(xmlNodePtr node, ...)
2973 {
2974 va_list pairs;
2975 va_start(pairs, node);
2976 pcmk__xe_set_propv(node, pairs);
2977 va_end(pairs);
2978 }
2979
2980 // Deprecated functions kept only for backward API compatibility
2981
2982 #include <crm/common/xml_compat.h>
2983
2984 xmlNode *
find_entity(xmlNode * parent,const char * node_name,const char * id)2985 find_entity(xmlNode *parent, const char *node_name, const char *id)
2986 {
2987 return pcmk__xe_match(parent, node_name,
2988 ((id == NULL)? id : XML_ATTR_ID), id);
2989 }
2990
2991 // End deprecated API
2992