pacemaker  2.0.4-2deceaa
Scalable High-Availability cluster resource manager
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
xml.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2020 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>
28 #include <crm/common/xml.h>
29 #include <crm/common/xml_internal.h> /* CRM_XML_LOG_BASE */
30 #include "crmcommon_private.h"
31 
32 #define XML_BUFFER_SIZE 4096
33 #define XML_PARSER_DEBUG 0
34 
35 /* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
36  * by libxml2, which is potentially ambiguous and dangerous. We should drop it
37  * when we can break backward compatibility with configurations that might be
38  * relying on it (i.e. pacemaker 3.0.0).
39  *
40  * It might be a good idea to have a transitional period where we first try
41  * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
42  * it, logging a warning if it succeeds.
43  */
44 #define PCMK__XML_PARSE_OPTS (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
45 
46 typedef struct {
47  int found;
48  const char *string;
49 } filter_t;
50 
51 typedef struct xml_deleted_obj_s {
52  char *path;
53  int position;
55 
56 /* *INDENT-OFF* */
57 
58 static filter_t filter[] = {
59  { 0, XML_ATTR_ORIGIN },
60  { 0, XML_CIB_ATTR_WRITTEN },
61  { 0, XML_ATTR_UPDATE_ORIG },
63  { 0, XML_ATTR_UPDATE_USER },
64 };
65 /* *INDENT-ON* */
66 
67 static xmlNode *subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right, gboolean * changed);
68 static xmlNode *find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact);
69 static int add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update);
70 
71 #define CHUNK_SIZE 1024
72 
73 bool
74 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
75 {
76  if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
77  return FALSE;
78  } else if(is_not_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
79  return FALSE;
80  } else if (lazy && is_not_set(((xml_private_t *)xml->doc->_private)->flags,
81  xpf_lazy)) {
82  return FALSE;
83  }
84  return TRUE;
85 }
86 
87 #define buffer_print(buffer, max, offset, fmt, args...) do { \
88  int rc = (max); \
89  if(buffer) { \
90  rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
91  } \
92  if(buffer && rc < 0) { \
93  crm_perror(LOG_ERR, "snprintf failed at offset %d", offset); \
94  (buffer)[(offset)] = 0; \
95  break; \
96  } else if(rc >= ((max) - (offset))) { \
97  char *tmp = NULL; \
98  (max) = QB_MAX(CHUNK_SIZE, (max) * 2); \
99  tmp = realloc_safe((buffer), (max)); \
100  CRM_ASSERT(tmp); \
101  (buffer) = tmp; \
102  } else { \
103  offset += rc; \
104  break; \
105  } \
106  } while(1);
107 
108 static void
109 insert_prefix(int options, char **buffer, int *offset, int *max, int depth)
110 {
111  if (options & xml_log_option_formatted) {
112  size_t spaces = 2 * depth;
113 
114  if ((*buffer) == NULL || spaces >= ((*max) - (*offset))) {
115  (*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
116  (*buffer) = realloc_safe((*buffer), (*max));
117  }
118  memset((*buffer) + (*offset), ' ', spaces);
119  (*offset) += spaces;
120  }
121 }
122 
123 static void
124 set_parent_flag(xmlNode *xml, long flag)
125 {
126 
127  for(; xml; xml = xml->parent) {
128  xml_private_t *p = xml->_private;
129 
130  if(p == NULL) {
131  /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
132  } else {
133  p->flags |= flag;
134  /* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
135  }
136  }
137 }
138 
139 void
140 pcmk__set_xml_flag(xmlNode *xml, enum xml_private_flags flag)
141 {
142 
143  if(xml && xml->doc && xml->doc->_private){
144  /* During calls to xmlDocCopyNode(), xml->doc may be unset */
145  xml_private_t *p = xml->doc->_private;
146 
147  p->flags |= flag;
148  /* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
149  }
150 }
151 
152 static void
153 __xml_node_dirty(xmlNode *xml)
154 {
156  set_parent_flag(xml, xpf_dirty);
157 }
158 
159 static void
160 __xml_node_clean(xmlNode *xml)
161 {
162  xmlNode *cIter = NULL;
163  xml_private_t *p = xml->_private;
164 
165  if(p) {
166  p->flags = 0;
167  }
168 
169  for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
170  __xml_node_clean(cIter);
171  }
172 }
173 
174 static void
175 crm_node_created(xmlNode *xml)
176 {
177  xmlNode *cIter = NULL;
178  xml_private_t *p = xml->_private;
179 
180  if(p && pcmk__tracking_xml_changes(xml, FALSE)) {
181  if(is_not_set(p->flags, xpf_created)) {
182  p->flags |= xpf_created;
183  __xml_node_dirty(xml);
184  }
185 
186  for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
187  crm_node_created(cIter);
188  }
189  }
190 }
191 
192 void
194 {
195  xmlNode *parent = a->parent;
196  xml_private_t *p = NULL;
197 
198  p = a->_private;
199  p->flags |= (xpf_dirty|xpf_modified);
200  p->flags = (p->flags & ~xpf_deleted);
201  /* crm_trace("Setting flag %x due to %s[@id=%s, @%s=%s]", */
202  /* xpf_dirty, parent?parent->name:NULL, ID(parent), a->name, a->children->content); */
203 
204  __xml_node_dirty(parent);
205 }
206 
207 int get_tag_name(const char *input, size_t offset, size_t max);
208 int get_attr_name(const char *input, size_t offset, size_t max);
209 int get_attr_value(const char *input, size_t offset, size_t max);
210 gboolean can_prune_leaf(xmlNode * xml_node);
211 
212 static int add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff);
213 
214 #define XML_PRIVATE_MAGIC (long) 0x81726354
215 
216 static void
217 __xml_deleted_obj_free(void *data)
218 {
219  if(data) {
220  xml_deleted_obj_t *deleted_obj = data;
221 
222  free(deleted_obj->path);
223  free(deleted_obj);
224  }
225 }
226 
227 static void
228 __xml_private_clean(xml_private_t *p)
229 {
230  if(p) {
232 
233  free(p->user);
234  p->user = NULL;
235 
236  if(p->acls) {
237  pcmk__free_acls(p->acls);
238  p->acls = NULL;
239  }
240 
241  if(p->deleted_objs) {
242  g_list_free_full(p->deleted_objs, __xml_deleted_obj_free);
243  p->deleted_objs = NULL;
244  }
245  }
246 }
247 
248 
249 static void
250 __xml_private_free(xml_private_t *p)
251 {
252  __xml_private_clean(p);
253  free(p);
254 }
255 
256 static void
257 pcmkDeregisterNode(xmlNodePtr node)
258 {
259  /* need to explicitly avoid our custom _private field cleanup when
260  called from internal XSLT cleanup (xsltApplyStylesheetInternal
261  -> xsltFreeTransformContext -> xsltFreeRVTs -> xmlFreeDoc)
262  onto result tree fragments, represented as standalone documents
263  with otherwise infeasible space-prefixed name (xsltInternals.h:
264  XSLT_MARK_RES_TREE_FRAG) and carrying it's own load at _private
265  field -- later assert on the XML_PRIVATE_MAGIC would explode */
266  if (node->type != XML_DOCUMENT_NODE || node->name == NULL
267  || node->name[0] != ' ') {
268  __xml_private_free(node->_private);
269  }
270 }
271 
272 static void
273 pcmkRegisterNode(xmlNodePtr node)
274 {
275  xml_private_t *p = NULL;
276 
277  switch(node->type) {
278  case XML_ELEMENT_NODE:
279  case XML_DOCUMENT_NODE:
280  case XML_ATTRIBUTE_NODE:
281  case XML_COMMENT_NODE:
282  p = calloc(1, sizeof(xml_private_t));
284  /* Flags will be reset if necessary when tracking is enabled */
285  p->flags |= (xpf_dirty|xpf_created);
286  node->_private = p;
287  break;
288  case XML_TEXT_NODE:
289  case XML_DTD_NODE:
290  case XML_CDATA_SECTION_NODE:
291  break;
292  default:
293  /* Ignore */
294  crm_trace("Ignoring %p %d", node, node->type);
295  CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
296  break;
297  }
298 
299  if(p && pcmk__tracking_xml_changes(node, FALSE)) {
300  /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
301  * not hooked up at the point we are called
302  */
304  __xml_node_dirty(node);
305  }
306 }
307 
308 void
309 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
310 {
311  xml_accept_changes(xml);
312  crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
314  if(enforce_acls) {
315  if(acl_source == NULL) {
316  acl_source = xml;
317  }
319  pcmk__unpack_acl(acl_source, xml, user);
320  pcmk__apply_acl(xml);
321  }
322 }
323 
324 bool xml_tracking_changes(xmlNode * xml)
325 {
326  if(xml == NULL) {
327  return FALSE;
328 
329  } else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
330  return TRUE;
331  }
332  return FALSE;
333 }
334 
335 bool xml_document_dirty(xmlNode *xml)
336 {
337  if(xml != NULL && xml->doc && xml->doc->_private) {
338  xml_private_t *doc = xml->doc->_private;
339 
340  return is_set(doc->flags, xpf_dirty);
341  }
342  return FALSE;
343 }
344 
345 /*
346 <diff format="2.0">
347  <version>
348  <source admin_epoch="1" epoch="2" num_updates="3"/>
349  <target admin_epoch="1" epoch="3" num_updates="0"/>
350  </version>
351  <change operation="add" xpath="/cib/configuration/nodes">
352  <node id="node2" uname="node2" description="foo"/>
353  </change>
354  <change operation="add" xpath="/cib/configuration/nodes/node[node2]">
355  <instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
356  <nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
357  </instance_attributes>
358  </change>
359  <change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
360  <change-list>
361  <change-attr operation="set" name="type" value="member"/>
362  <change-attr operation="unset" name="description"/>
363  </change-list>
364  <change-result>
365  <node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
366  </change-result>
367  </change>
368  <change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
369  <change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
370  <change-list>
371  <change-attr operation="set" name="description" value="some garbage here"/>
372  </change-list>
373  <change-result>
374  <group id="g1" description="some garbage here"/><!-- NOTE: not recursive -->
375  </change-result>
376  </change>
377  <change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
378  <change-list>
379  <change-attr operation="set" name="oper" value="member"/>
380  <change-attr operation="set" name="operation_key" value="Fence_start_0"/>
381  <change-attr operation="set" name="operation" value="start"/>
382  <change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
383  <change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
384  <change-attr operation="set" name="call-id" value="2"/>
385  <change-attr operation="set" name="rc-code" value="0"/>
386  </change-list>
387  <change-result>
388  <lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate" transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
389  </change-result>
390  </change>
391 </diff>
392  */
393 static int __xml_offset(xmlNode *xml)
394 {
395  int position = 0;
396  xmlNode *cIter = NULL;
397 
398  for(cIter = xml; cIter->prev; cIter = cIter->prev) {
399  xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
400 
401  if(is_not_set(p->flags, xpf_skip)) {
402  position++;
403  }
404  }
405 
406  return position;
407 }
408 
409 static int __xml_offset_no_deletions(xmlNode *xml)
410 {
411  int position = 0;
412  xmlNode *cIter = NULL;
413 
414  for(cIter = xml; cIter->prev; cIter = cIter->prev) {
415  xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
416 
417  if(is_not_set(p->flags, xpf_deleted)) {
418  position++;
419  }
420  }
421 
422  return position;
423 }
424 
425 static void
426 __xml_build_changes(xmlNode * xml, xmlNode *patchset)
427 {
428  xmlNode *cIter = NULL;
429  xmlAttr *pIter = NULL;
430  xmlNode *change = NULL;
431  xml_private_t *p = xml->_private;
432 
433  if(patchset && is_set(p->flags, xpf_created)) {
434  int offset = 0;
435  char buffer[XML_BUFFER_SIZE];
436 
437  if (pcmk__element_xpath(NULL, xml->parent, buffer, offset,
438  sizeof(buffer)) > 0) {
439  int position = __xml_offset_no_deletions(xml);
440 
441  change = create_xml_node(patchset, XML_DIFF_CHANGE);
442 
443  crm_xml_add(change, XML_DIFF_OP, "create");
444  crm_xml_add(change, XML_DIFF_PATH, buffer);
445  crm_xml_add_int(change, XML_DIFF_POSITION, position);
446  add_node_copy(change, xml);
447  }
448 
449  return;
450  }
451 
452  for (pIter = pcmk__first_xml_attr(xml); pIter != NULL; pIter = pIter->next) {
453  xmlNode *attr = NULL;
454 
455  p = pIter->_private;
456  if(is_not_set(p->flags, xpf_deleted) && is_not_set(p->flags, xpf_dirty)) {
457  continue;
458  }
459 
460  if(change == NULL) {
461  int offset = 0;
462  char buffer[XML_BUFFER_SIZE];
463 
464  if (pcmk__element_xpath(NULL, xml, buffer, offset,
465  sizeof(buffer)) > 0) {
466  change = create_xml_node(patchset, XML_DIFF_CHANGE);
467 
468  crm_xml_add(change, XML_DIFF_OP, "modify");
469  crm_xml_add(change, XML_DIFF_PATH, buffer);
470 
471  change = create_xml_node(change, XML_DIFF_LIST);
472  }
473  }
474 
475  attr = create_xml_node(change, XML_DIFF_ATTR);
476 
477  crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
478  if(p->flags & xpf_deleted) {
479  crm_xml_add(attr, XML_DIFF_OP, "unset");
480 
481  } else {
482  const char *value = crm_element_value(xml, (const char *)pIter->name);
483 
484  crm_xml_add(attr, XML_DIFF_OP, "set");
485  crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
486  }
487  }
488 
489  if(change) {
490  xmlNode *result = NULL;
491 
492  change = create_xml_node(change->parent, XML_DIFF_RESULT);
493  result = create_xml_node(change, (const char *)xml->name);
494 
495  for (pIter = pcmk__first_xml_attr(xml); pIter != NULL; pIter = pIter->next) {
496  const char *value = crm_element_value(xml, (const char *)pIter->name);
497 
498  p = pIter->_private;
499  if (is_not_set(p->flags, xpf_deleted)) {
500  crm_xml_add(result, (const char *)pIter->name, value);
501  }
502  }
503  }
504 
505  for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
506  __xml_build_changes(cIter, patchset);
507  }
508 
509  p = xml->_private;
510  if(patchset && is_set(p->flags, xpf_moved)) {
511  int offset = 0;
512  char buffer[XML_BUFFER_SIZE];
513 
514  crm_trace("%s.%s moved to position %d", xml->name, ID(xml), __xml_offset(xml));
515  if (pcmk__element_xpath(NULL, xml, buffer, offset,
516  sizeof(buffer)) > 0) {
517  change = create_xml_node(patchset, XML_DIFF_CHANGE);
518 
519  crm_xml_add(change, XML_DIFF_OP, "move");
520  crm_xml_add(change, XML_DIFF_PATH, buffer);
521  crm_xml_add_int(change, XML_DIFF_POSITION, __xml_offset_no_deletions(xml));
522  }
523  }
524 }
525 
526 static void
527 __xml_accept_changes(xmlNode * xml)
528 {
529  xmlNode *cIter = NULL;
530  xmlAttr *pIter = NULL;
531  xml_private_t *p = xml->_private;
532 
533  p->flags = xpf_none;
534  pIter = pcmk__first_xml_attr(xml);
535 
536  while (pIter != NULL) {
537  const xmlChar *name = pIter->name;
538 
539  p = pIter->_private;
540  pIter = pIter->next;
541 
542  if(p->flags & xpf_deleted) {
543  xml_remove_prop(xml, (const char *)name);
544 
545  } else {
546  p->flags = xpf_none;
547  }
548  }
549 
550  for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
551  __xml_accept_changes(cIter);
552  }
553 }
554 
555 static bool
556 is_config_change(xmlNode *xml)
557 {
558  GListPtr gIter = NULL;
559  xml_private_t *p = NULL;
560  xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
561 
562  if(config) {
563  p = config->_private;
564  }
565  if(p && is_set(p->flags, xpf_dirty)) {
566  return TRUE;
567  }
568 
569  if(xml->doc && xml->doc->_private) {
570  p = xml->doc->_private;
571  for(gIter = p->deleted_objs; gIter; gIter = gIter->next) {
572  xml_deleted_obj_t *deleted_obj = gIter->data;
573 
574  if(strstr(deleted_obj->path, "/"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION) != NULL) {
575  return TRUE;
576  }
577  }
578  }
579 
580  return FALSE;
581 }
582 
583 static void
584 xml_repair_v1_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed)
585 {
586  int lpc = 0;
587  xmlNode *cib = NULL;
588  xmlNode *diff_child = NULL;
589 
590  const char *tag = NULL;
591 
592  const char *vfields[] = {
596  };
597 
598  if (local_diff == NULL) {
599  crm_trace("Nothing to do");
600  return;
601  }
602 
603  tag = "diff-removed";
604  diff_child = find_xml_node(local_diff, tag, FALSE);
605  if (diff_child == NULL) {
606  diff_child = create_xml_node(local_diff, tag);
607  }
608 
609  tag = XML_TAG_CIB;
610  cib = find_xml_node(diff_child, tag, FALSE);
611  if (cib == NULL) {
612  cib = create_xml_node(diff_child, tag);
613  }
614 
615  for(lpc = 0; last && lpc < DIMOF(vfields); lpc++){
616  const char *value = crm_element_value(last, vfields[lpc]);
617 
618  crm_xml_add(diff_child, vfields[lpc], value);
619  if(changed || lpc == 2) {
620  crm_xml_add(cib, vfields[lpc], value);
621  }
622  }
623 
624  tag = "diff-added";
625  diff_child = find_xml_node(local_diff, tag, FALSE);
626  if (diff_child == NULL) {
627  diff_child = create_xml_node(local_diff, tag);
628  }
629 
630  tag = XML_TAG_CIB;
631  cib = find_xml_node(diff_child, tag, FALSE);
632  if (cib == NULL) {
633  cib = create_xml_node(diff_child, tag);
634  }
635 
636  for(lpc = 0; next && lpc < DIMOF(vfields); lpc++){
637  const char *value = crm_element_value(next, vfields[lpc]);
638 
639  crm_xml_add(diff_child, vfields[lpc], value);
640  }
641 
642  if (next) {
643  xmlAttrPtr xIter = NULL;
644 
645  for (xIter = next->properties; xIter; xIter = xIter->next) {
646  const char *p_name = (const char *)xIter->name;
647  const char *p_value = crm_element_value(next, p_name);
648 
649  xmlSetProp(cib, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
650  }
651  }
652 
653  crm_log_xml_explicit(local_diff, "Repaired-diff");
654 }
655 
656 static xmlNode *
657 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress)
658 {
659  xmlNode *patchset = diff_xml_object(source, target, suppress);
660 
661  if(patchset) {
663  xml_repair_v1_diff(source, target, patchset, config);
664  crm_xml_add(patchset, "format", "1");
665  }
666  return patchset;
667 }
668 
669 static xmlNode *
670 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
671 {
672  int lpc = 0;
673  GListPtr gIter = NULL;
674  xml_private_t *doc = NULL;
675 
676  xmlNode *v = NULL;
677  xmlNode *version = NULL;
678  xmlNode *patchset = NULL;
679  const char *vfields[] = {
683  };
684 
685  CRM_ASSERT(target);
686  if(xml_document_dirty(target) == FALSE) {
687  return NULL;
688  }
689 
690  CRM_ASSERT(target->doc);
691  doc = target->doc->_private;
692 
693  patchset = create_xml_node(NULL, XML_TAG_DIFF);
694  crm_xml_add_int(patchset, "format", 2);
695 
696  version = create_xml_node(patchset, XML_DIFF_VERSION);
697 
698  v = create_xml_node(version, XML_DIFF_VSOURCE);
699  for(lpc = 0; lpc < DIMOF(vfields); lpc++){
700  const char *value = crm_element_value(source, vfields[lpc]);
701 
702  if(value == NULL) {
703  value = "1";
704  }
705  crm_xml_add(v, vfields[lpc], value);
706  }
707 
708  v = create_xml_node(version, XML_DIFF_VTARGET);
709  for(lpc = 0; lpc < DIMOF(vfields); lpc++){
710  const char *value = crm_element_value(target, vfields[lpc]);
711 
712  if(value == NULL) {
713  value = "1";
714  }
715  crm_xml_add(v, vfields[lpc], value);
716  }
717 
718  for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
719  xml_deleted_obj_t *deleted_obj = gIter->data;
720  xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
721 
722  crm_xml_add(change, XML_DIFF_OP, "delete");
723  crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
724  if (deleted_obj->position >= 0) {
725  crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
726  }
727  }
728 
729  __xml_build_changes(target, patchset);
730  return patchset;
731 }
732 
733 xmlNode *
734 xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
735 {
736  int counter = 0;
737  bool config = FALSE;
738  xmlNode *patch = NULL;
739  const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
740 
741  xml_acl_disable(target);
742  if(xml_document_dirty(target) == FALSE) {
743  crm_trace("No change %d", format);
744  return NULL; /* No change */
745  }
746 
747  config = is_config_change(target);
748  if(config_changed) {
749  *config_changed = config;
750  }
751 
752  if(manage_version && config) {
753  crm_trace("Config changed %d", format);
754  crm_xml_add(target, XML_ATTR_NUMUPDATES, "0");
755 
756  crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
757  crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
758 
759  } else if(manage_version) {
760  crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
761  crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, XML_ATTR_NUMUPDATES));
762  crm_xml_add_int(target, XML_ATTR_NUMUPDATES, counter+1);
763  }
764 
765  if(format == 0) {
766  if (compare_version("3.0.8", version) < 0) {
767  format = 2;
768 
769  } else {
770  format = 1;
771  }
772  crm_trace("Using patch format %d for version: %s", format, version);
773  }
774 
775  switch(format) {
776  case 1:
777  patch = xml_create_patchset_v1(source, target, config, FALSE);
778  break;
779  case 2:
780  patch = xml_create_patchset_v2(source, target);
781  break;
782  default:
783  crm_err("Unknown patch format: %d", format);
784  return NULL;
785  }
786 
787  return patch;
788 }
789 
790 void
791 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
792 {
793  int format = 1;
794  const char *version = NULL;
795  char *digest = NULL;
796 
797  if (patch == NULL || source == NULL || target == NULL) {
798  return;
799  }
800 
801  /* NOTE: We should always call xml_accept_changes() before calculating digest. */
802  /* Otherwise, with an on-tracking dirty target, we could get a wrong digest. */
803  CRM_LOG_ASSERT(xml_document_dirty(target) == FALSE);
804 
805  crm_element_value_int(patch, "format", &format);
806  if (format > 1 && with_digest == FALSE) {
807  return;
808  }
809 
810  version = crm_element_value(source, XML_ATTR_CRM_VERSION);
811  digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
812 
813  crm_xml_add(patch, XML_ATTR_DIGEST, digest);
814  free(digest);
815 
816  return;
817 }
818 
819 static void
820 __xml_log_element(int log_level, const char *file, const char *function, int line,
821  const char *prefix, xmlNode * data, int depth, int options);
822 
823 void
824 xml_log_patchset(uint8_t log_level, const char *function, xmlNode * patchset)
825 {
826  int format = 1;
827  xmlNode *child = NULL;
828  xmlNode *added = NULL;
829  xmlNode *removed = NULL;
830  gboolean is_first = TRUE;
831 
832  int add[] = { 0, 0, 0 };
833  int del[] = { 0, 0, 0 };
834 
835  const char *fmt = NULL;
836  const char *digest = NULL;
837  int options = xml_log_option_formatted;
838 
839  static struct qb_log_callsite *patchset_cs = NULL;
840 
841  if (log_level == LOG_NEVER) {
842  return;
843  }
844  if (patchset_cs == NULL) {
845  patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset", log_level, __LINE__, 0);
846  }
847 
848  if (patchset == NULL) {
849  crm_trace("Empty patch");
850  return;
851 
852  } else if ((log_level != LOG_STDOUT)
853  && !crm_is_callsite_active(patchset_cs, log_level, 0)) {
854  return;
855  }
856 
857  xml_patch_versions(patchset, add, del);
858  fmt = crm_element_value(patchset, "format");
859  digest = crm_element_value(patchset, XML_ATTR_DIGEST);
860 
861  if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
862  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
863  "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
864  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
865  "Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
866 
867  } else if (patchset != NULL && (add[0] || add[1] || add[2])) {
868  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
869  "%s: Local-only Change: %d.%d.%d", function ? function : "",
870  add[0], add[1], add[2]);
871  }
872 
873  crm_element_value_int(patchset, "format", &format);
874  if(format == 2) {
875  xmlNode *change = NULL;
876 
877  for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
878  const char *op = crm_element_value(change, XML_DIFF_OP);
879  const char *xpath = crm_element_value(change, XML_DIFF_PATH);
880 
881  if(op == NULL) {
882  } else if(strcmp(op, "create") == 0) {
883  int lpc = 0, max = 0;
884  char *prefix = crm_strdup_printf("++ %s: ", xpath);
885 
886  max = strlen(prefix);
887  __xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
889 
890  for(lpc = 2; lpc < max; lpc++) {
891  prefix[lpc] = ' ';
892  }
893 
894  __xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
896  free(prefix);
897 
898  } else if(strcmp(op, "move") == 0) {
899  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+~ %s moved to offset %s", xpath, crm_element_value(change, XML_DIFF_POSITION));
900 
901  } else if(strcmp(op, "modify") == 0) {
902  xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
903  char buffer_set[XML_BUFFER_SIZE];
904  char buffer_unset[XML_BUFFER_SIZE];
905  int o_set = 0;
906  int o_unset = 0;
907 
908  buffer_set[0] = 0;
909  buffer_unset[0] = 0;
910  for (child = __xml_first_child(clist); child != NULL; child = __xml_next(child)) {
911  const char *name = crm_element_value(child, "name");
912 
913  op = crm_element_value(child, XML_DIFF_OP);
914  if(op == NULL) {
915  } else if(strcmp(op, "set") == 0) {
916  const char *value = crm_element_value(child, "value");
917 
918  if(o_set > 0) {
919  o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, ", ");
920  }
921  o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, "@%s=%s", name, value);
922 
923  } else if(strcmp(op, "unset") == 0) {
924  if(o_unset > 0) {
925  o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, ", ");
926  }
927  o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, "@%s", name);
928  }
929  }
930  if(o_set) {
931  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+ %s: %s", xpath, buffer_set);
932  }
933  if(o_unset) {
934  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s: %s", xpath, buffer_unset);
935  }
936 
937  } else if(strcmp(op, "delete") == 0) {
938  int position = -1;
939 
940  crm_element_value_int(change, XML_DIFF_POSITION, &position);
941  if (position >= 0) {
942  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)", xpath, position);
943 
944  } else {
945  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", xpath);
946  }
947  }
948  }
949  return;
950  }
951 
952  if (log_level < LOG_DEBUG || function == NULL) {
953  options |= xml_log_option_diff_short;
954  }
955 
956  removed = find_xml_node(patchset, "diff-removed", FALSE);
957  for (child = __xml_first_child(removed); child != NULL; child = __xml_next(child)) {
958  log_data_element(log_level, __FILE__, function, __LINE__, "- ", child, 0,
959  options | xml_log_option_diff_minus);
960  if (is_first) {
961  is_first = FALSE;
962  } else {
963  do_crm_log_alias(log_level, __FILE__, function, __LINE__, " --- ");
964  }
965  }
966 
967  is_first = TRUE;
968  added = find_xml_node(patchset, "diff-added", FALSE);
969  for (child = __xml_first_child(added); child != NULL; child = __xml_next(child)) {
970  log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child, 0,
971  options | xml_log_option_diff_plus);
972  if (is_first) {
973  is_first = FALSE;
974  } else {
975  do_crm_log_alias(log_level, __FILE__, function, __LINE__, " +++ ");
976  }
977  }
978 }
979 
980 void
981 xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
982 {
983  GListPtr gIter = NULL;
984  xml_private_t *doc = NULL;
985 
986  if (log_level == LOG_NEVER) {
987  return;
988  }
989 
990  CRM_ASSERT(xml);
991  CRM_ASSERT(xml->doc);
992 
993  doc = xml->doc->_private;
994  if(is_not_set(doc->flags, xpf_dirty)) {
995  return;
996  }
997 
998  for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
999  xml_deleted_obj_t *deleted_obj = gIter->data;
1000 
1001  if (deleted_obj->position >= 0) {
1002  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)",
1003  deleted_obj->path, deleted_obj->position);
1004 
1005  } else {
1006  do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s",
1007  deleted_obj->path);
1008  }
1009  }
1010 
1011  log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
1013 }
1014 
1015 void
1016 xml_accept_changes(xmlNode * xml)
1017 {
1018  xmlNode *top = NULL;
1019  xml_private_t *doc = NULL;
1020 
1021  if(xml == NULL) {
1022  return;
1023  }
1024 
1025  crm_trace("Accepting changes to %p", xml);
1026  doc = xml->doc->_private;
1027  top = xmlDocGetRootElement(xml->doc);
1028 
1029  __xml_private_clean(xml->doc->_private);
1030 
1031  if(is_not_set(doc->flags, xpf_dirty)) {
1032  doc->flags = xpf_none;
1033  return;
1034  }
1035 
1036  doc->flags = xpf_none;
1037  __xml_accept_changes(top);
1038 }
1039 
1040 static xmlNode *
1041 find_element(xmlNode *haystack, xmlNode *needle, gboolean exact)
1042 {
1043  CRM_CHECK(needle != NULL, return NULL);
1044  return (needle->type == XML_COMMENT_NODE)?
1045  find_xml_comment(haystack, needle, exact)
1046  : find_entity(haystack, crm_element_name(needle), ID(needle));
1047 }
1048 
1049 /* Simplified version for applying v1-style XML patches */
1050 static void
1051 __subtract_xml_object(xmlNode * target, xmlNode * patch)
1052 {
1053  xmlNode *patch_child = NULL;
1054  xmlNode *cIter = NULL;
1055  xmlAttrPtr xIter = NULL;
1056 
1057  char *id = NULL;
1058  const char *name = NULL;
1059  const char *value = NULL;
1060 
1061  if (target == NULL || patch == NULL) {
1062  return;
1063  }
1064 
1065  if (target->type == XML_COMMENT_NODE) {
1066  gboolean dummy;
1067 
1068  subtract_xml_comment(target->parent, target, patch, &dummy);
1069  }
1070 
1071  name = crm_element_name(target);
1072  CRM_CHECK(name != NULL, return);
1073  CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
1074  CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
1075 
1076  /* check for XML_DIFF_MARKER in a child */
1077  id = crm_element_value_copy(target, XML_ATTR_ID);
1078  value = crm_element_value(patch, XML_DIFF_MARKER);
1079  if (value != NULL && strcmp(value, "removed:top") == 0) {
1080  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1081  free_xml(target);
1082  free(id);
1083  return;
1084  }
1085 
1086  for (xIter = pcmk__first_xml_attr(patch); xIter != NULL; xIter = xIter->next) {
1087  const char *p_name = (const char *)xIter->name;
1088 
1089  /* Removing and then restoring the id field would change the ordering of properties */
1090  if (safe_str_neq(p_name, XML_ATTR_ID)) {
1091  xml_remove_prop(target, p_name);
1092  }
1093  }
1094 
1095  /* changes to child objects */
1096  cIter = __xml_first_child(target);
1097  while (cIter) {
1098  xmlNode *target_child = cIter;
1099 
1100  cIter = __xml_next(cIter);
1101  patch_child = find_element(patch, target_child, FALSE);
1102  __subtract_xml_object(target_child, patch_child);
1103  }
1104  free(id);
1105 }
1106 
1107 static void
1108 __add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * patch)
1109 {
1110  xmlNode *patch_child = NULL;
1111  xmlNode *target_child = NULL;
1112  xmlAttrPtr xIter = NULL;
1113 
1114  const char *id = NULL;
1115  const char *name = NULL;
1116  const char *value = NULL;
1117 
1118  if (patch == NULL) {
1119  return;
1120  } else if (parent == NULL && target == NULL) {
1121  return;
1122  }
1123 
1124  /* check for XML_DIFF_MARKER in a child */
1125  value = crm_element_value(patch, XML_DIFF_MARKER);
1126  if (target == NULL
1127  && value != NULL
1128  && strcmp(value, "added:top") == 0) {
1129  id = ID(patch);
1130  name = crm_element_name(patch);
1131  crm_trace("We are the root of the addition: %s.id=%s", name, id);
1132  add_node_copy(parent, patch);
1133  return;
1134 
1135  } else if(target == NULL) {
1136  id = ID(patch);
1137  name = crm_element_name(patch);
1138  crm_err("Could not locate: %s.id=%s", name, id);
1139  return;
1140  }
1141 
1142  if (target->type == XML_COMMENT_NODE) {
1143  add_xml_comment(parent, target, patch);
1144  }
1145 
1146  name = crm_element_name(target);
1147  CRM_CHECK(name != NULL, return);
1148  CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
1149  CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
1150 
1151  for (xIter = pcmk__first_xml_attr(patch); xIter != NULL; xIter = xIter->next) {
1152  const char *p_name = (const char *)xIter->name;
1153  const char *p_value = crm_element_value(patch, p_name);
1154 
1155  xml_remove_prop(target, p_name); /* Preserve the patch order */
1156  crm_xml_add(target, p_name, p_value);
1157  }
1158 
1159  /* changes to child objects */
1160  for (patch_child = __xml_first_child(patch); patch_child != NULL;
1161  patch_child = __xml_next(patch_child)) {
1162 
1163  target_child = find_element(target, patch_child, FALSE);
1164  __add_xml_object(target, target_child, patch_child);
1165  }
1166 }
1167 
1179 static bool
1180 find_patch_xml_node(xmlNode *patchset, int format, bool added,
1181  xmlNode **patch_node)
1182 {
1183  xmlNode *cib_node;
1184  const char *label;
1185 
1186  switch(format) {
1187  case 1:
1188  label = added? "diff-added" : "diff-removed";
1189  *patch_node = find_xml_node(patchset, label, FALSE);
1190  cib_node = find_xml_node(*patch_node, "cib", FALSE);
1191  if (cib_node != NULL) {
1192  *patch_node = cib_node;
1193  }
1194  break;
1195  case 2:
1196  label = added? "target" : "source";
1197  *patch_node = find_xml_node(patchset, "version", FALSE);
1198  *patch_node = find_xml_node(*patch_node, label, FALSE);
1199  break;
1200  default:
1201  crm_warn("Unknown patch format: %d", format);
1202  *patch_node = NULL;
1203  return FALSE;
1204  }
1205  return TRUE;
1206 }
1207 
1208 bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
1209 {
1210  int lpc = 0;
1211  int format = 1;
1212  xmlNode *tmp = NULL;
1213 
1214  const char *vfields[] = {
1218  };
1219 
1220 
1221  crm_element_value_int(patchset, "format", &format);
1222 
1223  /* Process removals */
1224  if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
1225  return -EINVAL;
1226  }
1227  if (tmp) {
1228  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1229  crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
1230  crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
1231  }
1232  }
1233 
1234  /* Process additions */
1235  if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
1236  return -EINVAL;
1237  }
1238  if (tmp) {
1239  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1240  crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
1241  crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
1242  }
1243  }
1244 
1245  return pcmk_ok;
1246 }
1247 
1248 static int
1249 xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
1250 {
1251  int lpc = 0;
1252  bool changed = FALSE;
1253 
1254  int this[] = { 0, 0, 0 };
1255  int add[] = { 0, 0, 0 };
1256  int del[] = { 0, 0, 0 };
1257 
1258  const char *vfields[] = {
1262  };
1263 
1264  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1265  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
1266  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
1267  if (this[lpc] < 0) {
1268  this[lpc] = 0;
1269  }
1270  }
1271 
1272  /* Set some defaults in case nothing is present */
1273  add[0] = this[0];
1274  add[1] = this[1];
1275  add[2] = this[2] + 1;
1276  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1277  del[lpc] = this[lpc];
1278  }
1279 
1280  xml_patch_versions(patchset, add, del);
1281 
1282  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1283  if(this[lpc] < del[lpc]) {
1284  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc],
1285  this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]);
1286  return -pcmk_err_diff_resync;
1287 
1288  } else if(this[lpc] > del[lpc]) {
1289  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc],
1290  this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset);
1291  crm_log_xml_info(patchset, "OldPatch");
1292  return -pcmk_err_old_data;
1293  }
1294  }
1295 
1296  for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1297  if(add[lpc] > del[lpc]) {
1298  changed = TRUE;
1299  }
1300  }
1301 
1302  if(changed == FALSE) {
1303  crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]);
1304  return -pcmk_err_old_data;
1305  }
1306 
1307  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
1308  add[0], add[1], add[2], this[0], this[1], this[2]);
1309  return pcmk_ok;
1310 }
1311 
1312 static int
1313 xml_apply_patchset_v1(xmlNode *xml, xmlNode *patchset)
1314 {
1315  int rc = pcmk_ok;
1316  int root_nodes_seen = 0;
1317 
1318  xmlNode *child_diff = NULL;
1319  xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
1320  xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
1321  xmlNode *old = copy_xml(xml);
1322 
1323  crm_trace("Subtraction Phase");
1324  for (child_diff = __xml_first_child(removed); child_diff != NULL;
1325  child_diff = __xml_next(child_diff)) {
1326  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
1327  if (root_nodes_seen == 0) {
1328  __subtract_xml_object(xml, child_diff);
1329  }
1330  root_nodes_seen++;
1331  }
1332 
1333  if (root_nodes_seen > 1) {
1334  crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
1335  rc = -ENOTUNIQ;
1336  }
1337 
1338  root_nodes_seen = 0;
1339  crm_trace("Addition Phase");
1340  if (rc == pcmk_ok) {
1341  xmlNode *child_diff = NULL;
1342 
1343  for (child_diff = __xml_first_child(added); child_diff != NULL;
1344  child_diff = __xml_next(child_diff)) {
1345  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
1346  if (root_nodes_seen == 0) {
1347  __add_xml_object(NULL, xml, child_diff);
1348  }
1349  root_nodes_seen++;
1350  }
1351  }
1352 
1353  if (root_nodes_seen > 1) {
1354  crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
1355  rc = -ENOTUNIQ;
1356  }
1357 
1358  purge_diff_markers(xml); /* Purge prior to checking the digest */
1359 
1360  free_xml(old);
1361  return rc;
1362 }
1363 
1364 static xmlNode *
1365 __first_xml_child_match(xmlNode *parent, const char *name, const char *id, int position)
1366 {
1367  xmlNode *cIter = NULL;
1368 
1369  for (cIter = __xml_first_child(parent); cIter != NULL; cIter = __xml_next(cIter)) {
1370  if(strcmp((const char *)cIter->name, name) != 0) {
1371  continue;
1372  } else if(id) {
1373  const char *cid = ID(cIter);
1374  if(cid == NULL || strcmp(cid, id) != 0) {
1375  continue;
1376  }
1377  }
1378 
1379  /* The "position" makes sense only for XML comments for now */
1380  if (cIter->type == XML_COMMENT_NODE
1381  && position >= 0
1382  && __xml_offset(cIter) != position) {
1383  continue;
1384  }
1385 
1386  return cIter;
1387  }
1388  return NULL;
1389 }
1390 
1404 static xmlNode *
1405 __xml_find_path(xmlNode *top, const char *key, int target_position)
1406 {
1407  xmlNode *target = (xmlNode*) top->doc;
1408  const char *current = key;
1409  char *section;
1410  char *remainder;
1411  char *id;
1412  char *tag;
1413  char *path = NULL;
1414  int rc;
1415  size_t key_len;
1416 
1417  CRM_CHECK(key != NULL, return NULL);
1418  key_len = strlen(key);
1419 
1420  /* These are scanned from key after a slash, so they can't be bigger
1421  * than key_len - 1 characters plus a null terminator.
1422  */
1423 
1424  remainder = calloc(key_len, sizeof(char));
1425  CRM_ASSERT(remainder != NULL);
1426 
1427  section = calloc(key_len, sizeof(char));
1428  CRM_ASSERT(section != NULL);
1429 
1430  id = calloc(key_len, sizeof(char));
1431  CRM_ASSERT(id != NULL);
1432 
1433  tag = calloc(key_len, sizeof(char));
1434  CRM_ASSERT(tag != NULL);
1435 
1436  do {
1437  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1438  rc = sscanf(current, "/%[^/]%s", section, remainder);
1439  if (rc > 0) {
1440  // Separate FIRST_COMPONENT into TAG[@id='ID']
1441  int f = sscanf(section, "%[^[][@id='%[^']", tag, id);
1442  int current_position = -1;
1443 
1444  /* The target position is for the final component tag, so only use
1445  * it if there is nothing left to search after this component.
1446  */
1447  if ((rc == 1) && (target_position >= 0)) {
1448  current_position = target_position;
1449  }
1450 
1451  switch (f) {
1452  case 1:
1453  target = __first_xml_child_match(target, tag, NULL, current_position);
1454  break;
1455  case 2:
1456  target = __first_xml_child_match(target, tag, id, current_position);
1457  break;
1458  default:
1459  // This should not be possible
1460  target = NULL;
1461  break;
1462  }
1463  current = remainder;
1464  }
1465 
1466  // Continue if something remains to search, and we've matched so far
1467  } while ((rc == 2) && target);
1468 
1469  if (target) {
1470  crm_trace("Found %s for %s",
1471  (path = (char *) xmlGetNodePath(target)), key);
1472  free(path);
1473  } else {
1474  crm_debug("No match for %s", key);
1475  }
1476 
1477  free(remainder);
1478  free(section);
1479  free(tag);
1480  free(id);
1481  return target;
1482 }
1483 
1484 typedef struct xml_change_obj_s {
1485  xmlNode *change;
1486  xmlNode *match;
1488 
1489 static gint
1490 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
1491 {
1492  const xml_change_obj_t *change_obj_a = a;
1493  const xml_change_obj_t *change_obj_b = b;
1494  int position_a = -1;
1495  int position_b = -1;
1496 
1497  crm_element_value_int(change_obj_a->change, XML_DIFF_POSITION, &position_a);
1498  crm_element_value_int(change_obj_b->change, XML_DIFF_POSITION, &position_b);
1499 
1500  if (position_a < position_b) {
1501  return -1;
1502 
1503  } else if (position_a > position_b) {
1504  return 1;
1505  }
1506 
1507  return 0;
1508 }
1509 
1510 static int
1511 xml_apply_patchset_v2(xmlNode *xml, xmlNode *patchset)
1512 {
1513  int rc = pcmk_ok;
1514  xmlNode *change = NULL;
1515  GListPtr change_objs = NULL;
1516  GListPtr gIter = NULL;
1517 
1518  for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
1519  xmlNode *match = NULL;
1520  const char *op = crm_element_value(change, XML_DIFF_OP);
1521  const char *xpath = crm_element_value(change, XML_DIFF_PATH);
1522  int position = -1;
1523 
1524  if(op == NULL) {
1525  continue;
1526  }
1527 
1528  crm_trace("Processing %s %s", change->name, op);
1529 
1530  // "delete" changes for XML comments are generated with "position"
1531  if(strcmp(op, "delete") == 0) {
1532  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1533  }
1534  match = __xml_find_path(xml, xpath, position);
1535  crm_trace("Performing %s on %s with %p", op, xpath, match);
1536 
1537  if(match == NULL && strcmp(op, "delete") == 0) {
1538  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1539  continue;
1540 
1541  } else if(match == NULL) {
1542  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1543  rc = -pcmk_err_diff_failed;
1544  continue;
1545 
1546  } else if (strcmp(op, "create") == 0 || strcmp(op, "move") == 0) {
1547  // Delay the adding of a "create" object
1548  xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
1549 
1550  CRM_ASSERT(change_obj != NULL);
1551 
1552  change_obj->change = change;
1553  change_obj->match = match;
1554 
1555  change_objs = g_list_append(change_objs, change_obj);
1556 
1557  if (strcmp(op, "move") == 0) {
1558  // Temporarily put the "move" object after the last sibling
1559  if (match->parent != NULL && match->parent->last != NULL) {
1560  xmlAddNextSibling(match->parent->last, match);
1561  }
1562  }
1563 
1564  } else if(strcmp(op, "delete") == 0) {
1565  free_xml(match);
1566 
1567  } else if(strcmp(op, "modify") == 0) {
1568  xmlAttr *pIter = pcmk__first_xml_attr(match);
1569  xmlNode *attrs = __xml_first_child(first_named_child(change, XML_DIFF_RESULT));
1570 
1571  if(attrs == NULL) {
1572  rc = -ENOMSG;
1573  continue;
1574  }
1575  while(pIter != NULL) {
1576  const char *name = (const char *)pIter->name;
1577 
1578  pIter = pIter->next;
1579  xml_remove_prop(match, name);
1580  }
1581 
1582  for (pIter = pcmk__first_xml_attr(attrs); pIter != NULL; pIter = pIter->next) {
1583  const char *name = (const char *)pIter->name;
1584  const char *value = crm_element_value(attrs, name);
1585 
1586  crm_xml_add(match, name, value);
1587  }
1588 
1589  } else {
1590  crm_err("Unknown operation: %s", op);
1591  rc = -pcmk_err_diff_failed;
1592  }
1593  }
1594 
1595  // Changes should be generated in the right order. Double checking.
1596  change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
1597 
1598  for (gIter = change_objs; gIter; gIter = gIter->next) {
1599  xml_change_obj_t *change_obj = gIter->data;
1600  xmlNode *match = change_obj->match;
1601  const char *op = NULL;
1602  const char *xpath = NULL;
1603 
1604  change = change_obj->change;
1605 
1606  op = crm_element_value(change, XML_DIFF_OP);
1607  xpath = crm_element_value(change, XML_DIFF_PATH);
1608 
1609  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1610 
1611  if(strcmp(op, "create") == 0) {
1612  int position = 0;
1613  xmlNode *child = NULL;
1614  xmlNode *match_child = NULL;
1615 
1616  match_child = match->children;
1617  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1618 
1619  while(match_child && position != __xml_offset(match_child)) {
1620  match_child = match_child->next;
1621  }
1622 
1623  child = xmlDocCopyNode(change->children, match->doc, 1);
1624  if(match_child) {
1625  crm_trace("Adding %s at position %d", child->name, position);
1626  xmlAddPrevSibling(match_child, child);
1627 
1628  } else if(match->last) { /* Add to the end */
1629  crm_trace("Adding %s at position %d (end)", child->name, position);
1630  xmlAddNextSibling(match->last, child);
1631 
1632  } else {
1633  crm_trace("Adding %s at position %d (first)", child->name, position);
1634  CRM_LOG_ASSERT(position == 0);
1635  xmlAddChild(match, child);
1636  }
1637  crm_node_created(child);
1638 
1639  } else if(strcmp(op, "move") == 0) {
1640  int position = 0;
1641 
1642  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1643  if(position != __xml_offset(match)) {
1644  xmlNode *match_child = NULL;
1645  int p = position;
1646 
1647  if(p > __xml_offset(match)) {
1648  p++; /* Skip ourselves */
1649  }
1650 
1651  CRM_ASSERT(match->parent != NULL);
1652  match_child = match->parent->children;
1653 
1654  while(match_child && p != __xml_offset(match_child)) {
1655  match_child = match_child->next;
1656  }
1657 
1658  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1659  match->name, position, __xml_offset(match), match->prev,
1660  match_child?"next":"last", match_child?match_child:match->parent->last);
1661 
1662  if(match_child) {
1663  xmlAddPrevSibling(match_child, match);
1664 
1665  } else {
1666  CRM_ASSERT(match->parent->last != NULL);
1667  xmlAddNextSibling(match->parent->last, match);
1668  }
1669 
1670  } else {
1671  crm_trace("%s is already in position %d", match->name, position);
1672  }
1673 
1674  if(position != __xml_offset(match)) {
1675  crm_err("Moved %s.%s to position %d instead of %d (%p)",
1676  match->name, ID(match), __xml_offset(match), position, match->prev);
1677  rc = -pcmk_err_diff_failed;
1678  }
1679  }
1680  }
1681 
1682  g_list_free_full(change_objs, free);
1683  return rc;
1684 }
1685 
1686 int
1687 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1688 {
1689  int format = 1;
1690  int rc = pcmk_ok;
1691  xmlNode *old = NULL;
1692  const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1693 
1694  if(patchset == NULL) {
1695  return rc;
1696  }
1697 
1698  xml_log_patchset(LOG_TRACE, __FUNCTION__, patchset);
1699 
1700  crm_element_value_int(patchset, "format", &format);
1701  if(check_version) {
1702  rc = xml_patch_version_check(xml, patchset, format);
1703  if(rc != pcmk_ok) {
1704  return rc;
1705  }
1706  }
1707 
1708  if(digest) {
1709  /* Make it available for logging if the result doesn't have the expected digest */
1710  old = copy_xml(xml);
1711  }
1712 
1713  if(rc == pcmk_ok) {
1714  switch(format) {
1715  case 1:
1716  rc = xml_apply_patchset_v1(xml, patchset);
1717  break;
1718  case 2:
1719  rc = xml_apply_patchset_v2(xml, patchset);
1720  break;
1721  default:
1722  crm_err("Unknown patch format: %d", format);
1723  rc = -EINVAL;
1724  }
1725  }
1726 
1727  if(rc == pcmk_ok && digest) {
1728  static struct qb_log_callsite *digest_cs = NULL;
1729 
1730  char *new_digest = NULL;
1731  char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
1732 
1733  if (digest_cs == NULL) {
1734  digest_cs =
1735  qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
1737  }
1738 
1739  new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1740  if (safe_str_neq(new_digest, digest)) {
1741  crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest);
1742  rc = -pcmk_err_diff_failed;
1743 
1744  if (digest_cs && digest_cs->targets) {
1745  save_xml_to_file(old, "PatchDigest:input", NULL);
1746  save_xml_to_file(xml, "PatchDigest:result", NULL);
1747  save_xml_to_file(patchset,"PatchDigest:diff", NULL);
1748 
1749  } else {
1750  crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
1751  }
1752 
1753  } else {
1754  crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest);
1755  }
1756  free(new_digest);
1757  free(version);
1758  }
1759  free_xml(old);
1760  return rc;
1761 }
1762 
1763 xmlNode *
1764 find_xml_node(xmlNode * root, const char *search_path, gboolean must_find)
1765 {
1766  xmlNode *a_child = NULL;
1767  const char *name = "NULL";
1768 
1769  if (root != NULL) {
1770  name = crm_element_name(root);
1771  }
1772 
1773  if (search_path == NULL) {
1774  crm_warn("Will never find <NULL>");
1775  return NULL;
1776  }
1777 
1778  for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
1779  if (strcmp((const char *)a_child->name, search_path) == 0) {
1780 /* crm_trace("returning node (%s).", crm_element_name(a_child)); */
1781  return a_child;
1782  }
1783  }
1784 
1785  if (must_find) {
1786  crm_warn("Could not find %s in %s.", search_path, name);
1787  } else if (root != NULL) {
1788  crm_trace("Could not find %s in %s.", search_path, name);
1789  } else {
1790  crm_trace("Could not find %s in <NULL>.", search_path);
1791  }
1792 
1793  return NULL;
1794 }
1795 
1796 /* As the name suggests, the perfect match is required for both node
1797  name and fully specified attribute, otherwise, when attribute not
1798  specified, the outcome is the first node matching on the name. */
1799 static xmlNode *
1800 find_entity_by_attr_or_just_name(xmlNode *parent, const char *node_name,
1801  const char *attr_n, const char *attr_v)
1802 {
1803  xmlNode *child;
1804 
1805  /* ensure attr_v specified when attr_n is */
1806  CRM_CHECK(attr_n == NULL || attr_v != NULL, return NULL);
1807 
1808  for (child = __xml_first_child(parent); child != NULL; child = __xml_next(child)) {
1809  /* XXX uncertain if the first check is strictly necessary here */
1810  if (node_name == NULL || !strcmp((const char *) child->name, node_name)) {
1811  if (attr_n == NULL
1812  || crm_str_eq(crm_element_value(child, attr_n), attr_v, TRUE)) {
1813  return child;
1814  }
1815  }
1816  }
1817 
1818  crm_trace("node <%s%s%s%s%s> not found in %s", crm_str(node_name),
1819  attr_n ? " " : "",
1820  attr_n ? attr_n : "",
1821  attr_n ? "=" : "",
1822  attr_n ? attr_v : "",
1823  crm_element_name(parent));
1824 
1825  return NULL;
1826 }
1827 
1828 xmlNode *
1829 find_entity(xmlNode *parent, const char *node_name, const char *id)
1830 {
1831  return find_entity_by_attr_or_just_name(parent, node_name,
1832  (id == NULL) ? id : XML_ATTR_ID, id);
1833 }
1834 
1835 void
1836 copy_in_properties(xmlNode * target, xmlNode * src)
1837 {
1838  if (src == NULL) {
1839  crm_warn("No node to copy properties from");
1840 
1841  } else if (target == NULL) {
1842  crm_err("No node to copy properties into");
1843 
1844  } else {
1845  xmlAttrPtr pIter = NULL;
1846 
1847  for (pIter = pcmk__first_xml_attr(src); pIter != NULL; pIter = pIter->next) {
1848  const char *p_name = (const char *)pIter->name;
1849  const char *p_value = pcmk__xml_attr_value(pIter);
1850 
1851  expand_plus_plus(target, p_name, p_value);
1852  }
1853  }
1854 
1855  return;
1856 }
1857 
1858 void
1859 fix_plus_plus_recursive(xmlNode * target)
1860 {
1861  /* TODO: Remove recursion and use xpath searches for value++ */
1862  xmlNode *child = NULL;
1863  xmlAttrPtr pIter = NULL;
1864 
1865  for (pIter = pcmk__first_xml_attr(target); pIter != NULL; pIter = pIter->next) {
1866  const char *p_name = (const char *)pIter->name;
1867  const char *p_value = pcmk__xml_attr_value(pIter);
1868 
1869  expand_plus_plus(target, p_name, p_value);
1870  }
1871  for (child = __xml_first_child(target); child != NULL; child = __xml_next(child)) {
1872  fix_plus_plus_recursive(child);
1873  }
1874 }
1875 
1876 void
1877 expand_plus_plus(xmlNode * target, const char *name, const char *value)
1878 {
1879  int offset = 1;
1880  int name_len = 0;
1881  int int_value = 0;
1882  int value_len = 0;
1883 
1884  const char *old_value = NULL;
1885 
1886  if (value == NULL || name == NULL) {
1887  return;
1888  }
1889 
1890  old_value = crm_element_value(target, name);
1891 
1892  if (old_value == NULL) {
1893  /* if no previous value, set unexpanded */
1894  goto set_unexpanded;
1895 
1896  } else if (strstr(value, name) != value) {
1897  goto set_unexpanded;
1898  }
1899 
1900  name_len = strlen(name);
1901  value_len = strlen(value);
1902  if (value_len < (name_len + 2)
1903  || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
1904  goto set_unexpanded;
1905  }
1906 
1907  /* if we are expanding ourselves,
1908  * then no previous value was set and leave int_value as 0
1909  */
1910  if (old_value != value) {
1911  int_value = char2score(old_value);
1912  }
1913 
1914  if (value[name_len + 1] != '+') {
1915  const char *offset_s = value + (name_len + 2);
1916 
1917  offset = char2score(offset_s);
1918  }
1919  int_value += offset;
1920 
1921  if (int_value > INFINITY) {
1922  int_value = (int)INFINITY;
1923  }
1924 
1925  crm_xml_add_int(target, name, int_value);
1926  return;
1927 
1928  set_unexpanded:
1929  if (old_value == value) {
1930  /* the old value is already set, nothing to do */
1931  return;
1932  }
1933  crm_xml_add(target, name, value);
1934  return;
1935 }
1936 
1937 xmlDoc *
1938 getDocPtr(xmlNode * node)
1939 {
1940  xmlDoc *doc = NULL;
1941 
1942  CRM_CHECK(node != NULL, return NULL);
1943 
1944  doc = node->doc;
1945  if (doc == NULL) {
1946  doc = xmlNewDoc((pcmkXmlStr) "1.0");
1947  xmlDocSetRootElement(doc, node);
1948  xmlSetTreeDoc(node, doc);
1949  }
1950  return doc;
1951 }
1952 
1953 xmlNode *
1954 add_node_copy(xmlNode * parent, xmlNode * src_node)
1955 {
1956  xmlNode *child = NULL;
1957  xmlDoc *doc = getDocPtr(parent);
1958 
1959  CRM_CHECK(src_node != NULL, return NULL);
1960 
1961  child = xmlDocCopyNode(src_node, doc, 1);
1962  xmlAddChild(parent, child);
1963  crm_node_created(child);
1964  return child;
1965 }
1966 
1967 int
1968 add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
1969 {
1970  add_node_copy(parent, child);
1971  free_xml(child);
1972  return 1;
1973 }
1974 
1975 xmlNode *
1976 create_xml_node(xmlNode * parent, const char *name)
1977 {
1978  xmlDoc *doc = NULL;
1979  xmlNode *node = NULL;
1980 
1981  if (name == NULL || name[0] == 0) {
1982  CRM_CHECK(name != NULL && name[0] == 0, return NULL);
1983  return NULL;
1984  }
1985 
1986  if (parent == NULL) {
1987  doc = xmlNewDoc((pcmkXmlStr) "1.0");
1988  node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
1989  xmlDocSetRootElement(doc, node);
1990 
1991  } else {
1992  doc = getDocPtr(parent);
1993  node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
1994  xmlAddChild(parent, node);
1995  }
1996  crm_node_created(node);
1997  return node;
1998 }
1999 
2000 xmlNode *
2001 pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
2002 {
2003  xmlNode *node = create_xml_node(parent, name);
2004 
2005  if (node != NULL) {
2006  xmlNodeSetContent(node, (pcmkXmlStr) content);
2007  }
2008 
2009  return node;
2010 }
2011 
2012 xmlNode *
2013 pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
2014  const char *class_name, const char *text)
2015 {
2016  xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
2017 
2018  if (class_name != NULL) {
2019  xmlSetProp(node, (pcmkXmlStr) "class", (pcmkXmlStr) class_name);
2020  }
2021 
2022  if (id != NULL) {
2023  xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) id);
2024  }
2025 
2026  return node;
2027 }
2028 
2029 int
2030 pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer,
2031  int offset, size_t buffer_size)
2032 {
2033  const char *id = ID(xml);
2034 
2035  if(offset == 0 && prefix == NULL && xml->parent) {
2036  offset = pcmk__element_xpath(NULL, xml->parent, buffer, offset,
2037  buffer_size);
2038  }
2039 
2040  if(id) {
2041  offset += snprintf(buffer + offset, buffer_size - offset,
2042  "/%s[@id='%s']", (const char *) xml->name, id);
2043  } else if(xml->name) {
2044  offset += snprintf(buffer + offset, buffer_size - offset,
2045  "/%s", (const char *) xml->name);
2046  }
2047 
2048  return offset;
2049 }
2050 
2051 char *
2052 xml_get_path(xmlNode *xml)
2053 {
2054  int offset = 0;
2055  char buffer[XML_BUFFER_SIZE];
2056 
2057  if (pcmk__element_xpath(NULL, xml, buffer, offset, sizeof(buffer)) > 0) {
2058  return strdup(buffer);
2059  }
2060  return NULL;
2061 }
2062 
2068 void
2070 {
2071  xmlUnlinkNode(xml); // Detaches from parent and siblings
2072  xmlFreeNode(xml); // Frees
2073 }
2074 
2075 static void
2076 free_xml_with_position(xmlNode * child, int position)
2077 {
2078  if (child != NULL) {
2079  xmlNode *top = NULL;
2080  xmlDoc *doc = child->doc;
2081  xml_private_t *p = child->_private;
2082 
2083  if (doc != NULL) {
2084  top = xmlDocGetRootElement(doc);
2085  }
2086 
2087  if (doc != NULL && top == child) {
2088  /* Free everything */
2089  xmlFreeDoc(doc);
2090 
2091  } else if (pcmk__check_acl(child, NULL, xpf_acl_write) == FALSE) {
2092  int offset = 0;
2093  char buffer[XML_BUFFER_SIZE];
2094 
2095  pcmk__element_xpath(NULL, child, buffer, offset, sizeof(buffer));
2096  crm_trace("Cannot remove %s %x", buffer, p->flags);
2097  return;
2098 
2099  } else {
2100  if (doc && pcmk__tracking_xml_changes(child, FALSE)
2101  && is_not_set(p->flags, xpf_created)) {
2102  int offset = 0;
2103  char buffer[XML_BUFFER_SIZE];
2104 
2105  if (pcmk__element_xpath(NULL, child, buffer, offset,
2106  sizeof(buffer)) > 0) {
2107  xml_deleted_obj_t *deleted_obj = calloc(1, sizeof(xml_deleted_obj_t));
2108 
2109  crm_trace("Deleting %s %p from %p", buffer, child, doc);
2110 
2111  deleted_obj->path = strdup(buffer);
2112 
2113  deleted_obj->position = -1;
2114  /* Record the "position" only for XML comments for now */
2115  if (child->type == XML_COMMENT_NODE) {
2116  if (position >= 0) {
2117  deleted_obj->position = position;
2118 
2119  } else {
2120  deleted_obj->position = __xml_offset(child);
2121  }
2122  }
2123 
2124  p = doc->_private;
2125  p->deleted_objs = g_list_append(p->deleted_objs, deleted_obj);
2126  pcmk__set_xml_flag(child, xpf_dirty);
2127  }
2128  }
2129  pcmk_free_xml_subtree(child);
2130  }
2131  }
2132 }
2133 
2134 
2135 void
2136 free_xml(xmlNode * child)
2137 {
2138  free_xml_with_position(child, -1);
2139 }
2140 
2141 xmlNode *
2142 copy_xml(xmlNode * src)
2143 {
2144  xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
2145  xmlNode *copy = xmlDocCopyNode(src, doc, 1);
2146 
2147  xmlDocSetRootElement(doc, copy);
2148  xmlSetTreeDoc(copy, doc);
2149  return copy;
2150 }
2151 
2152 static void
2153 crm_xml_err(void *ctx, const char *fmt, ...)
2154 G_GNUC_PRINTF(2, 3);
2155 
2156 static void
2157 crm_xml_err(void *ctx, const char *fmt, ...)
2158 {
2159  va_list ap;
2160  static struct qb_log_callsite *xml_error_cs = NULL;
2161 
2162  if (xml_error_cs == NULL) {
2163  xml_error_cs = qb_log_callsite_get(
2164  __func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
2165  }
2166 
2167  va_start(ap, fmt);
2168  if (xml_error_cs && xml_error_cs->targets) {
2169  CRM_XML_LOG_BASE(LOG_ERR, TRUE,
2170  crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error",
2171  TRUE, TRUE),
2172  "XML Error: ", fmt, ap);
2173  } else {
2174  CRM_XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
2175  }
2176  va_end(ap);
2177 }
2178 
2179 xmlNode *
2180 string2xml(const char *input)
2181 {
2182  xmlNode *xml = NULL;
2183  xmlDocPtr output = NULL;
2184  xmlParserCtxtPtr ctxt = NULL;
2185  xmlErrorPtr last_error = NULL;
2186 
2187  if (input == NULL) {
2188  crm_err("Can't parse NULL input");
2189  return NULL;
2190  }
2191 
2192  /* create a parser context */
2193  ctxt = xmlNewParserCtxt();
2194  CRM_CHECK(ctxt != NULL, return NULL);
2195 
2196  xmlCtxtResetLastError(ctxt);
2197  xmlSetGenericErrorFunc(ctxt, crm_xml_err);
2198  output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
2200  if (output) {
2201  xml = xmlDocGetRootElement(output);
2202  }
2203  last_error = xmlCtxtGetLastError(ctxt);
2204  if (last_error && last_error->code != XML_ERR_OK) {
2205  /* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
2206  /*
2207  * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
2208  * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
2209  */
2210  crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
2211  last_error->domain, last_error->level, last_error->code, last_error->message);
2212 
2213  if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
2214  CRM_LOG_ASSERT("Cannot parse an empty string");
2215 
2216  } else if (last_error->code != XML_ERR_DOCUMENT_END) {
2217  crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
2218  input);
2219  if (xml != NULL) {
2220  crm_log_xml_err(xml, "Partial");
2221  }
2222 
2223  } else {
2224  int len = strlen(input);
2225  int lpc = 0;
2226 
2227  while(lpc < len) {
2228  crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
2229  lpc += 80;
2230  }
2231 
2232  CRM_LOG_ASSERT("String parsing error");
2233  }
2234  }
2235 
2236  xmlFreeParserCtxt(ctxt);
2237  return xml;
2238 }
2239 
2240 xmlNode *
2242 {
2243  size_t data_length = 0;
2244  size_t read_chars = 0;
2245 
2246  char *xml_buffer = NULL;
2247  xmlNode *xml_obj = NULL;
2248 
2249  do {
2250  xml_buffer = realloc_safe(xml_buffer, data_length + XML_BUFFER_SIZE);
2251  read_chars = fread(xml_buffer + data_length, 1, XML_BUFFER_SIZE, stdin);
2252  data_length += read_chars;
2253  } while (read_chars == XML_BUFFER_SIZE);
2254 
2255  if (data_length == 0) {
2256  crm_warn("No XML supplied on stdin");
2257  free(xml_buffer);
2258  return NULL;
2259  }
2260 
2261  xml_buffer[data_length] = '\0';
2262  xml_obj = string2xml(xml_buffer);
2263  free(xml_buffer);
2264 
2265  crm_log_xml_trace(xml_obj, "Created fragment");
2266  return xml_obj;
2267 }
2268 
2269 static char *
2270 decompress_file(const char *filename)
2271 {
2272  char *buffer = NULL;
2273 
2274 #if HAVE_BZLIB_H
2275  int rc = 0;
2276  size_t length = 0, read_len = 0;
2277 
2278  BZFILE *bz_file = NULL;
2279  FILE *input = fopen(filename, "r");
2280 
2281  if (input == NULL) {
2282  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
2283  return NULL;
2284  }
2285 
2286  bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
2287  if (rc != BZ_OK) {
2288  crm_err("Could not prepare to read compressed %s: %s "
2289  CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
2290  BZ2_bzReadClose(&rc, bz_file);
2291  return NULL;
2292  }
2293 
2294  rc = BZ_OK;
2295  // cppcheck seems not to understand the abort-logic in realloc_safe
2296  // cppcheck-suppress memleak
2297  while (rc == BZ_OK) {
2298  buffer = realloc_safe(buffer, XML_BUFFER_SIZE + length + 1);
2299  read_len = BZ2_bzRead(&rc, bz_file, buffer + length, XML_BUFFER_SIZE);
2300 
2301  crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
2302 
2303  if (rc == BZ_OK || rc == BZ_STREAM_END) {
2304  length += read_len;
2305  }
2306  }
2307 
2308  buffer[length] = '\0';
2309 
2310  if (rc != BZ_STREAM_END) {
2311  crm_err("Could not read compressed %s: %s "
2312  CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
2313  free(buffer);
2314  buffer = NULL;
2315  }
2316 
2317  BZ2_bzReadClose(&rc, bz_file);
2318  fclose(input);
2319 
2320 #else
2321  crm_err("Could not read compressed %s: not built with bzlib support",
2322  filename);
2323 #endif
2324  return buffer;
2325 }
2326 
2327 void
2328 strip_text_nodes(xmlNode * xml)
2329 {
2330  xmlNode *iter = xml->children;
2331 
2332  while (iter) {
2333  xmlNode *next = iter->next;
2334 
2335  switch (iter->type) {
2336  case XML_TEXT_NODE:
2337  /* Remove it */
2338  pcmk_free_xml_subtree(iter);
2339  break;
2340 
2341  case XML_ELEMENT_NODE:
2342  /* Search it */
2343  strip_text_nodes(iter);
2344  break;
2345 
2346  default:
2347  /* Leave it */
2348  break;
2349  }
2350 
2351  iter = next;
2352  }
2353 }
2354 
2355 xmlNode *
2356 filename2xml(const char *filename)
2357 {
2358  xmlNode *xml = NULL;
2359  xmlDocPtr output = NULL;
2360  gboolean uncompressed = TRUE;
2361  xmlParserCtxtPtr ctxt = NULL;
2362  xmlErrorPtr last_error = NULL;
2363 
2364  /* create a parser context */
2365  ctxt = xmlNewParserCtxt();
2366  CRM_CHECK(ctxt != NULL, return NULL);
2367 
2368  xmlCtxtResetLastError(ctxt);
2369  xmlSetGenericErrorFunc(ctxt, crm_xml_err);
2370 
2371  if (filename) {
2372  uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
2373  }
2374 
2375  if (filename == NULL) {
2376  /* STDIN_FILENO == fileno(stdin) */
2377  output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
2379 
2380  } else if (uncompressed) {
2381  output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS);
2382 
2383  } else {
2384  char *input = decompress_file(filename);
2385 
2386  output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
2388  free(input);
2389  }
2390 
2391  if (output && (xml = xmlDocGetRootElement(output))) {
2392  strip_text_nodes(xml);
2393  }
2394 
2395  last_error = xmlCtxtGetLastError(ctxt);
2396  if (last_error && last_error->code != XML_ERR_OK) {
2397  /* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
2398  /*
2399  * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
2400  * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
2401  */
2402  crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
2403  last_error->domain, last_error->level, last_error->code, last_error->message);
2404 
2405  if (last_error && last_error->code != XML_ERR_OK) {
2406  crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
2407  if (xml != NULL) {
2408  crm_log_xml_err(xml, "Partial");
2409  }
2410  }
2411  }
2412 
2413  xmlFreeParserCtxt(ctxt);
2414  return xml;
2415 }
2416 
2425 const char *
2426 crm_xml_add_last_written(xmlNode *xml_node)
2427 {
2428  const char *now_str = pcmk__epoch2str(NULL);
2429 
2430  return crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN,
2431  now_str ? now_str : "Could not determine current time");
2432 }
2433 
2439 void
2441 {
2442  char *c;
2443 
2444  for (c = id; *c; ++c) {
2445  /* @TODO Sanitize more comprehensively */
2446  switch (*c) {
2447  case ':':
2448  case '#':
2449  *c = '.';
2450  }
2451  }
2452 }
2453 
2461 void
2462 crm_xml_set_id(xmlNode *xml, const char *format, ...)
2463 {
2464  va_list ap;
2465  int len = 0;
2466  char *id = NULL;
2467 
2468  /* equivalent to crm_strdup_printf() */
2469  va_start(ap, format);
2470  len = vasprintf(&id, format, ap);
2471  va_end(ap);
2472  CRM_ASSERT(len > 0);
2473 
2474  crm_xml_sanitize_id(id);
2475  crm_xml_add(xml, XML_ATTR_ID, id);
2476  free(id);
2477 }
2478 
2490 static int
2491 write_xml_stream(xmlNode * xml_node, const char *filename, FILE * stream, gboolean compress)
2492 {
2493  int res = 0;
2494  char *buffer = NULL;
2495  unsigned int out = 0;
2496 
2497  crm_log_xml_trace(xml_node, "writing");
2498 
2499  buffer = dump_xml_formatted(xml_node);
2500  CRM_CHECK(buffer && strlen(buffer),
2501  crm_log_xml_warn(xml_node, "formatting failed");
2502  res = -pcmk_err_generic;
2503  goto bail);
2504 
2505  if (compress) {
2506 #if HAVE_BZLIB_H
2507  int rc = BZ_OK;
2508  unsigned int in = 0;
2509  BZFILE *bz_file = NULL;
2510 
2511  bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
2512  if (rc != BZ_OK) {
2513  crm_warn("Not compressing %s: could not prepare file stream: %s "
2514  CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc);
2515  } else {
2516  BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
2517  if (rc != BZ_OK) {
2518  crm_warn("Not compressing %s: could not compress data: %s "
2519  CRM_XS " bzerror=%d errno=%d",
2520  filename, bz2_strerror(rc), rc, errno);
2521  }
2522  }
2523 
2524  if (rc == BZ_OK) {
2525  BZ2_bzWriteClose(&rc, bz_file, 0, &in, &out);
2526  if (rc != BZ_OK) {
2527  crm_warn("Not compressing %s: could not write compressed data: %s "
2528  CRM_XS " bzerror=%d errno=%d",
2529  filename, bz2_strerror(rc), rc, errno);
2530  out = 0; // retry without compression
2531  } else {
2532  res = (int) out;
2533  crm_trace("Compressed XML for %s from %u bytes to %u",
2534  filename, in, out);
2535  }
2536  }
2537 #else
2538  crm_warn("Not compressing %s: not built with bzlib support", filename);
2539 #endif
2540  }
2541 
2542  if (out == 0) {
2543  res = fprintf(stream, "%s", buffer);
2544  if (res < 0) {
2545  res = -errno;
2546  crm_perror(LOG_ERR, "writing %s", filename);
2547  goto bail;
2548  }
2549  }
2550 
2551  bail:
2552 
2553  if (fflush(stream) != 0) {
2554  res = -errno;
2555  crm_perror(LOG_ERR, "flushing %s", filename);
2556  }
2557 
2558  /* Don't report error if the file does not support synchronization */
2559  if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) {
2560  res = -errno;
2561  crm_perror(LOG_ERR, "synchronizing %s", filename);
2562  }
2563 
2564  fclose(stream);
2565 
2566  crm_trace("Saved %d bytes%s to %s as XML",
2567  res, ((out > 0)? " (compressed)" : ""), filename);
2568  free(buffer);
2569 
2570  return res;
2571 }
2572 
2583 int
2584 write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
2585 {
2586  FILE *stream = NULL;
2587 
2588  CRM_CHECK(xml_node && (fd > 0), return -EINVAL);
2589  stream = fdopen(fd, "w");
2590  if (stream == NULL) {
2591  return -errno;
2592  }
2593  return write_xml_stream(xml_node, filename, stream, compress);
2594 }
2595 
2605 int
2606 write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
2607 {
2608  FILE *stream = NULL;
2609 
2610  CRM_CHECK(xml_node && filename, return -EINVAL);
2611  stream = fopen(filename, "w");
2612  if (stream == NULL) {
2613  return -errno;
2614  }
2615  return write_xml_stream(xml_node, filename, stream, compress);
2616 }
2617 
2618 xmlNode *
2619 get_message_xml(xmlNode * msg, const char *field)
2620 {
2621  xmlNode *tmp = first_named_child(msg, field);
2622 
2623  return __xml_first_child(tmp);
2624 }
2625 
2626 gboolean
2627 add_message_xml(xmlNode * msg, const char *field, xmlNode * xml)
2628 {
2629  xmlNode *holder = create_xml_node(msg, field);
2630 
2631  add_node_copy(holder, xml);
2632  return TRUE;
2633 }
2634 
2635 static char *
2636 crm_xml_escape_shuffle(char *text, int start, int *length, const char *replace)
2637 {
2638  int lpc;
2639  int offset = strlen(replace) - 1; /* We have space for 1 char already */
2640 
2641  *length += offset;
2642  text = realloc_safe(text, *length);
2643 
2644  for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
2645  text[lpc] = text[lpc - offset];
2646  }
2647 
2648  memcpy(text + start, replace, offset + 1);
2649  return text;
2650 }
2651 
2652 char *
2653 crm_xml_escape(const char *text)
2654 {
2655  int index;
2656  int changes = 0;
2657  int length = 1 + strlen(text);
2658  char *copy = strdup(text);
2659 
2660  /*
2661  * When xmlCtxtReadDoc() parses &lt; and friends in a
2662  * value, it converts them to their human readable
2663  * form.
2664  *
2665  * If one uses xmlNodeDump() to convert it back to a
2666  * string, all is well, because special characters are
2667  * converted back to their escape sequences.
2668  *
2669  * However xmlNodeDump() is randomly dog slow, even with the same
2670  * input. So we need to replicate the escaping in our custom
2671  * version so that the result can be re-parsed by xmlCtxtReadDoc()
2672  * when necessary.
2673  */
2674 
2675  for (index = 0; index < length; index++) {
2676  switch (copy[index]) {
2677  case 0:
2678  break;
2679  case '<':
2680  copy = crm_xml_escape_shuffle(copy, index, &length, "&lt;");
2681  changes++;
2682  break;
2683  case '>':
2684  copy = crm_xml_escape_shuffle(copy, index, &length, "&gt;");
2685  changes++;
2686  break;
2687  case '"':
2688  copy = crm_xml_escape_shuffle(copy, index, &length, "&quot;");
2689  changes++;
2690  break;
2691  case '\'':
2692  copy = crm_xml_escape_shuffle(copy, index, &length, "&apos;");
2693  changes++;
2694  break;
2695  case '&':
2696  copy = crm_xml_escape_shuffle(copy, index, &length, "&amp;");
2697  changes++;
2698  break;
2699  case '\t':
2700  /* Might as well just expand to a few spaces... */
2701  copy = crm_xml_escape_shuffle(copy, index, &length, " ");
2702  changes++;
2703  break;
2704  case '\n':
2705  /* crm_trace("Convert: \\%.3o", copy[index]); */
2706  copy = crm_xml_escape_shuffle(copy, index, &length, "\\n");
2707  changes++;
2708  break;
2709  case '\r':
2710  copy = crm_xml_escape_shuffle(copy, index, &length, "\\r");
2711  changes++;
2712  break;
2713  /* For debugging...
2714  case '\\':
2715  crm_trace("Passthrough: \\%c", copy[index+1]);
2716  break;
2717  */
2718  default:
2719  /* Check for and replace non-printing characters with their octal equivalent */
2720  if(copy[index] < ' ' || copy[index] > '~') {
2721  char *replace = crm_strdup_printf("\\%.3o", copy[index]);
2722 
2723  /* crm_trace("Convert to octal: \\%.3o", copy[index]); */
2724  copy = crm_xml_escape_shuffle(copy, index, &length, replace);
2725  free(replace);
2726  changes++;
2727  }
2728  }
2729  }
2730 
2731  if (changes) {
2732  crm_trace("Dumped '%s'", copy);
2733  }
2734  return copy;
2735 }
2736 
2737 static inline void
2738 dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
2739 {
2740  char *p_value = NULL;
2741  const char *p_name = NULL;
2742  xml_private_t *p = NULL;
2743 
2744  CRM_ASSERT(buffer != NULL);
2745  if (attr == NULL || attr->children == NULL) {
2746  return;
2747  }
2748 
2749  p = attr->_private;
2750  if (p && is_set(p->flags, xpf_deleted)) {
2751  return;
2752  }
2753 
2754  p_name = (const char *)attr->name;
2755  p_value = crm_xml_escape((const char *)attr->children->content);
2756  buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
2757  free(p_value);
2758 }
2759 
2760 static void
2761 __xml_log_element(int log_level, const char *file, const char *function, int line,
2762  const char *prefix, xmlNode * data, int depth, int options)
2763 {
2764  int max = 0;
2765  int offset = 0;
2766  const char *name = NULL;
2767  const char *hidden = NULL;
2768 
2769  xmlNode *child = NULL;
2770  xmlAttrPtr pIter = NULL;
2771 
2772  if ((data == NULL) || (log_level == LOG_NEVER)) {
2773  return;
2774  }
2775 
2776  name = crm_element_name(data);
2777 
2778  if(is_set(options, xml_log_option_open)) {
2779  char *buffer = NULL;
2780 
2781  insert_prefix(options, &buffer, &offset, &max, depth);
2782 
2783  if (data->type == XML_COMMENT_NODE) {
2784  buffer_print(buffer, max, offset, "<!--%s-->", data->content);
2785 
2786  } else {
2787  buffer_print(buffer, max, offset, "<%s", name);
2788 
2789  hidden = crm_element_value(data, "hidden");
2790  for (pIter = pcmk__first_xml_attr(data); pIter != NULL; pIter = pIter->next) {
2791  xml_private_t *p = pIter->_private;
2792  const char *p_name = (const char *)pIter->name;
2793  const char *p_value = pcmk__xml_attr_value(pIter);
2794  char *p_copy = NULL;
2795 
2796  if(is_set(p->flags, xpf_deleted)) {
2797  continue;
2798  } else if ((is_set(options, xml_log_option_diff_plus)
2799  || is_set(options, xml_log_option_diff_minus))
2800  && strcmp(XML_DIFF_MARKER, p_name) == 0) {
2801  continue;
2802 
2803  } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
2804  p_copy = strdup("*****");
2805 
2806  } else {
2807  p_copy = crm_xml_escape(p_value);
2808  }
2809 
2810  buffer_print(buffer, max, offset, " %s=\"%s\"", p_name, p_copy);
2811  free(p_copy);
2812  }
2813 
2814  if(xml_has_children(data) == FALSE) {
2815  buffer_print(buffer, max, offset, "/>");
2816 
2817  } else if(is_set(options, xml_log_option_children)) {
2818  buffer_print(buffer, max, offset, ">");
2819 
2820  } else {
2821  buffer_print(buffer, max, offset, "/>");
2822  }
2823  }
2824 
2825  do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
2826  free(buffer);
2827  }
2828 
2829  if(data->type == XML_COMMENT_NODE) {
2830  return;
2831 
2832  } else if(xml_has_children(data) == FALSE) {
2833  return;
2834 
2835  } else if(is_set(options, xml_log_option_children)) {
2836  offset = 0;
2837  max = 0;
2838 
2839  for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
2840  __xml_log_element(log_level, file, function, line, prefix, child, depth + 1, options|xml_log_option_open|xml_log_option_close);
2841  }
2842  }
2843 
2844  if(is_set(options, xml_log_option_close)) {
2845  char *buffer = NULL;
2846 
2847  insert_prefix(options, &buffer, &offset, &max, depth);
2848  buffer_print(buffer, max, offset, "</%s>", name);
2849 
2850  do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
2851  free(buffer);
2852  }
2853 }
2854 
2855 static void
2856 __xml_log_change_element(int log_level, const char *file, const char *function, int line,
2857  const char *prefix, xmlNode * data, int depth, int options)
2858 {
2859  xml_private_t *p;
2860  char *prefix_m = NULL;
2861  xmlNode *child = NULL;
2862  xmlAttrPtr pIter = NULL;
2863 
2864  if ((data == NULL) || (log_level == LOG_NEVER)) {
2865  return;
2866  }
2867 
2868  p = data->_private;
2869 
2870  prefix_m = strdup(prefix);
2871  prefix_m[1] = '+';
2872 
2873  if(is_set(p->flags, xpf_dirty) && is_set(p->flags, xpf_created)) {
2874  /* Continue and log full subtree */
2875  __xml_log_element(log_level, file, function, line,
2876  prefix_m, data, depth, options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
2877 
2878  } else if(is_set(p->flags, xpf_dirty)) {
2879  char *spaces = calloc(80, 1);
2880  int s_count = 0, s_max = 80;
2881  char *prefix_del = NULL;
2882  char *prefix_moved = NULL;
2883  const char *flags = prefix;
2884 
2885  insert_prefix(options, &spaces, &s_count, &s_max, depth);
2886  prefix_del = strdup(prefix);
2887  prefix_del[0] = '-';
2888  prefix_del[1] = '-';
2889  prefix_moved = strdup(prefix);
2890  prefix_moved[1] = '~';
2891 
2892  if(is_set(p->flags, xpf_moved)) {
2893  flags = prefix_moved;
2894  } else {
2895  flags = prefix;
2896  }
2897 
2898  __xml_log_element(log_level, file, function, line,
2899  flags, data, depth, options|xml_log_option_open);
2900 
2901  for (pIter = pcmk__first_xml_attr(data); pIter != NULL; pIter = pIter->next) {
2902  const char *aname = (const char*)pIter->name;
2903 
2904  p = pIter->_private;
2905  if(is_set(p->flags, xpf_deleted)) {
2906  const char *value = crm_element_value(data, aname);
2907  flags = prefix_del;
2908  do_crm_log_alias(log_level, file, function, line,
2909  "%s %s @%s=%s", flags, spaces, aname, value);
2910 
2911  } else if(is_set(p->flags, xpf_dirty)) {
2912  const char *value = crm_element_value(data, aname);
2913 
2914  if(is_set(p->flags, xpf_created)) {
2915  flags = prefix_m;
2916 
2917  } else if(is_set(p->flags, xpf_modified)) {
2918  flags = prefix;
2919 
2920  } else if(is_set(p->flags, xpf_moved)) {
2921  flags = prefix_moved;
2922 
2923  } else {
2924  flags = prefix;
2925  }
2926  do_crm_log_alias(log_level, file, function, line,
2927  "%s %s @%s=%s", flags, spaces, aname, value);
2928  }
2929  }
2930  free(prefix_moved);
2931  free(prefix_del);
2932  free(spaces);
2933 
2934  for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
2935  __xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
2936  }
2937 
2938  __xml_log_element(log_level, file, function, line,
2939  prefix, data, depth, options|xml_log_option_close);
2940 
2941  } else {
2942  for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
2943  __xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
2944  }
2945  }
2946 
2947  free(prefix_m);
2948 
2949 }
2950 
2951 void
2952 log_data_element(int log_level, const char *file, const char *function, int line,
2953  const char *prefix, xmlNode * data, int depth, int options)
2954 {
2955  xmlNode *a_child = NULL;
2956 
2957  char *prefix_m = NULL;
2958 
2959  if (log_level == LOG_NEVER) {
2960  return;
2961  }
2962 
2963  if (prefix == NULL) {
2964  prefix = "";
2965  }
2966 
2967  /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
2968  if (data == NULL) {
2969  do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
2970  "No data to dump as XML");
2971  return;
2972  }
2973 
2974  if(is_set(options, xml_log_option_dirty_add) || is_set(options, xml_log_option_dirty_add)) {
2975  __xml_log_change_element(log_level, file, function, line, prefix, data, depth, options);
2976  return;
2977  }
2978 
2979  if (is_set(options, xml_log_option_formatted)) {
2980  if (is_set(options, xml_log_option_diff_plus)
2981  && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
2982  options |= xml_log_option_diff_all;
2983  prefix_m = strdup(prefix);
2984  prefix_m[1] = '+';
2985  prefix = prefix_m;
2986 
2987  } else if (is_set(options, xml_log_option_diff_minus)
2988  && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
2989  options |= xml_log_option_diff_all;
2990  prefix_m = strdup(prefix);
2991  prefix_m[1] = '-';
2992  prefix = prefix_m;
2993  }
2994  }
2995 
2996  if (is_set(options, xml_log_option_diff_short)
2997  && is_not_set(options, xml_log_option_diff_all)) {
2998  /* Still searching for the actual change */
2999  for (a_child = __xml_first_child(data); a_child != NULL; a_child = __xml_next(a_child)) {
3000  log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
3001  }
3002  } else {
3003  __xml_log_element(log_level, file, function, line, prefix, data, depth,
3005  }
3006  free(prefix_m);
3007 }
3008 
3009 static void
3010 dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
3011 {
3012  int lpc;
3013  xmlAttrPtr xIter = NULL;
3014  static int filter_len = DIMOF(filter);
3015 
3016  for (lpc = 0; options && lpc < filter_len; lpc++) {
3017  filter[lpc].found = FALSE;
3018  }
3019 
3020  for (xIter = pcmk__first_xml_attr(data); xIter != NULL; xIter = xIter->next) {
3021  bool skip = FALSE;
3022  const char *p_name = (const char *)xIter->name;
3023 
3024  for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
3025  if (filter[lpc].found == FALSE && strcmp(p_name, filter[lpc].string) == 0) {
3026  filter[lpc].found = TRUE;
3027  skip = TRUE;
3028  break;
3029  }
3030  }
3031 
3032  if (skip == FALSE) {
3033  dump_xml_attr(xIter, options, buffer, offset, max);
3034  }
3035  }
3036 }
3037 
3038 static void
3039 dump_xml_element(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
3040 {
3041  const char *name = NULL;
3042 
3043  CRM_ASSERT(max != NULL);
3044  CRM_ASSERT(offset != NULL);
3045  CRM_ASSERT(buffer != NULL);
3046 
3047  if (data == NULL) {
3048  crm_trace("Nothing to dump");
3049  return;
3050  }
3051 
3052  if (*buffer == NULL) {
3053  *offset = 0;
3054  *max = 0;
3055  }
3056 
3057  name = crm_element_name(data);
3058  CRM_ASSERT(name != NULL);
3059 
3060  insert_prefix(options, buffer, offset, max, depth);
3061  buffer_print(*buffer, *max, *offset, "<%s", name);
3062 
3063  if (options & xml_log_option_filtered) {
3064  dump_filtered_xml(data, options, buffer, offset, max);
3065 
3066  } else {
3067  xmlAttrPtr xIter = NULL;
3068 
3069  for (xIter = pcmk__first_xml_attr(data); xIter != NULL; xIter = xIter->next) {
3070  dump_xml_attr(xIter, options, buffer, offset, max);
3071  }
3072  }
3073 
3074  if (data->children == NULL) {
3075  buffer_print(*buffer, *max, *offset, "/>");
3076 
3077  } else {
3078  buffer_print(*buffer, *max, *offset, ">");
3079  }
3080 
3081  if (options & xml_log_option_formatted) {
3082  buffer_print(*buffer, *max, *offset, "\n");
3083  }
3084 
3085  if (data->children) {
3086  xmlNode *xChild = NULL;
3087  for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
3088  crm_xml_dump(xChild, options, buffer, offset, max, depth + 1);
3089  }
3090 
3091  insert_prefix(options, buffer, offset, max, depth);
3092  buffer_print(*buffer, *max, *offset, "</%s>", name);
3093 
3094  if (options & xml_log_option_formatted) {
3095  buffer_print(*buffer, *max, *offset, "\n");
3096  }
3097  }
3098 }
3099 
3100 static void
3101 dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
3102 {
3103  CRM_ASSERT(max != NULL);
3104  CRM_ASSERT(offset != NULL);
3105  CRM_ASSERT(buffer != NULL);
3106 
3107  if (data == NULL) {
3108  crm_trace("Nothing to dump");
3109  return;
3110  }
3111 
3112  if (*buffer == NULL) {
3113  *offset = 0;
3114  *max = 0;
3115  }
3116 
3117  insert_prefix(options, buffer, offset, max, depth);
3118 
3119  buffer_print(*buffer, *max, *offset, "%s", data->content);
3120 
3121  if (options & xml_log_option_formatted) {
3122  buffer_print(*buffer, *max, *offset, "\n");
3123  }
3124 }
3125 
3126 static void
3127 dump_xml_cdata(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
3128 {
3129  CRM_ASSERT(max != NULL);
3130  CRM_ASSERT(offset != NULL);
3131  CRM_ASSERT(buffer != NULL);
3132 
3133  if (data == NULL) {
3134  crm_trace("Nothing to dump");
3135  return;
3136  }
3137 
3138  if (*buffer == NULL) {
3139  *offset = 0;
3140  *max = 0;
3141  }
3142 
3143  insert_prefix(options, buffer, offset, max, depth);
3144 
3145  buffer_print(*buffer, *max, *offset, "<![CDATA[");
3146  buffer_print(*buffer, *max, *offset, "%s", data->content);
3147  buffer_print(*buffer, *max, *offset, "]]>");
3148 
3149  if (options & xml_log_option_formatted) {
3150  buffer_print(*buffer, *max, *offset, "\n");
3151  }
3152 }
3153 
3154 
3155 static void
3156 dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
3157 {
3158  CRM_ASSERT(max != NULL);
3159  CRM_ASSERT(offset != NULL);
3160  CRM_ASSERT(buffer != NULL);
3161 
3162  if (data == NULL) {
3163  crm_trace("Nothing to dump");
3164  return;
3165  }
3166 
3167  if (*buffer == NULL) {
3168  *offset = 0;
3169  *max = 0;
3170  }
3171 
3172  insert_prefix(options, buffer, offset, max, depth);
3173 
3174  buffer_print(*buffer, *max, *offset, "<!--");
3175  buffer_print(*buffer, *max, *offset, "%s", data->content);
3176  buffer_print(*buffer, *max, *offset, "-->");
3177 
3178  if (options & xml_log_option_formatted) {
3179  buffer_print(*buffer, *max, *offset, "\n");
3180  }
3181 }
3182 
3183 #define PCMK__XMLDUMP_STATS 0
3184 
3185 void
3186 crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
3187 {
3188  if(data == NULL) {
3189  *offset = 0;
3190  *max = 0;
3191  return;
3192  }
3193 
3194  if (is_not_set(options, xml_log_option_filtered)
3195  && is_set(options, xml_log_option_full_fledged)) {
3196  /* libxml's serialization reuse is a good idea, sadly we cannot
3197  apply it for the filtered cases (preceding filtering pass
3198  would preclude further reuse of such in-situ modified XML
3199  in generic context and is likely not a win performance-wise),
3200  and there's also a historically unstable throughput argument
3201  (likely stemming from memory allocation overhead, eventhough
3202  that shall be minimized with defaults preset in crm_xml_init) */
3203 #if (PCMK__XMLDUMP_STATS - 0)
3204  time_t next, new = time(NULL);
3205 #endif
3206  xmlDoc *doc;
3207  xmlOutputBuffer *xml_buffer;
3208 
3209  doc = getDocPtr(data);
3210  /* doc will only be NULL if data is */
3211  CRM_CHECK(doc != NULL, return);
3212 
3213  xml_buffer = xmlAllocOutputBuffer(NULL);
3214  CRM_ASSERT(xml_buffer != NULL);
3215 
3216  /* XXX we could setup custom allocation scheme for the particular
3217  buffer, but it's subsumed with crm_xml_init that needs to
3218  be invoked prior to entering this function as such, since
3219  its other branch vitally depends on it -- what can be done
3220  about this all is to have a facade parsing functions that
3221  would 100% mark entering libxml code for us, since we don't
3222  do anything as crazy as swapping out the binary form of the
3223  parsed tree (but those would need to be strictly used as
3224  opposed to libxml's raw functions) */
3225 
3226  xmlNodeDumpOutput(xml_buffer, doc, data, 0,
3227  (options & xml_log_option_formatted), NULL);
3228  /* attempt adding final NL - failing shouldn't be fatal here */
3229  (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n");
3230  if (xml_buffer->buffer != NULL) {
3231  buffer_print(*buffer, *max, *offset, "%s",
3232  (char *) xmlBufContent(xml_buffer->buffer));
3233  }
3234 
3235 #if (PCMK__XMLDUMP_STATS - 0)
3236  next = time(NULL);
3237  if ((now + 1) < next) {
3238  crm_log_xml_trace(data, "Long time");
3239  crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
3240  }
3241 #endif
3242 
3243  /* asserted allocation before so there should be something to remove */
3244  (void) xmlOutputBufferClose(xml_buffer);
3245  return;
3246  }
3247 
3248  switch(data->type) {
3249  case XML_ELEMENT_NODE:
3250  /* Handle below */
3251  dump_xml_element(data, options, buffer, offset, max, depth);
3252  break;
3253  case XML_TEXT_NODE:
3254  /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
3255  if (options & xml_log_option_text) {
3256  dump_xml_text(data, options, buffer, offset, max, depth);
3257  }
3258  return;
3259  case XML_COMMENT_NODE:
3260  dump_xml_comment(data, options, buffer, offset, max, depth);
3261  break;
3262  case XML_CDATA_SECTION_NODE:
3263  dump_xml_cdata(data, options, buffer, offset, max, depth);
3264  break;
3265  default:
3266  crm_warn("Unhandled type: %d", data->type);
3267  return;
3268 
3269  /*
3270  XML_ATTRIBUTE_NODE = 2
3271  XML_ENTITY_REF_NODE = 5
3272  XML_ENTITY_NODE = 6
3273  XML_PI_NODE = 7
3274  XML_DOCUMENT_NODE = 9
3275  XML_DOCUMENT_TYPE_NODE = 10
3276  XML_DOCUMENT_FRAG_NODE = 11
3277  XML_NOTATION_NODE = 12
3278  XML_HTML_DOCUMENT_NODE = 13
3279  XML_DTD_NODE = 14
3280  XML_ELEMENT_DECL = 15
3281  XML_ATTRIBUTE_DECL = 16
3282  XML_ENTITY_DECL = 17
3283  XML_NAMESPACE_DECL = 18
3284  XML_XINCLUDE_START = 19
3285  XML_XINCLUDE_END = 20
3286  XML_DOCB_DOCUMENT_NODE = 21
3287  */
3288  }
3289 
3290 }
3291 
3292 void
3293 crm_buffer_add_char(char **buffer, int *offset, int *max, char c)
3294 {
3295  buffer_print(*buffer, *max, *offset, "%c", c);
3296 }
3297 
3298 char *
3299 dump_xml_formatted_with_text(xmlNode * an_xml_node)
3300 {
3301  char *buffer = NULL;
3302  int offset = 0, max = 0;
3303 
3304  crm_xml_dump(an_xml_node,
3305  xml_log_option_formatted|xml_log_option_full_fledged,
3306  &buffer, &offset, &max, 0);
3307  return buffer;
3308 }
3309 
3310 char *
3311 dump_xml_formatted(xmlNode * an_xml_node)
3312 {
3313  char *buffer = NULL;
3314  int offset = 0, max = 0;
3315 
3316  crm_xml_dump(an_xml_node, xml_log_option_formatted, &buffer, &offset, &max, 0);
3317  return buffer;
3318 }
3319 
3320 char *
3321 dump_xml_unformatted(xmlNode * an_xml_node)
3322 {
3323  char *buffer = NULL;
3324  int offset = 0, max = 0;
3325 
3326  crm_xml_dump(an_xml_node, 0, &buffer, &offset, &max, 0);
3327  return buffer;
3328 }
3329 
3330 gboolean
3331 xml_has_children(const xmlNode * xml_root)
3332 {
3333  if (xml_root != NULL && xml_root->children != NULL) {
3334  return TRUE;
3335  }
3336  return FALSE;
3337 }
3338 
3339 void
3340 xml_remove_prop(xmlNode * obj, const char *name)
3341 {
3342  if (pcmk__check_acl(obj, NULL, xpf_acl_write) == FALSE) {
3343  crm_trace("Cannot remove %s from %s", name, obj->name);
3344 
3345  } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
3346  /* Leave in place (marked for removal) until after the diff is calculated */
3347  xml_private_t *p = NULL;
3348  xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
3349 
3350  p = attr->_private;
3351  set_parent_flag(obj, xpf_dirty);
3352  p->flags |= xpf_deleted;
3353  /* crm_trace("Setting flag %x due to %s[@id=%s].%s", xpf_dirty, obj->name, ID(obj), name); */
3354 
3355  } else {
3356  xmlUnsetProp(obj, (pcmkXmlStr) name);
3357  }
3358 }
3359 
3360 void
3361 purge_diff_markers(xmlNode * a_node)
3362 {
3363  xmlNode *child = NULL;
3364 
3365  CRM_CHECK(a_node != NULL, return);
3366 
3368  for (child = __xml_first_child(a_node); child != NULL; child = __xml_next(child)) {
3369  purge_diff_markers(child);
3370  }
3371 }
3372 
3373 void
3374 save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
3375 {
3376  char *f = NULL;
3377 
3378  if (filename == NULL) {
3379  char *uuid = crm_generate_uuid();
3380 
3381  f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
3382  filename = f;
3383  free(uuid);
3384  }
3385 
3386  crm_info("Saving %s to %s", desc, filename);
3387  write_xml_file(xml, filename, FALSE);
3388  free(f);
3389 }
3390 
3391 gboolean
3392 apply_xml_diff(xmlNode *old_xml, xmlNode * diff, xmlNode **new_xml)
3393 {
3394  gboolean result = TRUE;
3395  int root_nodes_seen = 0;
3396  static struct qb_log_callsite *digest_cs = NULL;
3397  const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
3398  const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
3399 
3400  xmlNode *child_diff = NULL;
3401  xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
3402  xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
3403 
3404  CRM_CHECK(new_xml != NULL, return FALSE);
3405  if (digest_cs == NULL) {
3406  digest_cs =
3407  qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
3409  }
3410 
3411  crm_trace("Subtraction Phase");
3412  for (child_diff = __xml_first_child(removed); child_diff != NULL;
3413  child_diff = __xml_next(child_diff)) {
3414  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
3415  if (root_nodes_seen == 0) {
3416  *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE, NULL, NULL);
3417  }
3418  root_nodes_seen++;
3419  }
3420 
3421  if (root_nodes_seen == 0) {
3422  *new_xml = copy_xml(old_xml);
3423 
3424  } else if (root_nodes_seen > 1) {
3425  crm_err("(-) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
3426  result = FALSE;
3427  }
3428 
3429  root_nodes_seen = 0;
3430  crm_trace("Addition Phase");
3431  if (result) {
3432  xmlNode *child_diff = NULL;
3433 
3434  for (child_diff = __xml_first_child(added); child_diff != NULL;
3435  child_diff = __xml_next(child_diff)) {
3436  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
3437  if (root_nodes_seen == 0) {
3438  add_xml_object(NULL, *new_xml, child_diff, TRUE);
3439  }
3440  root_nodes_seen++;
3441  }
3442  }
3443 
3444  if (root_nodes_seen > 1) {
3445  crm_err("(+) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
3446  result = FALSE;
3447 
3448  } else if (result && digest) {
3449  char *new_digest = NULL;
3450 
3451  purge_diff_markers(*new_xml); /* Purge now so the diff is ok */
3452  new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE, version);
3453  if (safe_str_neq(new_digest, digest)) {
3454  crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest);
3455  result = FALSE;
3456 
3457  crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
3458  if (digest_cs && digest_cs->targets) {
3459  save_xml_to_file(old_xml, "diff:original", NULL);
3460  save_xml_to_file(diff, "diff:input", NULL);
3461  save_xml_to_file(*new_xml, "diff:new", NULL);
3462  }
3463 
3464  } else {
3465  crm_trace("Digest matched: expected %s, calculated %s", digest, new_digest);
3466  }
3467  free(new_digest);
3468 
3469  } else if (result) {
3470  purge_diff_markers(*new_xml); /* Purge now so the diff is ok */
3471  }
3472 
3473  return result;
3474 }
3475 
3483 static void
3484 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
3485 {
3486  for (xmlAttr *attr = pcmk__first_xml_attr(xml); attr; attr = attr->next) {
3487  ((xml_private_t *) (attr->_private))->flags |= flag;
3488  }
3489 }
3490 
3500 static void
3501 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
3502  const char *old_value)
3503 {
3504  xml_private_t *p = new_xml->doc->_private;
3505  xmlAttr *attr = NULL;
3506 
3507  // Prevent the dirty flag being set recursively upwards
3509 
3510  // Restore the old value (and the tracking flag)
3511  attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
3512  set_bit(p->flags, xpf_tracking);
3513 
3514  // Reset flags (so the attribute doesn't appear as newly created)
3515  p = attr->_private;
3516  p->flags = 0;
3517 
3518  // Check ACLs and mark restored value for later removal
3519  xml_remove_prop(new_xml, attr_name);
3520 
3521  crm_trace("XML attribute %s=%s was removed from %s",
3522  attr_name, old_value, element);
3523 }
3524 
3525 /*
3526  * \internal
3527  * \brief Check ACLs for a changed XML attribute
3528  */
3529 static void
3530 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
3531  const char *old_value)
3532 {
3533  char *vcopy = crm_element_value_copy(new_xml, attr_name);
3534 
3535  crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
3536  attr_name, old_value, vcopy, element);
3537 
3538  // Restore the original value
3539  xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
3540 
3541  // Change it back to the new value, to check ACLs
3542  crm_xml_add(new_xml, attr_name, vcopy);
3543  free(vcopy);
3544 }
3545 
3550 static void
3551 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
3552  xmlAttr *new_attr, int p_old, int p_new)
3553 {
3554  xml_private_t *p = new_attr->_private;
3555 
3556  crm_trace("XML attribute %s moved from position %d to %d in %s",
3557  old_attr->name, p_old, p_new, element);
3558 
3559  // Mark document, element, and all element's parents as changed
3560  __xml_node_dirty(new_xml);
3561 
3562  // Mark attribute as changed
3563  p->flags |= xpf_dirty|xpf_moved;
3564 
3565  p = (p_old > p_new)? old_attr->_private : new_attr->_private;
3566  p->flags |= xpf_skip;
3567 }
3568 
3573 static void
3574 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
3575 {
3576  xmlAttr *attr_iter = pcmk__first_xml_attr(old_xml);
3577 
3578  while (attr_iter != NULL) {
3579  xmlAttr *old_attr = attr_iter;
3580  xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
3581  const char *name = (const char *) attr_iter->name;
3582  const char *old_value = crm_element_value(old_xml, name);
3583 
3584  attr_iter = attr_iter->next;
3585  if (new_attr == NULL) {
3586  mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
3587  old_value);
3588 
3589  } else {
3590  xml_private_t *p = new_attr->_private;
3591  int new_pos = __xml_offset((xmlNode*) new_attr);
3592  int old_pos = __xml_offset((xmlNode*) old_attr);
3593  const char *new_value = crm_element_value(new_xml, name);
3594 
3595  // This attribute isn't new
3596  p->flags = (p->flags & ~xpf_created);
3597 
3598  if (strcmp(new_value, old_value) != 0) {
3599  mark_attr_changed(new_xml, (const char *) old_xml->name, name,
3600  old_value);
3601 
3602  } else if ((old_pos != new_pos)
3603  && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
3604  mark_attr_moved(new_xml, (const char *) old_xml->name,
3605  old_attr, new_attr, old_pos, new_pos);
3606  }
3607  }
3608  }
3609 }
3610 
3615 static void
3616 mark_created_attrs(xmlNode *new_xml)
3617 {
3618  xmlAttr *attr_iter = pcmk__first_xml_attr(new_xml);
3619 
3620  while (attr_iter != NULL) {
3621  xmlAttr *new_attr = attr_iter;
3622  xml_private_t *p = attr_iter->_private;
3623 
3624  attr_iter = attr_iter->next;
3625  if (is_set(p->flags, xpf_created)) {
3626  const char *attr_name = (const char *) new_attr->name;
3627 
3628  crm_trace("Created new attribute %s=%s in %s",
3629  attr_name, crm_element_value(new_xml, attr_name),
3630  new_xml->name);
3631 
3632  /* Check ACLs (we can't use the remove-then-create trick because it
3633  * would modify the attribute position).
3634  */
3635  if (pcmk__check_acl(new_xml, attr_name, xpf_acl_write)) {
3636  pcmk__mark_xml_attr_dirty(new_attr);
3637  } else {
3638  // Creation was not allowed, so remove the attribute
3639  xmlUnsetProp(new_xml, new_attr->name);
3640  }
3641  }
3642  }
3643 }
3644 
3649 static void
3650 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
3651 {
3652  set_attrs_flag(new_xml, xpf_created); // cleared later if not really new
3653  xml_diff_old_attrs(old_xml, new_xml);
3654  mark_created_attrs(new_xml);
3655 }
3656 
3666 static void
3667 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
3668 {
3669  // Re-create the child element so we can check ACLs
3670  xmlNode *candidate = add_node_copy(new_parent, old_child);
3671 
3672  // Clear flags on new child and its children
3673  __xml_node_clean(candidate);
3674 
3675  // Check whether ACLs allow the deletion
3676  pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
3677 
3678  // Remove the child again (which will track it in document's deleted_objs)
3679  free_xml_with_position(candidate, __xml_offset(old_child));
3680 
3681  if (find_element(new_parent, old_child, TRUE) == NULL) {
3682  ((xml_private_t *) (old_child->_private))->flags |= xpf_skip;
3683  }
3684 }
3685 
3686 static void
3687 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
3688  int p_old, int p_new)
3689 {
3690  xml_private_t *p = new_child->_private;
3691 
3692  crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
3693  new_child->name, (ID(new_child)? ID(new_child) : "<no id>"),
3694  p_old, p_new, new_parent->name);
3695  __xml_node_dirty(new_parent);
3696  p->flags |= xpf_moved;
3697 
3698  if (p_old > p_new) {
3699  p = old_child->_private;
3700  } else {
3701  p = new_child->_private;
3702  }
3703  p->flags |= xpf_skip;
3704 }
3705 
3706 static void
3707 __xml_diff_object(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
3708 {
3709  xmlNode *cIter = NULL;
3710  xml_private_t *p = NULL;
3711 
3712  CRM_CHECK(new_xml != NULL, return);
3713  if (old_xml == NULL) {
3714  crm_node_created(new_xml);
3715  pcmk__apply_creation_acl(new_xml, check_top);
3716  return;
3717  }
3718 
3719  p = new_xml->_private;
3720  CRM_CHECK(p != NULL, return);
3721 
3722  if(p->flags & xpf_processed) {
3723  /* Avoid re-comparing nodes */
3724  return;
3725  }
3726  p->flags |= xpf_processed;
3727 
3728  xml_diff_attrs(old_xml, new_xml);
3729 
3730  // Check for differences in the original children
3731  for (cIter = __xml_first_child(old_xml); cIter != NULL; ) {
3732  xmlNode *old_child = cIter;
3733  xmlNode *new_child = find_element(new_xml, cIter, TRUE);
3734 
3735  cIter = __xml_next(cIter);
3736  if(new_child) {
3737  __xml_diff_object(old_child, new_child, TRUE);
3738 
3739  } else {
3740  mark_child_deleted(old_child, new_xml);
3741  }
3742  }
3743 
3744  // Check for moved or created children
3745  for (cIter = __xml_first_child(new_xml); cIter != NULL; ) {
3746  xmlNode *new_child = cIter;
3747  xmlNode *old_child = find_element(old_xml, cIter, TRUE);
3748 
3749  cIter = __xml_next(cIter);
3750  if(old_child == NULL) {
3751  // This is a newly created child
3752  p = new_child->_private;
3753  p->flags |= xpf_skip;
3754  __xml_diff_object(old_child, new_child, TRUE);
3755 
3756  } else {
3757  /* Check for movement, we already checked for differences */
3758  int p_new = __xml_offset(new_child);
3759  int p_old = __xml_offset(old_child);
3760 
3761  if(p_old != p_new) {
3762  mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
3763  }
3764  }
3765  }
3766 }
3767 
3768 void
3769 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
3770 {
3771  pcmk__set_xml_flag(new_xml, xpf_lazy);
3772  xml_calculate_changes(old_xml, new_xml);
3773 }
3774 
3775 void
3776 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
3777 {
3778  CRM_CHECK(safe_str_eq(crm_element_name(old_xml),
3779  crm_element_name(new_xml)),
3780  return);
3781  CRM_CHECK(safe_str_eq(ID(old_xml), ID(new_xml)), return);
3782 
3783  if(xml_tracking_changes(new_xml) == FALSE) {
3784  xml_track_changes(new_xml, NULL, NULL, FALSE);
3785  }
3786 
3787  __xml_diff_object(old_xml, new_xml, FALSE);
3788 }
3789 
3790 xmlNode *
3791 diff_xml_object(xmlNode * old, xmlNode * new, gboolean suppress)
3792 {
3793  xmlNode *tmp1 = NULL;
3794  xmlNode *diff = create_xml_node(NULL, "diff");
3795  xmlNode *removed = create_xml_node(diff, "diff-removed");
3796  xmlNode *added = create_xml_node(diff, "diff-added");
3797 
3799 
3800  tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
3801  if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
3802  free_xml(tmp1);
3803  }
3804 
3805  tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
3806  if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
3807  free_xml(tmp1);
3808  }
3809 
3810  if (added->children == NULL && removed->children == NULL) {
3811  free_xml(diff);
3812  diff = NULL;
3813  }
3814 
3815  return diff;
3816 }
3817 
3818 gboolean
3819 can_prune_leaf(xmlNode * xml_node)
3820 {
3821  xmlNode *cIter = NULL;
3822  xmlAttrPtr pIter = NULL;
3823  gboolean can_prune = TRUE;
3824  const char *name = crm_element_name(xml_node);
3825 
3829  || safe_str_eq(name, XML_ACL_TAG_ROLE_REFv1)) {
3830  return FALSE;
3831  }
3832 
3833  for (pIter = pcmk__first_xml_attr(xml_node); pIter != NULL; pIter = pIter->next) {
3834  const char *p_name = (const char *)pIter->name;
3835 
3836  if (strcmp(p_name, XML_ATTR_ID) == 0) {
3837  continue;
3838  }
3839  can_prune = FALSE;
3840  }
3841 
3842  cIter = __xml_first_child(xml_node);
3843  while (cIter) {
3844  xmlNode *child = cIter;
3845 
3846  cIter = __xml_next(cIter);
3847  if (can_prune_leaf(child)) {
3848  free_xml(child);
3849  } else {
3850  can_prune = FALSE;
3851  }
3852  }
3853  return can_prune;
3854 }
3855 
3856 static xmlNode *
3857 find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact)
3858 {
3859  xmlNode *a_child = NULL;
3860  int search_offset = __xml_offset(search_comment);
3861 
3862  CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
3863 
3864  for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
3865  if (exact) {
3866  int offset = __xml_offset(a_child);
3867  xml_private_t *p = a_child->_private;
3868 
3869  if (offset < search_offset) {
3870  continue;
3871 
3872  } else if (offset > search_offset) {
3873  return NULL;
3874  }
3875 
3876  if (is_set(p->flags, xpf_skip)) {
3877  continue;
3878  }
3879  }
3880 
3881  if (a_child->type == XML_COMMENT_NODE
3882  && safe_str_eq((const char *)a_child->content, (const char *)search_comment->content)) {
3883  return a_child;
3884 
3885  } else if (exact) {
3886  return NULL;
3887  }
3888  }
3889 
3890  return NULL;
3891 }
3892 
3893 static xmlNode *
3894 subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right,
3895  gboolean * changed)
3896 {
3897  CRM_CHECK(left != NULL, return NULL);
3898  CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
3899 
3900  if (right == NULL
3901  || safe_str_neq((const char *)left->content, (const char *)right->content)) {
3902  xmlNode *deleted = NULL;
3903 
3904  deleted = add_node_copy(parent, left);
3905  *changed = TRUE;
3906 
3907  return deleted;
3908  }
3909 
3910  return NULL;
3911 }
3912 
3913 xmlNode *
3914 subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
3915  gboolean full, gboolean * changed, const char *marker)
3916 {
3917  gboolean dummy = FALSE;
3918  gboolean skip = FALSE;
3919  xmlNode *diff = NULL;
3920  xmlNode *right_child = NULL;
3921  xmlNode *left_child = NULL;
3922  xmlAttrPtr xIter = NULL;
3923 
3924  const char *id = NULL;
3925  const char *name = NULL;
3926  const char *value = NULL;
3927  const char *right_val = NULL;
3928 
3929  int lpc = 0;
3930  static int filter_len = DIMOF(filter);
3931 
3932  if (changed == NULL) {
3933  changed = &dummy;
3934  }
3935 
3936  if (left == NULL) {
3937  return NULL;
3938  }
3939 
3940  if (left->type == XML_COMMENT_NODE) {
3941  return subtract_xml_comment(parent, left, right, changed);
3942  }
3943 
3944  id = ID(left);
3945  if (right == NULL) {
3946  xmlNode *deleted = NULL;
3947 
3948  crm_trace("Processing <%s id=%s> (complete copy)", crm_element_name(left), id);
3949  deleted = add_node_copy(parent, left);
3950  crm_xml_add(deleted, XML_DIFF_MARKER, marker);
3951 
3952  *changed = TRUE;
3953  return deleted;
3954  }
3955 
3956  name = crm_element_name(left);
3957  CRM_CHECK(name != NULL, return NULL);
3958  CRM_CHECK(safe_str_eq(crm_element_name(left), crm_element_name(right)), return NULL);
3959 
3960  /* check for XML_DIFF_MARKER in a child */
3961  value = crm_element_value(right, XML_DIFF_MARKER);
3962  if (value != NULL && strcmp(value, "removed:top") == 0) {
3963  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
3964  *changed = TRUE;
3965  return NULL;
3966  }
3967 
3968  /* Avoiding creating the full heirarchy would save even more work here */
3969  diff = create_xml_node(parent, name);
3970 
3971  /* Reset filter */
3972  for (lpc = 0; lpc < filter_len; lpc++) {
3973  filter[lpc].found = FALSE;
3974  }
3975 
3976  /* changes to child objects */
3977  for (left_child = __xml_first_child(left); left_child != NULL;
3978  left_child = __xml_next(left_child)) {
3979  gboolean child_changed = FALSE;
3980 
3981  right_child = find_element(right, left_child, FALSE);
3982  subtract_xml_object(diff, left_child, right_child, full, &child_changed, marker);
3983  if (child_changed) {
3984  *changed = TRUE;
3985  }
3986  }
3987 
3988  if (*changed == FALSE) {
3989  /* Nothing to do */
3990 
3991  } else if (full) {
3992  xmlAttrPtr pIter = NULL;
3993 
3994  for (pIter = pcmk__first_xml_attr(left); pIter != NULL; pIter = pIter->next) {
3995  const char *p_name = (const char *)pIter->name;
3996  const char *p_value = pcmk__xml_attr_value(pIter);
3997 
3998  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
3999  }
4000 
4001  /* We already have everything we need... */
4002  goto done;
4003  }
4004 
4005  /* changes to name/value pairs */
4006  for (xIter = pcmk__first_xml_attr(left); xIter != NULL; xIter = xIter->next) {
4007  const char *prop_name = (const char *)xIter->name;
4008  xmlAttrPtr right_attr = NULL;
4009  xml_private_t *p = NULL;
4010 
4011  if (strcmp(prop_name, XML_ATTR_ID) == 0) {
4012  /* id already obtained when present ~ this case, so just reuse */
4013  xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_ID, (pcmkXmlStr) id);
4014  continue;
4015  }
4016 
4017  skip = FALSE;
4018  for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
4019  if (filter[lpc].found == FALSE && strcmp(prop_name, filter[lpc].string) == 0) {
4020  filter[lpc].found = TRUE;
4021  skip = TRUE;
4022  break;
4023  }
4024  }
4025 
4026  if (skip) {
4027  continue;
4028  }
4029 
4030  right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
4031  if (right_attr) {
4032  p = right_attr->_private;
4033  }
4034 
4035  right_val = crm_element_value(right, prop_name);
4036  if (right_val == NULL || (p && is_set(p->flags, xpf_deleted))) {
4037  /* new */
4038  *changed = TRUE;
4039  if (full) {
4040  xmlAttrPtr pIter = NULL;
4041 
4042  for (pIter = pcmk__first_xml_attr(left); pIter != NULL; pIter = pIter->next) {
4043  const char *p_name = (const char *)pIter->name;
4044  const char *p_value = pcmk__xml_attr_value(pIter);
4045 
4046  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
4047  }
4048  break;
4049 
4050  } else {
4051  const char *left_value = crm_element_value(left, prop_name);
4052 
4053  xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
4054  crm_xml_add(diff, prop_name, left_value);
4055  }
4056 
4057  } else {
4058  /* Only now do we need the left value */
4059  const char *left_value = crm_element_value(left, prop_name);
4060 
4061  if (strcmp(left_value, right_val) == 0) {
4062  /* unchanged */
4063 
4064  } else {
4065  *changed = TRUE;
4066  if (full) {
4067  xmlAttrPtr pIter = NULL;
4068 
4069  crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
4070  crm_element_name(left), id);
4071  for (pIter = pcmk__first_xml_attr(left); pIter != NULL; pIter = pIter->next) {
4072  const char *p_name = (const char *)pIter->name;
4073  const char *p_value = pcmk__xml_attr_value(pIter);
4074 
4075  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
4076  }
4077  break;
4078 
4079  } else {
4080  crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
4081  prop_name, left_value, right_val, crm_element_name(left), id);
4082  crm_xml_add(diff, prop_name, left_value);
4083  }
4084  }
4085  }
4086  }
4087 
4088  if (*changed == FALSE) {
4089  free_xml(diff);
4090  return NULL;
4091 
4092  } else if (full == FALSE && id) {
4093  crm_xml_add(diff, XML_ATTR_ID, id);
4094  }
4095  done:
4096  return diff;
4097 }
4098 
4099 static int
4100 add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update)
4101 {
4102  CRM_CHECK(update != NULL, return 0);
4103  CRM_CHECK(update->type == XML_COMMENT_NODE, return 0);
4104 
4105  if (target == NULL) {
4106  target = find_xml_comment(parent, update, FALSE);
4107  }
4108 
4109  if (target == NULL) {
4110  add_node_copy(parent, update);
4111 
4112  /* We won't reach here currently */
4113  } else if (safe_str_neq((const char *)target->content, (const char *)update->content)) {
4114  xmlFree(target->content);
4115  target->content = xmlStrdup(update->content);
4116  }
4117 
4118  return 0;
4119 }
4120 
4121 static int
4122 add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff)
4123 {
4124  xmlNode *a_child = NULL;
4125  const char *object_name = NULL,
4126  *object_href = NULL,
4127  *object_href_val = NULL;
4128 
4129 #if XML_PARSE_DEBUG
4130  crm_log_xml_trace("update:", update);
4131  crm_log_xml_trace("target:", target);
4132 #endif
4133 
4134  CRM_CHECK(update != NULL, return 0);
4135 
4136  if (update->type == XML_COMMENT_NODE) {
4137  return add_xml_comment(parent, target, update);
4138  }
4139 
4140  object_name = crm_element_name(update);
4141  object_href_val = ID(update);
4142  if (object_href_val != NULL) {
4143  object_href = XML_ATTR_ID;
4144  } else {
4145  object_href_val = crm_element_value(update, XML_ATTR_IDREF);
4146  object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF;
4147  }
4148 
4149  CRM_CHECK(object_name != NULL, return 0);
4150  CRM_CHECK(target != NULL || parent != NULL, return 0);
4151 
4152  if (target == NULL) {
4153  target = find_entity_by_attr_or_just_name(parent, object_name,
4154  object_href, object_href_val);
4155  }
4156 
4157  if (target == NULL) {
4158  target = create_xml_node(parent, object_name);
4159  CRM_CHECK(target != NULL, return 0);
4160 #if XML_PARSER_DEBUG
4161  crm_trace("Added <%s%s%s%s%s/>", crm_str(object_name),
4162  object_href ? " " : "",
4163  object_href ? object_href : "",
4164  object_href ? "=" : "",
4165  object_href ? object_href_val : "");
4166 
4167  } else {
4168  crm_trace("Found node <%s%s%s%s%s/> to update", crm_str(object_name),
4169  object_href ? " " : "",
4170  object_href ? object_href : "",
4171  object_href ? "=" : "",
4172  object_href ? object_href_val : "");
4173 #endif
4174  }
4175 
4176  CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(update)), return 0);
4177 
4178  if (as_diff == FALSE) {
4179  /* So that expand_plus_plus() gets called */
4180  copy_in_properties(target, update);
4181 
4182  } else {
4183  /* No need for expand_plus_plus(), just raw speed */
4184  xmlAttrPtr pIter = NULL;
4185 
4186  for (pIter = pcmk__first_xml_attr(update); pIter != NULL; pIter = pIter->next) {
4187  const char *p_name = (const char *)pIter->name;
4188  const char *p_value = pcmk__xml_attr_value(pIter);
4189 
4190  /* Remove it first so the ordering of the update is preserved */
4191  xmlUnsetProp(target, (pcmkXmlStr) p_name);
4192  xmlSetProp(target, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
4193  }
4194  }
4195 
4196  for (a_child = __xml_first_child(update); a_child != NULL; a_child = __xml_next(a_child)) {
4197 #if XML_PARSER_DEBUG
4198  crm_trace("Updating child <%s%s%s%s%s/>", crm_str(object_name),
4199  object_href ? " " : "",
4200  object_href ? object_href : "",
4201  object_href ? "=" : "",
4202  object_href ? object_href_val : "");
4203 #endif
4204  add_xml_object(target, NULL, a_child, as_diff);
4205  }
4206 
4207 #if XML_PARSER_DEBUG
4208  crm_trace("Finished with <%s%s%s%s%s/>", crm_str(object_name),
4209  object_href ? " " : "",
4210  object_href ? object_href : "",
4211  object_href ? "=" : "",
4212  object_href ? object_href_val : "");
4213 #endif
4214  return 0;
4215 }
4216 
4217 gboolean
4218 update_xml_child(xmlNode * child, xmlNode * to_update)
4219 {
4220  gboolean can_update = TRUE;
4221  xmlNode *child_of_child = NULL;
4222 
4223  CRM_CHECK(child != NULL, return FALSE);
4224  CRM_CHECK(to_update != NULL, return FALSE);
4225 
4226  if (safe_str_neq(crm_element_name(to_update), crm_element_name(child))) {
4227  can_update = FALSE;
4228 
4229  } else if (safe_str_neq(ID(to_update), ID(child))) {
4230  can_update = FALSE;
4231 
4232  } else if (can_update) {
4233 #if XML_PARSER_DEBUG
4234  crm_log_xml_trace(child, "Update match found...");
4235 #endif
4236  add_xml_object(NULL, child, to_update, FALSE);
4237  }
4238 
4239  for (child_of_child = __xml_first_child(child); child_of_child != NULL;
4240  child_of_child = __xml_next(child_of_child)) {
4241  /* only update the first one */
4242  if (can_update) {
4243  break;
4244  }
4245  can_update = update_xml_child(child_of_child, to_update);
4246  }
4247 
4248  return can_update;
4249 }
4250 
4251 int
4252 find_xml_children(xmlNode ** children, xmlNode * root,
4253  const char *tag, const char *field, const char *value, gboolean search_matches)
4254 {
4255  int match_found = 0;
4256 
4257  CRM_CHECK(root != NULL, return FALSE);
4258  CRM_CHECK(children != NULL, return FALSE);
4259 
4260  if (tag != NULL && safe_str_neq(tag, crm_element_name(root))) {
4261 
4262  } else if (value != NULL && safe_str_neq(value, crm_element_value(root, field))) {
4263 
4264  } else {
4265  if (*children == NULL) {
4266  *children = create_xml_node(NULL, __FUNCTION__);
4267  }
4268  add_node_copy(*children, root);
4269  match_found = 1;
4270  }
4271 
4272  if (search_matches || match_found == 0) {
4273  xmlNode *child = NULL;
4274 
4275  for (child = __xml_first_child(root); child != NULL; child = __xml_next(child)) {
4276  match_found += find_xml_children(children, child, tag, field, value, search_matches);
4277  }
4278  }
4279 
4280  return match_found;
4281 }
4282 
4283 gboolean
4284 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
4285 {
4286  gboolean can_delete = FALSE;
4287  xmlNode *child_of_child = NULL;
4288 
4289  const char *up_id = NULL;
4290  const char *child_id = NULL;
4291  const char *right_val = NULL;
4292 
4293  CRM_CHECK(child != NULL, return FALSE);
4294  CRM_CHECK(update != NULL, return FALSE);
4295 
4296  up_id = ID(update);
4297  child_id = ID(child);
4298 
4299  if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
4300  can_delete = TRUE;
4301  }
4302  if (safe_str_neq(crm_element_name(update), crm_element_name(child))) {
4303  can_delete = FALSE;
4304  }
4305  if (can_delete && delete_only) {
4306  xmlAttrPtr pIter = NULL;
4307 
4308  for (pIter = pcmk__first_xml_attr(update); pIter != NULL; pIter = pIter->next) {
4309  const char *p_name = (const char *)pIter->name;
4310  const char *p_value = pcmk__xml_attr_value(pIter);
4311 
4312  right_val = crm_element_value(child, p_name);
4313  if (safe_str_neq(p_value, right_val)) {
4314  can_delete = FALSE;
4315  }
4316  }
4317  }
4318 
4319  if (can_delete && parent != NULL) {
4320  crm_log_xml_trace(child, "Delete match found...");
4321  if (delete_only || update == NULL) {
4322  free_xml(child);
4323 
4324  } else {
4325  xmlNode *tmp = copy_xml(update);
4326  xmlDoc *doc = tmp->doc;
4327  xmlNode *old = NULL;
4328 
4329  xml_accept_changes(tmp);
4330  old = xmlReplaceNode(child, tmp);
4331 
4332  if(xml_tracking_changes(tmp)) {
4333  /* Replaced sections may have included relevant ACLs */
4334  pcmk__apply_acl(tmp);
4335  }
4336 
4337  xml_calculate_changes(old, tmp);
4338  xmlDocSetRootElement(doc, old);
4339  free_xml(old);
4340  }
4341  child = NULL;
4342  return TRUE;
4343 
4344  } else if (can_delete) {
4345  crm_log_xml_debug(child, "Cannot delete the search root");
4346  can_delete = FALSE;
4347  }
4348 
4349  child_of_child = __xml_first_child(child);
4350  while (child_of_child) {
4351  xmlNode *next = __xml_next(child_of_child);
4352 
4353  can_delete = replace_xml_child(child, child_of_child, update, delete_only);
4354 
4355  /* only delete the first one */
4356  if (can_delete) {
4357  child_of_child = NULL;
4358  } else {
4359  child_of_child = next;
4360  }
4361  }
4362 
4363  return can_delete;
4364 }
4365 
4366 xmlNode *
4367 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
4368 {
4369  xmlNode *child = NULL;
4370  GSList *nvpairs = NULL;
4371  xmlNode *result = NULL;
4372  const char *name = NULL;
4373 
4374  CRM_CHECK(input != NULL, return NULL);
4375 
4376  name = crm_element_name(input);
4377  CRM_CHECK(name != NULL, return NULL);
4378 
4379  result = create_xml_node(parent, name);
4380  nvpairs = pcmk_xml_attrs2nvpairs(input);
4381  nvpairs = pcmk_sort_nvpairs(nvpairs);
4382  pcmk_nvpairs2xml_attrs(nvpairs, result);
4383  pcmk_free_nvpairs(nvpairs);
4384 
4385  for (child = __xml_first_child(input); child != NULL;
4386  child = __xml_next(child)) {
4387 
4388  if (recursive) {
4389  sorted_xml(child, result, recursive);
4390  } else {
4391  add_node_copy(result, child);
4392  }
4393  }
4394 
4395  return result;
4396 }
4397 
4398 xmlNode *
4399 first_named_child(const xmlNode *parent, const char *name)
4400 {
4401  xmlNode *match = NULL;
4402 
4403  for (match = __xml_first_child_element(parent); match != NULL;
4404  match = __xml_next_element(match)) {
4405  /*
4406  * name == NULL gives first child regardless of name; this is
4407  * semantically incorrect in this function, but may be necessary
4408  * due to prior use of xml_child_iter_filter
4409  */
4410  if (name == NULL || strcmp((const char *)match->name, name) == 0) {
4411  return match;
4412  }
4413  }
4414  return NULL;
4415 }
4416 
4424 xmlNode *
4425 crm_next_same_xml(const xmlNode *sibling)
4426 {
4427  xmlNode *match = __xml_next_element(sibling);
4428  const char *name = crm_element_name(sibling);
4429 
4430  while (match != NULL) {
4431  if (!strcmp(crm_element_name(match), name)) {
4432  return match;
4433  }
4434  match = __xml_next_element(match);
4435  }
4436  return NULL;
4437 }
4438 
4439 void
4441 {
4442  static bool init = TRUE;
4443 
4444  if(init) {
4445  init = FALSE;
4446  /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
4447  * realloc_safe()s and it can take upwards of 18 seconds (yes, seconds)
4448  * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
4449  * less than 1 second.
4450  */
4451  xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
4452 
4453  /* Populate and free the _private field when nodes are created and destroyed */
4454  xmlDeregisterNodeDefault(pcmkDeregisterNode);
4455  xmlRegisterNodeDefault(pcmkRegisterNode);
4456 
4457  crm_schema_init();
4458  }
4459 }
4460 
4461 void
4463 {
4464  crm_info("Cleaning up memory from libxml2");
4466  xmlCleanupParser();
4467 }
4468 
4469 #define XPATH_MAX 512
4470 
4471 xmlNode *
4472 expand_idref(xmlNode * input, xmlNode * top)
4473 {
4474  const char *tag = NULL;
4475  const char *ref = NULL;
4476  xmlNode *result = input;
4477 
4478  if (result == NULL) {
4479  return NULL;
4480 
4481  } else if (top == NULL) {
4482  top = input;
4483  }
4484 
4485  tag = crm_element_name(result);
4486  ref = crm_element_value(result, XML_ATTR_IDREF);
4487 
4488  if (ref != NULL) {
4489  char *xpath_string = crm_strdup_printf("//%s[@id='%s']", tag, ref);
4490 
4491  result = get_xpath_object(xpath_string, top, LOG_ERR);
4492  if (result == NULL) {
4493  char *nodePath = (char *)xmlGetNodePath(top);
4494 
4495  crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
4496  crm_str(nodePath));
4497  free(nodePath);
4498  }
4499  free(xpath_string);
4500  }
4501  return result;
4502 }
4503 
4504 void
4505 crm_destroy_xml(gpointer data)
4506 {
4507  free_xml(data);
4508 }
4509 
4510 char *
4512 {
4513  static const char *base = NULL;
4514  char *ret = NULL;
4515 
4516  if (base == NULL) {
4517  base = getenv("PCMK_schema_directory");
4518  }
4519  if (pcmk__str_empty(base)) {
4520  base = CRM_SCHEMA_DIRECTORY;
4521  }
4522 
4523  switch (ns) {
4526  ret = strdup(base);
4527  break;
4530  ret = crm_strdup_printf("%s/base", base);
4531  break;
4532  default:
4533  crm_err("XML artefact family specified as %u not recognized", ns);
4534  }
4535  return ret;
4536 }
4537 
4538 char *
4539 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
4540 {
4541  char *base = pcmk__xml_artefact_root(ns), *ret = NULL;
4542 
4543  switch (ns) {
4546  ret = crm_strdup_printf("%s/%s.rng", base, filespec);
4547  break;
4550  ret = crm_strdup_printf("%s/%s.xsl", base, filespec);
4551  break;
4552  default:
4553  crm_err("XML artefact family specified as %u not recognized", ns);
4554  }
4555  free(base);
4556 
4557  return ret;
4558 }
#define pcmk_err_old_data
Definition: results.h:74
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:4511
#define LOG_TRACE
Definition: logging.h:36
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:233
xmlNode * find_xml_node(xmlNode *cib, const char *node_path, gboolean must_find)
Definition: xml.c:1764
#define XML_ATTR_UPDATE_ORIG
Definition: msg_xml.h:103
#define XML_DIFF_RESULT
Definition: msg_xml.h:411
A dumping ground.
GSList * pcmk_sort_nvpairs(GSList *list)
Sort a list of name/value pairs.
Definition: nvpair.c:146
void crm_schema_init(void)
Definition: schemas.c:379
xmlNode * get_message_xml(xmlNode *msg, const char *field)
Definition: xml.c:2619
#define crm_notice(fmt, args...)
Definition: logging.h:365
#define XML_ATTR_UPDATE_CLIENT
Definition: msg_xml.h:104
#define PCMK__XML_PARSE_OPTS
Definition: xml.c:44
#define XML_TAG_DIFF
Definition: msg_xml.h:404
const char * bz2_strerror(int rc)
Definition: results.c:718
gboolean safe_str_neq(const char *a, const char *b)
Definition: strings.c:263
#define INFINITY
Definition: crm.h:95
char * crm_generate_uuid(void)
Definition: utils.c:495
void pcmk_free_nvpairs(GSList *nvpairs)
Free a list of name/value pairs.
Definition: nvpair.c:102
#define XML_ATTR_NUMUPDATES
Definition: msg_xml.h:89
void pcmk__free_acls(GList *acls)
Definition: acl.c:45
void crm_buffer_add_char(char **buffer, int *offset, int *max, char c)
Definition: xml.c:3293
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:437
void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, gboolean formatted)
G_GNUC_INTERNAL int pcmk__element_xpath(const char *prefix, xmlNode *xml, char *buffer, int offset, size_t buffer_size)
Definition: xml.c:2030
G_GNUC_INTERNAL void pcmk__mark_xml_attr_dirty(xmlAttr *a)
Definition: xml.c:193
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:4399
void crm_xml_cleanup(void)
Definition: xml.c:4462
const char * crm_xml_add_int(xmlNode *node, const char *name, int value)
Create an XML attribute with specified name and integer value.
Definition: nvpair.c:424
xmlNode * pcmk_create_html_node(xmlNode *parent, const char *element_name, const char *id, const char *class_name, const char *text)
Definition: xml.c:2013
#define CRM_FEATURE_SET
Definition: crm.h:54
#define XML_ATTR_UPDATE_USER
Definition: msg_xml.h:105
int char2score(const char *score)
Definition: utils.c:59
xmlNode * find_entity(xmlNode *parent, const char *node_name, const char *id)
Definition: xml.c:1829
void xml_track_changes(xmlNode *xml, const char *user, xmlNode *acl_source, bool enforce_acls)
Definition: xml.c:309
#define buffer_print(buffer, max, offset, fmt, args...)
Definition: xml.c:87
void crm_schema_cleanup(void)
Definition: schemas.c:554
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: nvpair.c:316
void crm_xml_init(void)
Definition: xml.c:4440
#define pcmk_err_generic
Definition: results.h:70
int write_xml_file(xmlNode *xml_node, const char *filename, gboolean compress)
Write XML to a file.
Definition: xml.c:2606
#define XML_ATTR_IDREF
Definition: msg_xml.h:97
#define XML_NVPAIR_ATTR_NAME
Definition: msg_xml.h:339
void pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
Definition: acl.c:565
int get_attr_name(const char *input, size_t offset, size_t max)
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:219
struct xml_change_obj_s xml_change_obj_t
#define do_crm_log_alias(level, file, function, line, fmt, args...)
Log a message as if it came from a different code location.
Definition: logging.h:286
#define clear_bit(word, bit)
Definition: crm_internal.h:69
void copy_in_properties(xmlNode *target, xmlNode *src)
Definition: xml.c:1836
void xml_accept_changes(xmlNode *xml)
Definition: xml.c:1016
xmlNode * filename2xml(const char *filename)
Definition: xml.c:2356
unsigned int crm_trace_nonlog
Definition: logging.c:39
GSList * pcmk_xml_attrs2nvpairs(xmlNode *xml)
Create a list of name/value pairs from an XML node&#39;s attributes.
Definition: nvpair.c:161
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:558
void purge_diff_markers(xmlNode *a_node)
Definition: xml.c:3361
#define LOG_NEVER
Definition: logging.h:46
xmlNode * get_xpath_object(const char *xpath, xmlNode *xml_obj, int error_level)
Definition: xpath.c:211
#define CRM_XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
Definition: xml_internal.h:77
xmlNode * string2xml(const char *input)
Definition: xml.c:2180
void crm_xml_sanitize_id(char *id)
Sanitize a string so it is usable as an XML ID.
Definition: xml.c:2440
#define XML_ATTR_GENERATION
Definition: msg_xml.h:87
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:1938
void expand_plus_plus(xmlNode *target, const char *name, const char *value)
Definition: xml.c:1877
#define XML_ATTR_ORIGIN
Definition: msg_xml.h:91
bool xml_tracking_changes(xmlNode *xml)
Definition: xml.c:324
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:2142
#define CHUNK_SIZE
Definition: xml.c:71
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:635
#define crm_warn(fmt, args...)
Definition: logging.h:364
#define pcmk_err_diff_failed
Definition: results.h:75
#define set_bit(word, bit)
Definition: crm_internal.h:68
xmlNode * stdin2xml(void)
Definition: xml.c:2241
void pcmk_free_xml_subtree(xmlNode *xml)
Definition: xml.c:2069
int rc
Definition: pcmk_fence.c:34
#define XML_DIFF_OP
Definition: msg_xml.h:412
#define crm_debug(fmt, args...)
Definition: logging.h:368
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: xml.c:1687
#define XML_DIFF_ATTR
Definition: msg_xml.h:410
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:725
#define XML_DIFF_VERSION
Definition: msg_xml.h:405
#define XML_ATTR_ID
Definition: msg_xml.h:96
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:522
gboolean update_xml_child(xmlNode *child, xmlNode *to_update)
Definition: xml.c:4218
xmlNode * pcmk_create_xml_text_node(xmlNode *parent, const char *name, const char *content)
Definition: xml.c:2001
#define crm_trace(fmt, args...)
Definition: logging.h:369
#define XML_BUFFER_SIZE
Definition: xml.c:32
#define crm_log_xml_explicit(xml, text)
Definition: logging.h:379
#define XML_PRIVATE_MAGIC
Definition: xml.c:214
xmlNode * add_node_copy(xmlNode *new_parent, xmlNode *xml_node)
Definition: xml.c:1954
#define crm_log_xml_debug(xml, text)
Definition: logging.h:376
xmlNode * expand_idref(xmlNode *input, xmlNode *top)
Definition: xml.c:4472
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: xml.c:3392
Wrappers for and extensions to libxml2.
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:335
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:1976
#define crm_log_xml_warn(xml, text)
Definition: logging.h:373
char * dump_xml_formatted(xmlNode *msg)
Definition: xml.c:3311
G_GNUC_INTERNAL void pcmk__set_xml_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:140
#define XML_DIFF_POSITION
Definition: msg_xml.h:414
#define XML_TAG_RESOURCE_REF
Definition: msg_xml.h:173
void void crm_destroy_xml(gpointer data)
xmlNode destructor which can be used in glib collections
Definition: xml.c:4505
void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
Definition: acl.c:313
GListPtr deleted_objs
gboolean add_message_xml(xmlNode *msg, const char *field, xmlNode *xml)
Definition: xml.c:2627
void free_xml(xmlNode *child)
Definition: xml.c:2136
gboolean xml_has_children(const xmlNode *root)
Definition: xml.c:3331
gboolean crm_str_eq(const char *a, const char *b, gboolean use_case)
Definition: strings.c:326
xml_private_flags
const char * pcmk__epoch2str(time_t *when)
Definition: iso8601.c:1715
xmlNode * sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
Definition: xml.c:4367
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition: xml.c:791
const xmlChar * pcmkXmlStr
Definition: xml.h:51
gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
Definition: logging.c:561
#define XML_DIFF_VSOURCE
Definition: msg_xml.h:406
int find_xml_children(xmlNode **children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches)
Definition: xml.c:4252
const char * target
Definition: pcmk_fence.c:28
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
Definition: xml.c:1208
void xml_log_changes(uint8_t level, const char *function, xmlNode *xml)
Definition: xml.c:981
#define CRM_XS
Definition: logging.h:54
#define XML_TAG_CIB
Definition: msg_xml.h:76
#define XML_DIFF_CHANGE
Definition: msg_xml.h:408
#define XML_DIFF_PATH
Definition: msg_xml.h:413
#define XML_DIFF_VTARGET
Definition: msg_xml.h:407
pcmk__xml_artefact_ns
Definition: xml_internal.h:136
#define pcmk_err_diff_resync
Definition: results.h:76
#define crm_log_xml_err(xml, text)
Definition: logging.h:372
#define XML_DIFF_LIST
Definition: msg_xml.h:409
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:314
#define XML_DIFF_MARKER
Definition: msg_xml.h:75
#define crm_err(fmt, args...)
Definition: logging.h:363
#define CRM_ASSERT(expr)
Definition: results.h:42
#define ENOTUNIQ
Definition: portability.h:134
#define CRM_SCHEMA_DIRECTORY
Definition: config.h:47
#define XML_CIB_ATTR_WRITTEN
Definition: msg_xml.h:93
const char * pcmk__get_tmpdir(void)
Definition: io.c:559
void crm_xml_set_id(xmlNode *xml, const char *format,...) __attribute__((__format__(__printf__
#define XML_ACL_TAG_ROLE_REFv1
Definition: msg_xml.h:376
int get_attr_value(const char *input, size_t offset, size_t max)
void pcmk__apply_acl(xmlNode *xml)
Definition: acl.c:242
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:3340
char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version)
Calculate and return digest of XML tree.
Definition: digest.c:184
gboolean can_prune_leaf(xmlNode *xml_node)
Definition: xml.c:3819
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:611
int compare_version(const char *version1, const char *version2)
Definition: utils.c:227
#define crm_log_xml_info(xml, text)
Definition: logging.h:375
char * xml_get_path(xmlNode *xml)
Definition: xml.c:2052
char * dump_xml_unformatted(xmlNode *msg)
Definition: xml.c:3321
#define DIMOF(a)
Definition: crm.h:57
#define XML_ATTR_GENERATION_ADMIN
Definition: msg_xml.h:88
#define XML_NVPAIR_ATTR_VALUE
Definition: msg_xml.h:340
char data[0]
Definition: internal.h:90
#define crm_str(x)
Definition: logging.h:389
#define XML_ATTR_CRM_VERSION
Definition: msg_xml.h:79
void pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
Add XML attributes based on a list of name/value pairs.
Definition: nvpair.c:201
void save_xml_to_file(xmlNode *xml, const char *desc, const char *filename)
Definition: xml.c:3374
void strip_text_nodes(xmlNode *xml)
Definition: xml.c:2328
#define pcmk_ok
Definition: results.h:67
xmlNode * diff_xml_object(xmlNode *left, xmlNode *right, gboolean suppress)
Definition: xml.c:3791
int add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
Definition: xml.c:1968
int get_tag_name(const char *input, size_t offset, size_t max)
#define XML_CIB_TAG_OBJ_REF
Definition: msg_xml.h:393
#define crm_log_xml_trace(xml, text)
Definition: logging.h:377
#define XML_ACL_TAG_ROLE_REF
Definition: msg_xml.h:375
G_GNUC_INTERNAL bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
Definition: xml.c:74
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: xml.c:3914
#define XML_CIB_TAG_CONFIGURATION
Definition: msg_xml.h:138
#define ID(x)
Definition: msg_xml.h:418
void xml_log_patchset(uint8_t level, const char *function, xmlNode *xml)
Definition: xml.c:824
#define safe_str_eq(a, b)
Definition: util.h:65
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition: xml.c:3769
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition: xml.c:2584
char * crm_xml_escape(const char *text)
Definition: xml.c:2653
void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork)
Definition: utils.c:341
char * name
Definition: pcmk_fence.c:30
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:4539
void fix_plus_plus_recursive(xmlNode *target)
Definition: xml.c:1859
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version)
Definition: xml.c:734
#define LOG_STDOUT
Definition: logging.h:41
GList * GListPtr
Definition: crm.h:214
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
Definition: xml.c:3776
#define crm_info(fmt, args...)
Definition: logging.h:366
const char * crm_xml_add_last_written(xmlNode *xml_node)
Definition: xml.c:2426
char * dump_xml_formatted_with_text(xmlNode *msg)
Definition: xml.c:3299
uint32_t version
Definition: remote.c:147
uint64_t flags
Definition: remote.c:149
#define XML_ATTR_DIGEST
Definition: msg_xml.h:80
gboolean replace_xml_child(xmlNode *parent, xmlNode *child, xmlNode *update, gboolean delete_only)
Definition: xml.c:4284
void crm_xml_dump(xmlNode *data, int options, char **buffer, int *offset, int *max, int depth)
Definition: xml.c:3186
xmlNode * crm_next_same_xml(const xmlNode *sibling)
Get next instance of same XML tag.
Definition: xml.c:4425
struct xml_deleted_obj_s xml_deleted_obj_t