pacemaker  2.1.3-ea053b43a
Scalable High-Availability cluster resource manager
patchset.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2021 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdio.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <time.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <stdarg.h>
19 #include <bzlib.h>
20 
21 #include <libxml/parser.h>
22 #include <libxml/tree.h>
23 #include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */
24 
25 #include <crm/crm.h>
26 #include <crm/msg_xml.h>
27 #include <crm/common/xml.h>
28 #include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
29 #include "crmcommon_private.h"
30 
31 static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
32  xmlNode *right, gboolean *changed);
33 
34 /*
35 <diff format="2.0">
36  <version>
37  <source admin_epoch="1" epoch="2" num_updates="3"/>
38  <target admin_epoch="1" epoch="3" num_updates="0"/>
39  </version>
40  <change operation="add" xpath="/cib/configuration/nodes">
41  <node id="node2" uname="node2" description="foo"/>
42  </change>
43  <change operation="add" xpath="/cib/configuration/nodes/node[node2]">
44  <instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
45  <nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
46  </instance_attributes>
47  </change>
48  <change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
49  <change-list>
50  <change-attr operation="set" name="type" value="member"/>
51  <change-attr operation="unset" name="description"/>
52  </change-list>
53  <change-result>
54  <node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
55  </change-result>
56  </change>
57  <change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
58  <change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
59  <change-list>
60  <change-attr operation="set" name="description" value="some garbage here"/>
61  </change-list>
62  <change-result>
63  <group id="g1" description="some garbage here"/><!-- NOTE: not recursive -->
64  </change-result>
65  </change>
66  <change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
67  <change-list>
68  <change-attr operation="set" name="oper" value="member"/>
69  <change-attr operation="set" name="operation_key" value="Fence_start_0"/>
70  <change-attr operation="set" name="operation" value="start"/>
71  <change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
72  <change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
73  <change-attr operation="set" name="call-id" value="2"/>
74  <change-attr operation="set" name="rc-code" value="0"/>
75  </change-list>
76  <change-result>
77  <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"/>
78  </change-result>
79  </change>
80 </diff>
81  */
82 
83 // Add changes for specified XML to patchset
84 static void
85 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
86 {
87  xmlNode *cIter = NULL;
88  xmlAttr *pIter = NULL;
89  xmlNode *change = NULL;
90  xml_private_t *p = xml->_private;
91  const char *value = NULL;
92 
93  // If this XML node is new, just report that
94  if (patchset && pcmk_is_set(p->flags, pcmk__xf_created)) {
95  int offset = 0;
96  char buffer[PCMK__BUFFER_SIZE];
97 
98  if (pcmk__element_xpath(NULL, xml->parent, buffer, offset,
99  sizeof(buffer)) > 0) {
100  int position = pcmk__xml_position(xml, pcmk__xf_deleted);
101 
102  change = create_xml_node(patchset, XML_DIFF_CHANGE);
103 
104  crm_xml_add(change, XML_DIFF_OP, "create");
105  crm_xml_add(change, XML_DIFF_PATH, buffer);
106  crm_xml_add_int(change, XML_DIFF_POSITION, position);
107  add_node_copy(change, xml);
108  }
109 
110  return;
111  }
112 
113  // Check each of the XML node's attributes for changes
114  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
115  pIter = pIter->next) {
116  xmlNode *attr = NULL;
117 
118  p = pIter->_private;
119  if (!pcmk_any_flags_set(p->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
120  continue;
121  }
122 
123  if (change == NULL) {
124  int offset = 0;
125  char buffer[PCMK__BUFFER_SIZE];
126 
127  if (pcmk__element_xpath(NULL, xml, buffer, offset,
128  sizeof(buffer)) > 0) {
129  change = create_xml_node(patchset, XML_DIFF_CHANGE);
130 
131  crm_xml_add(change, XML_DIFF_OP, "modify");
132  crm_xml_add(change, XML_DIFF_PATH, buffer);
133 
134  change = create_xml_node(change, XML_DIFF_LIST);
135  }
136  }
137 
138  attr = create_xml_node(change, XML_DIFF_ATTR);
139 
140  crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
141  if (p->flags & pcmk__xf_deleted) {
142  crm_xml_add(attr, XML_DIFF_OP, "unset");
143 
144  } else {
145  crm_xml_add(attr, XML_DIFF_OP, "set");
146 
147  value = crm_element_value(xml, (const char *) pIter->name);
148  crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
149  }
150  }
151 
152  if (change) {
153  xmlNode *result = NULL;
154 
155  change = create_xml_node(change->parent, XML_DIFF_RESULT);
156  result = create_xml_node(change, (const char *)xml->name);
157 
158  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
159  pIter = pIter->next) {
160  p = pIter->_private;
161  if (!pcmk_is_set(p->flags, pcmk__xf_deleted)) {
162  value = crm_element_value(xml, (const char *) pIter->name);
163  crm_xml_add(result, (const char *)pIter->name, value);
164  }
165  }
166  }
167 
168  // Now recursively do the same for each child node of this node
169  for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
170  cIter = pcmk__xml_next(cIter)) {
171  add_xml_changes_to_patchset(cIter, patchset);
172  }
173 
174  p = xml->_private;
175  if (patchset && pcmk_is_set(p->flags, pcmk__xf_moved)) {
176  int offset = 0;
177  char buffer[PCMK__BUFFER_SIZE];
178 
179  crm_trace("%s.%s moved to position %d",
180  xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
181  if (pcmk__element_xpath(NULL, xml, buffer, offset,
182  sizeof(buffer)) > 0) {
183  change = create_xml_node(patchset, XML_DIFF_CHANGE);
184 
185  crm_xml_add(change, XML_DIFF_OP, "move");
186  crm_xml_add(change, XML_DIFF_PATH, buffer);
189  }
190  }
191 }
192 
193 static bool
194 is_config_change(xmlNode *xml)
195 {
196  GList *gIter = NULL;
197  xml_private_t *p = NULL;
198  xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
199 
200  if (config) {
201  p = config->_private;
202  }
203  if ((p != NULL) && pcmk_is_set(p->flags, pcmk__xf_dirty)) {
204  return TRUE;
205  }
206 
207  if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
208  p = xml->doc->_private;
209  for (gIter = p->deleted_objs; gIter; gIter = gIter->next) {
210  pcmk__deleted_xml_t *deleted_obj = gIter->data;
211 
212  if (strstr(deleted_obj->path,
213  "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION) != NULL) {
214  return TRUE;
215  }
216  }
217  }
218  return FALSE;
219 }
220 
221 static void
222 xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
223  gboolean changed)
224 {
225  int lpc = 0;
226  xmlNode *cib = NULL;
227  xmlNode *diff_child = NULL;
228 
229  const char *tag = NULL;
230 
231  const char *vfields[] = {
235  };
236 
237  if (local_diff == NULL) {
238  crm_trace("Nothing to do");
239  return;
240  }
241 
242  tag = "diff-removed";
243  diff_child = find_xml_node(local_diff, tag, FALSE);
244  if (diff_child == NULL) {
245  diff_child = create_xml_node(local_diff, tag);
246  }
247 
248  tag = XML_TAG_CIB;
249  cib = find_xml_node(diff_child, tag, FALSE);
250  if (cib == NULL) {
251  cib = create_xml_node(diff_child, tag);
252  }
253 
254  for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
255  const char *value = crm_element_value(last, vfields[lpc]);
256 
257  crm_xml_add(diff_child, vfields[lpc], value);
258  if (changed || lpc == 2) {
259  crm_xml_add(cib, vfields[lpc], value);
260  }
261  }
262 
263  tag = "diff-added";
264  diff_child = find_xml_node(local_diff, tag, FALSE);
265  if (diff_child == NULL) {
266  diff_child = create_xml_node(local_diff, tag);
267  }
268 
269  tag = XML_TAG_CIB;
270  cib = find_xml_node(diff_child, tag, FALSE);
271  if (cib == NULL) {
272  cib = create_xml_node(diff_child, tag);
273  }
274 
275  for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
276  const char *value = crm_element_value(next, vfields[lpc]);
277 
278  crm_xml_add(diff_child, vfields[lpc], value);
279  }
280 
281  for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
282  const char *p_value = crm_element_value(next, (const char *) a->name);
283 
284  xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
285  }
286 
287  crm_log_xml_explicit(local_diff, "Repaired-diff");
288 }
289 
290 static xmlNode *
291 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
292  bool suppress)
293 {
294  xmlNode *patchset = diff_xml_object(source, target, suppress);
295 
296  if (patchset) {
298  xml_repair_v1_diff(source, target, patchset, config);
299  crm_xml_add(patchset, "format", "1");
300  }
301  return patchset;
302 }
303 
304 static xmlNode *
305 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
306 {
307  int lpc = 0;
308  GList *gIter = NULL;
309  xml_private_t *doc = NULL;
310 
311  xmlNode *v = NULL;
312  xmlNode *version = NULL;
313  xmlNode *patchset = NULL;
314  const char *vfields[] = {
318  };
319 
321  if (!xml_document_dirty(target)) {
322  return NULL;
323  }
324 
325  CRM_ASSERT(target->doc);
326  doc = target->doc->_private;
327 
328  patchset = create_xml_node(NULL, XML_TAG_DIFF);
329  crm_xml_add_int(patchset, "format", 2);
330 
332 
334  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
335  const char *value = crm_element_value(source, vfields[lpc]);
336 
337  if (value == NULL) {
338  value = "1";
339  }
340  crm_xml_add(v, vfields[lpc], value);
341  }
342 
344  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
345  const char *value = crm_element_value(target, vfields[lpc]);
346 
347  if (value == NULL) {
348  value = "1";
349  }
350  crm_xml_add(v, vfields[lpc], value);
351  }
352 
353  for (gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
354  pcmk__deleted_xml_t *deleted_obj = gIter->data;
355  xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
356 
357  crm_xml_add(change, XML_DIFF_OP, "delete");
358  crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
359  if (deleted_obj->position >= 0) {
360  crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
361  }
362  }
363 
364  add_xml_changes_to_patchset(target, patchset);
365  return patchset;
366 }
367 
368 xmlNode *
369 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
370  bool *config_changed, bool manage_version)
371 {
372  int counter = 0;
373  bool config = FALSE;
374  xmlNode *patch = NULL;
375  const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
376 
378  if (!xml_document_dirty(target)) {
379  crm_trace("No change %d", format);
380  return NULL; /* No change */
381  }
382 
383  config = is_config_change(target);
384  if (config_changed) {
385  *config_changed = config;
386  }
387 
388  if (manage_version && config) {
389  crm_trace("Config changed %d", format);
391 
394 
395  } else if (manage_version) {
397  crm_trace("Status changed %d - %d %s", format, counter,
399  crm_xml_add_int(target, XML_ATTR_NUMUPDATES, (counter + 1));
400  }
401 
402  if (format == 0) {
403  if (compare_version("3.0.8", version) < 0) {
404  format = 2;
405  } else {
406  format = 1;
407  }
408  crm_trace("Using patch format %d for version: %s", format, version);
409  }
410 
411  switch (format) {
412  case 1:
413  patch = xml_create_patchset_v1(source, target, config, FALSE);
414  break;
415  case 2:
416  patch = xml_create_patchset_v2(source, target);
417  break;
418  default:
419  crm_err("Unknown patch format: %d", format);
420  return NULL;
421  }
422  return patch;
423 }
424 
425 void
426 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
427  bool with_digest)
428 {
429  int format = 1;
430  const char *version = NULL;
431  char *digest = NULL;
432 
433  if ((patch == NULL) || (source == NULL) || (target == NULL)) {
434  return;
435  }
436 
437  /* We should always call xml_accept_changes() before calculating a digest.
438  * Otherwise, with an on-tracking dirty target, we could get a wrong digest.
439  */
441 
442  crm_element_value_int(patch, "format", &format);
443  if ((format > 1) && !with_digest) {
444  return;
445  }
446 
448  digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
449 
450  crm_xml_add(patch, XML_ATTR_DIGEST, digest);
451  free(digest);
452 
453  return;
454 }
455 
456 void
457 xml_log_patchset(uint8_t log_level, const char *function, xmlNode *patchset)
458 {
459  int format = 1;
460  xmlNode *child = NULL;
461  xmlNode *added = NULL;
462  xmlNode *removed = NULL;
463  gboolean is_first = TRUE;
464 
465  int add[] = { 0, 0, 0 };
466  int del[] = { 0, 0, 0 };
467 
468  const char *fmt = NULL;
469  const char *digest = NULL;
470  int options = xml_log_option_formatted;
471 
472  static struct qb_log_callsite *patchset_cs = NULL;
473 
474  if (log_level == LOG_NEVER) {
475  return;
476  }
477  if (patchset_cs == NULL) {
478  patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset",
479  log_level, __LINE__, 0);
480  }
481 
482  if (patchset == NULL) {
483  crm_trace("Empty patch");
484  return;
485 
486  } else if ((log_level != LOG_STDOUT)
487  && !crm_is_callsite_active(patchset_cs, log_level, 0)) {
488  return;
489  }
490 
491  xml_patch_versions(patchset, add, del);
492  fmt = crm_element_value(patchset, "format");
493  digest = crm_element_value(patchset, XML_ATTR_DIGEST);
494 
495  if ((add[2] != del[2]) || (add[1] != del[1]) || (add[0] != del[0])) {
496  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
497  "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
498  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
499  "Diff: +++ %d.%d.%d %s",
500  add[0], add[1], add[2], digest);
501 
502  } else if ((patchset != NULL) && (add[0] || add[1] || add[2])) {
503  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
504  "%s: Local-only Change: %d.%d.%d",
505  (function? function : ""), add[0], add[1], add[2]);
506  }
507 
508  crm_element_value_int(patchset, "format", &format);
509  if (format == 2) {
510  xmlNode *change = NULL;
511 
512  for (change = pcmk__xml_first_child(patchset); change != NULL;
513  change = pcmk__xml_next(change)) {
514  const char *op = crm_element_value(change, XML_DIFF_OP);
515  const char *xpath = crm_element_value(change, XML_DIFF_PATH);
516 
517  if (op == NULL) {
518  } else if (strcmp(op, "create") == 0) {
519  int lpc = 0, max = 0;
520  char *prefix = crm_strdup_printf("++ %s: ", xpath);
521 
522  max = strlen(prefix);
523  pcmk__xe_log(log_level, __FILE__, function, __LINE__, prefix,
524  change->children, 0,
526 
527  for (lpc = 2; lpc < max; lpc++) {
528  prefix[lpc] = ' ';
529  }
530 
531  pcmk__xe_log(log_level, __FILE__, function, __LINE__, prefix,
532  change->children, 0,
535  free(prefix);
536 
537  } else if (strcmp(op, "move") == 0) {
538  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
539  "+~ %s moved to offset %s", xpath,
541 
542  } else if (strcmp(op, "modify") == 0) {
543  xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
544  char buffer_set[PCMK__BUFFER_SIZE];
545  char buffer_unset[PCMK__BUFFER_SIZE];
546  int o_set = 0;
547  int o_unset = 0;
548 
549  buffer_set[0] = 0;
550  buffer_unset[0] = 0;
551  for (child = pcmk__xml_first_child(clist); child != NULL;
552  child = pcmk__xml_next(child)) {
553  const char *name = crm_element_value(child, "name");
554 
555  op = crm_element_value(child, XML_DIFF_OP);
556  if (op == NULL) {
557  } else if (strcmp(op, "set") == 0) {
558  const char *value = crm_element_value(child, "value");
559 
560  if (o_set > 0) {
561  o_set += snprintf(buffer_set + o_set,
562  PCMK__BUFFER_SIZE - o_set, ", ");
563  }
564  o_set += snprintf(buffer_set + o_set,
565  PCMK__BUFFER_SIZE - o_set, "@%s=%s",
566  name, value);
567 
568  } else if (strcmp(op, "unset") == 0) {
569  if (o_unset > 0) {
570  o_unset += snprintf(buffer_unset + o_unset,
571  PCMK__BUFFER_SIZE - o_unset,
572  ", ");
573  }
574  o_unset += snprintf(buffer_unset + o_unset,
575  PCMK__BUFFER_SIZE - o_unset, "@%s",
576  name);
577  }
578  }
579  if (o_set) {
580  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
581  "+ %s: %s", xpath, buffer_set);
582  }
583  if (o_unset) {
584  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
585  "-- %s: %s", xpath, buffer_unset);
586  }
587 
588  } else if (strcmp(op, "delete") == 0) {
589  int position = -1;
590 
591  crm_element_value_int(change, XML_DIFF_POSITION, &position);
592  if (position >= 0) {
593  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
594  "-- %s (%d)", xpath, position);
595 
596  } else {
597  do_crm_log_alias(log_level, __FILE__, function, __LINE__,
598  "-- %s", xpath);
599  }
600  }
601  }
602  return;
603  }
604 
605  if ((log_level < LOG_DEBUG) || (function == NULL)) {
606  options |= xml_log_option_diff_short;
607  }
608 
609  removed = find_xml_node(patchset, "diff-removed", FALSE);
610  for (child = pcmk__xml_first_child(removed); child != NULL;
611  child = pcmk__xml_next(child)) {
612  log_data_element(log_level, __FILE__, function, __LINE__, "- ", child,
613  0, options|xml_log_option_diff_minus);
614  if (is_first) {
615  is_first = FALSE;
616  } else {
617  do_crm_log_alias(log_level, __FILE__, function, __LINE__, " --- ");
618  }
619  }
620 
621  is_first = TRUE;
622  added = find_xml_node(patchset, "diff-added", FALSE);
623  for (child = pcmk__xml_first_child(added); child != NULL;
624  child = pcmk__xml_next(child)) {
625  log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child,
626  0, options|xml_log_option_diff_plus);
627  if (is_first) {
628  is_first = FALSE;
629  } else {
630  do_crm_log_alias(log_level, __FILE__, function, __LINE__, " +++ ");
631  }
632  }
633 }
634 
635 // Return true if attribute name is not "id"
636 static bool
637 not_id(xmlAttrPtr attr, void *user_data)
638 {
639  return strcmp((const char *) attr->name, XML_ATTR_ID) != 0;
640 }
641 
642 // Apply the removals section of an v1 patchset to an XML node
643 static void
644 process_v1_removals(xmlNode *target, xmlNode *patch)
645 {
646  xmlNode *patch_child = NULL;
647  xmlNode *cIter = NULL;
648 
649  char *id = NULL;
650  const char *name = NULL;
651  const char *value = NULL;
652 
653  if ((target == NULL) || (patch == NULL)) {
654  return;
655  }
656 
657  if (target->type == XML_COMMENT_NODE) {
658  gboolean dummy;
659 
660  subtract_xml_comment(target->parent, target, patch, &dummy);
661  }
662 
663  name = crm_element_name(target);
664  CRM_CHECK(name != NULL, return);
665  CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
667  return);
668  CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
669 
670  // Check for XML_DIFF_MARKER in a child
672  value = crm_element_value(patch, XML_DIFF_MARKER);
673  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
674  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
675  free_xml(target);
676  free(id);
677  return;
678  }
679 
680  // Removing then restoring id would change ordering of properties
681  pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
682 
683  // Changes to child objects
684  cIter = pcmk__xml_first_child(target);
685  while (cIter) {
686  xmlNode *target_child = cIter;
687 
688  cIter = pcmk__xml_next(cIter);
689  patch_child = pcmk__xml_match(patch, target_child, false);
690  process_v1_removals(target_child, patch_child);
691  }
692  free(id);
693 }
694 
695 // Apply the additions section of an v1 patchset to an XML node
696 static void
697 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
698 {
699  xmlNode *patch_child = NULL;
700  xmlNode *target_child = NULL;
701  xmlAttrPtr xIter = NULL;
702 
703  const char *id = NULL;
704  const char *name = NULL;
705  const char *value = NULL;
706 
707  if (patch == NULL) {
708  return;
709  } else if ((parent == NULL) && (target == NULL)) {
710  return;
711  }
712 
713  // Check for XML_DIFF_MARKER in a child
714  value = crm_element_value(patch, XML_DIFF_MARKER);
715  if ((target == NULL) && (value != NULL)
716  && (strcmp(value, "added:top") == 0)) {
717  id = ID(patch);
718  name = crm_element_name(patch);
719  crm_trace("We are the root of the addition: %s.id=%s", name, id);
720  add_node_copy(parent, patch);
721  return;
722 
723  } else if (target == NULL) {
724  id = ID(patch);
725  name = crm_element_name(patch);
726  crm_err("Could not locate: %s.id=%s", name, id);
727  return;
728  }
729 
730  if (target->type == XML_COMMENT_NODE) {
731  pcmk__xc_update(parent, target, patch);
732  }
733 
734  name = crm_element_name(target);
735  CRM_CHECK(name != NULL, return);
736  CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
738  return);
739  CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
740 
741  for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
742  xIter = xIter->next) {
743  const char *p_name = (const char *) xIter->name;
744  const char *p_value = crm_element_value(patch, p_name);
745 
746  xml_remove_prop(target, p_name); // Preserve patch order
747  crm_xml_add(target, p_name, p_value);
748  }
749 
750  // Changes to child objects
751  for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
752  patch_child = pcmk__xml_next(patch_child)) {
753 
754  target_child = pcmk__xml_match(target, patch_child, false);
755  process_v1_additions(target, target_child, patch_child);
756  }
757 }
758 
770 static bool
771 find_patch_xml_node(xmlNode *patchset, int format, bool added,
772  xmlNode **patch_node)
773 {
774  xmlNode *cib_node;
775  const char *label;
776 
777  switch (format) {
778  case 1:
779  label = added? "diff-added" : "diff-removed";
780  *patch_node = find_xml_node(patchset, label, FALSE);
781  cib_node = find_xml_node(*patch_node, "cib", FALSE);
782  if (cib_node != NULL) {
783  *patch_node = cib_node;
784  }
785  break;
786  case 2:
787  label = added? "target" : "source";
788  *patch_node = find_xml_node(patchset, "version", FALSE);
789  *patch_node = find_xml_node(*patch_node, label, FALSE);
790  break;
791  default:
792  crm_warn("Unknown patch format: %d", format);
793  *patch_node = NULL;
794  return FALSE;
795  }
796  return TRUE;
797 }
798 
799 // Get CIB versions used for additions and deletions in a patchset
800 bool
801 xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
802 {
803  int lpc = 0;
804  int format = 1;
805  xmlNode *tmp = NULL;
806 
807  const char *vfields[] = {
811  };
812 
813 
814  crm_element_value_int(patchset, "format", &format);
815 
816  /* Process removals */
817  if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
818  return -EINVAL;
819  }
820  if (tmp != NULL) {
821  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
822  crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
823  crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
824  }
825  }
826 
827  /* Process additions */
828  if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
829  return -EINVAL;
830  }
831  if (tmp != NULL) {
832  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
833  crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
834  crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
835  }
836  }
837  return pcmk_ok;
838 }
839 
850 static int
851 xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
852 {
853  int lpc = 0;
854  bool changed = FALSE;
855 
856  int this[] = { 0, 0, 0 };
857  int add[] = { 0, 0, 0 };
858  int del[] = { 0, 0, 0 };
859 
860  const char *vfields[] = {
864  };
865 
866  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
867  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
868  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
869  if (this[lpc] < 0) {
870  this[lpc] = 0;
871  }
872  }
873 
874  /* Set some defaults in case nothing is present */
875  add[0] = this[0];
876  add[1] = this[1];
877  add[2] = this[2] + 1;
878  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
879  del[lpc] = this[lpc];
880  }
881 
882  xml_patch_versions(patchset, add, del);
883 
884  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
885  if (this[lpc] < del[lpc]) {
886  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
887  vfields[lpc], this[0], this[1], this[2],
888  del[0], del[1], del[2], add[0], add[1], add[2]);
889  return pcmk_rc_diff_resync;
890 
891  } else if (this[lpc] > del[lpc]) {
892  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
893  vfields[lpc], this[0], this[1], this[2],
894  del[0], del[1], del[2], add[0], add[1], add[2], patchset);
895  crm_log_xml_info(patchset, "OldPatch");
896  return pcmk_rc_old_data;
897  }
898  }
899 
900  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
901  if (add[lpc] > del[lpc]) {
902  changed = TRUE;
903  }
904  }
905 
906  if (!changed) {
907  crm_notice("Versions did not change in patch %d.%d.%d",
908  add[0], add[1], add[2]);
909  return pcmk_rc_old_data;
910  }
911 
912  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
913  add[0], add[1], add[2], this[0], this[1], this[2]);
914  return pcmk_rc_ok;
915 }
916 
926 static int
927 apply_v1_patchset(xmlNode *xml, xmlNode *patchset)
928 {
929  int rc = pcmk_rc_ok;
930  int root_nodes_seen = 0;
931 
932  xmlNode *child_diff = NULL;
933  xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
934  xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
935  xmlNode *old = copy_xml(xml);
936 
937  crm_trace("Subtraction Phase");
938  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
939  child_diff = pcmk__xml_next(child_diff)) {
940  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
941  if (root_nodes_seen == 0) {
942  process_v1_removals(xml, child_diff);
943  }
944  root_nodes_seen++;
945  }
946 
947  if (root_nodes_seen > 1) {
948  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
949  root_nodes_seen);
950  rc = ENOTUNIQ;
951  }
952 
953  root_nodes_seen = 0;
954  crm_trace("Addition Phase");
955  if (rc == pcmk_rc_ok) {
956  xmlNode *child_diff = NULL;
957 
958  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
959  child_diff = pcmk__xml_next(child_diff)) {
960  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
961  if (root_nodes_seen == 0) {
962  process_v1_additions(NULL, xml, child_diff);
963  }
964  root_nodes_seen++;
965  }
966  }
967 
968  if (root_nodes_seen > 1) {
969  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
970  root_nodes_seen);
971  rc = ENOTUNIQ;
972  }
973 
974  purge_diff_markers(xml); // Purge prior to checking digest
975 
976  free_xml(old);
977  return rc;
978 }
979 
980 // Return first child matching element name and optionally id or position
981 static xmlNode *
982 first_matching_xml_child(xmlNode *parent, const char *name, const char *id,
983  int position)
984 {
985  xmlNode *cIter = NULL;
986 
987  for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
988  cIter = pcmk__xml_next(cIter)) {
989  if (strcmp((const char *) cIter->name, name) != 0) {
990  continue;
991  } else if (id) {
992  const char *cid = ID(cIter);
993 
994  if ((cid == NULL) || (strcmp(cid, id) != 0)) {
995  continue;
996  }
997  }
998 
999  // "position" makes sense only for XML comments for now
1000  if ((cIter->type == XML_COMMENT_NODE)
1001  && (position >= 0)
1002  && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
1003  continue;
1004  }
1005 
1006  return cIter;
1007  }
1008  return NULL;
1009 }
1010 
1024 static xmlNode *
1025 search_v2_xpath(xmlNode *top, const char *key, int target_position)
1026 {
1027  xmlNode *target = (xmlNode *) top->doc;
1028  const char *current = key;
1029  char *section;
1030  char *remainder;
1031  char *id;
1032  char *tag;
1033  char *path = NULL;
1034  int rc;
1035  size_t key_len;
1036 
1037  CRM_CHECK(key != NULL, return NULL);
1038  key_len = strlen(key);
1039 
1040  /* These are scanned from key after a slash, so they can't be bigger
1041  * than key_len - 1 characters plus a null terminator.
1042  */
1043 
1044  remainder = calloc(key_len, sizeof(char));
1045  CRM_ASSERT(remainder != NULL);
1046 
1047  section = calloc(key_len, sizeof(char));
1048  CRM_ASSERT(section != NULL);
1049 
1050  id = calloc(key_len, sizeof(char));
1051  CRM_ASSERT(id != NULL);
1052 
1053  tag = calloc(key_len, sizeof(char));
1054  CRM_ASSERT(tag != NULL);
1055 
1056  do {
1057  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1058  rc = sscanf(current, "/%[^/]%s", section, remainder);
1059  if (rc > 0) {
1060  // Separate FIRST_COMPONENT into TAG[@id='ID']
1061  int f = sscanf(section, "%[^[][@id='%[^']", tag, id);
1062  int current_position = -1;
1063 
1064  /* The target position is for the final component tag, so only use
1065  * it if there is nothing left to search after this component.
1066  */
1067  if ((rc == 1) && (target_position >= 0)) {
1068  current_position = target_position;
1069  }
1070 
1071  switch (f) {
1072  case 1:
1073  target = first_matching_xml_child(target, tag, NULL,
1074  current_position);
1075  break;
1076  case 2:
1077  target = first_matching_xml_child(target, tag, id,
1078  current_position);
1079  break;
1080  default:
1081  // This should not be possible
1082  target = NULL;
1083  break;
1084  }
1085  current = remainder;
1086  }
1087 
1088  // Continue if something remains to search, and we've matched so far
1089  } while ((rc == 2) && target);
1090 
1091  if (target) {
1092  crm_trace("Found %s for %s",
1093  (path = (char *) xmlGetNodePath(target)), key);
1094  free(path);
1095  } else {
1096  crm_debug("No match for %s", key);
1097  }
1098 
1099  free(remainder);
1100  free(section);
1101  free(tag);
1102  free(id);
1103  return target;
1104 }
1105 
1106 typedef struct xml_change_obj_s {
1107  xmlNode *change;
1108  xmlNode *match;
1110 
1111 static gint
1112 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
1113 {
1114  const xml_change_obj_t *change_obj_a = a;
1115  const xml_change_obj_t *change_obj_b = b;
1116  int position_a = -1;
1117  int position_b = -1;
1118 
1119  crm_element_value_int(change_obj_a->change, XML_DIFF_POSITION, &position_a);
1120  crm_element_value_int(change_obj_b->change, XML_DIFF_POSITION, &position_b);
1121 
1122  if (position_a < position_b) {
1123  return -1;
1124 
1125  } else if (position_a > position_b) {
1126  return 1;
1127  }
1128 
1129  return 0;
1130 }
1131 
1141 static int
1142 apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
1143 {
1144  int rc = pcmk_rc_ok;
1145  xmlNode *change = NULL;
1146  GList *change_objs = NULL;
1147  GList *gIter = NULL;
1148 
1149  for (change = pcmk__xml_first_child(patchset); change != NULL;
1150  change = pcmk__xml_next(change)) {
1151  xmlNode *match = NULL;
1152  const char *op = crm_element_value(change, XML_DIFF_OP);
1153  const char *xpath = crm_element_value(change, XML_DIFF_PATH);
1154  int position = -1;
1155 
1156  if (op == NULL) {
1157  continue;
1158  }
1159 
1160  crm_trace("Processing %s %s", change->name, op);
1161 
1162  // "delete" changes for XML comments are generated with "position"
1163  if (strcmp(op, "delete") == 0) {
1164  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1165  }
1166  match = search_v2_xpath(xml, xpath, position);
1167  crm_trace("Performing %s on %s with %p", op, xpath, match);
1168 
1169  if ((match == NULL) && (strcmp(op, "delete") == 0)) {
1170  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1171  continue;
1172 
1173  } else if (match == NULL) {
1174  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1175  rc = pcmk_rc_diff_failed;
1176  continue;
1177 
1178  } else if ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) {
1179  // Delay the adding of a "create" object
1180  xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
1181 
1182  CRM_ASSERT(change_obj != NULL);
1183 
1184  change_obj->change = change;
1185  change_obj->match = match;
1186 
1187  change_objs = g_list_append(change_objs, change_obj);
1188 
1189  if (strcmp(op, "move") == 0) {
1190  // Temporarily put the "move" object after the last sibling
1191  if ((match->parent != NULL) && (match->parent->last != NULL)) {
1192  xmlAddNextSibling(match->parent->last, match);
1193  }
1194  }
1195 
1196  } else if (strcmp(op, "delete") == 0) {
1197  free_xml(match);
1198 
1199  } else if (strcmp(op, "modify") == 0) {
1200  xmlNode *attrs = NULL;
1201 
1202  attrs = pcmk__xml_first_child(first_named_child(change,
1203  XML_DIFF_RESULT));
1204  if (attrs == NULL) {
1205  rc = ENOMSG;
1206  continue;
1207  }
1208  pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
1209 
1210  for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
1211  pIter = pIter->next) {
1212  const char *name = (const char *) pIter->name;
1213  const char *value = crm_element_value(attrs, name);
1214 
1215  crm_xml_add(match, name, value);
1216  }
1217 
1218  } else {
1219  crm_err("Unknown operation: %s", op);
1220  rc = pcmk_rc_diff_failed;
1221  }
1222  }
1223 
1224  // Changes should be generated in the right order. Double checking.
1225  change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
1226 
1227  for (gIter = change_objs; gIter; gIter = gIter->next) {
1228  xml_change_obj_t *change_obj = gIter->data;
1229  xmlNode *match = change_obj->match;
1230  const char *op = NULL;
1231  const char *xpath = NULL;
1232 
1233  change = change_obj->change;
1234 
1235  op = crm_element_value(change, XML_DIFF_OP);
1236  xpath = crm_element_value(change, XML_DIFF_PATH);
1237 
1238  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1239 
1240  if (strcmp(op, "create") == 0) {
1241  int position = 0;
1242  xmlNode *child = NULL;
1243  xmlNode *match_child = NULL;
1244 
1245  match_child = match->children;
1246  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1247 
1248  while ((match_child != NULL)
1249  && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1250  match_child = match_child->next;
1251  }
1252 
1253  child = xmlDocCopyNode(change->children, match->doc, 1);
1254  if (match_child) {
1255  crm_trace("Adding %s at position %d", child->name, position);
1256  xmlAddPrevSibling(match_child, child);
1257 
1258  } else if (match->last) {
1259  crm_trace("Adding %s at position %d (end)",
1260  child->name, position);
1261  xmlAddNextSibling(match->last, child);
1262 
1263  } else {
1264  crm_trace("Adding %s at position %d (first)",
1265  child->name, position);
1266  CRM_LOG_ASSERT(position == 0);
1267  xmlAddChild(match, child);
1268  }
1269  pcmk__mark_xml_created(child);
1270 
1271  } else if (strcmp(op, "move") == 0) {
1272  int position = 0;
1273 
1274  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1275  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1276  xmlNode *match_child = NULL;
1277  int p = position;
1278 
1279  if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1280  p++; // Skip ourselves
1281  }
1282 
1283  CRM_ASSERT(match->parent != NULL);
1284  match_child = match->parent->children;
1285 
1286  while ((match_child != NULL)
1287  && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1288  match_child = match_child->next;
1289  }
1290 
1291  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1292  match->name, position,
1294  match->prev, (match_child? "next":"last"),
1295  (match_child? match_child : match->parent->last));
1296 
1297  if (match_child) {
1298  xmlAddPrevSibling(match_child, match);
1299 
1300  } else {
1301  CRM_ASSERT(match->parent->last != NULL);
1302  xmlAddNextSibling(match->parent->last, match);
1303  }
1304 
1305  } else {
1306  crm_trace("%s is already in position %d",
1307  match->name, position);
1308  }
1309 
1310  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1311  crm_err("Moved %s.%s to position %d instead of %d (%p)",
1312  match->name, ID(match),
1314  position, match->prev);
1315  rc = pcmk_rc_diff_failed;
1316  }
1317  }
1318  }
1319 
1320  g_list_free_full(change_objs, free);
1321  return rc;
1322 }
1323 
1324 int
1325 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1326 {
1327  int format = 1;
1328  int rc = pcmk_ok;
1329  xmlNode *old = NULL;
1330  const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1331 
1332  if (patchset == NULL) {
1333  return rc;
1334  }
1335 
1336  xml_log_patchset(LOG_TRACE, __func__, patchset);
1337 
1338  crm_element_value_int(patchset, "format", &format);
1339  if (check_version) {
1340  rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format));
1341  if (rc != pcmk_ok) {
1342  return rc;
1343  }
1344  }
1345 
1346  if (digest) {
1347  // Make it available for logging if result doesn't have expected digest
1348  old = copy_xml(xml);
1349  }
1350 
1351  if (rc == pcmk_ok) {
1352  switch (format) {
1353  case 1:
1354  rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1355  break;
1356  case 2:
1357  rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1358  break;
1359  default:
1360  crm_err("Unknown patch format: %d", format);
1361  rc = -EINVAL;
1362  }
1363  }
1364 
1365  if ((rc == pcmk_ok) && (digest != NULL)) {
1366  static struct qb_log_callsite *digest_cs = NULL;
1367 
1368  char *new_digest = NULL;
1370 
1371  if (digest_cs == NULL) {
1372  digest_cs = qb_log_callsite_get(__func__, __FILE__, "diff-digest",
1373  LOG_TRACE, __LINE__,
1375  }
1376 
1377  new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1378  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1379  crm_info("v%d digest mis-match: expected %s, calculated %s",
1380  format, digest, new_digest);
1381  rc = -pcmk_err_diff_failed;
1382 
1383  if ((digest_cs != NULL) && digest_cs->targets) {
1384  save_xml_to_file(old, "PatchDigest:input", NULL);
1385  save_xml_to_file(xml, "PatchDigest:result", NULL);
1386  save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1387 
1388  } else {
1389  crm_trace("%p %.6x", digest_cs,
1390  ((digest_cs != NULL)? digest_cs->targets : 0));
1391  }
1392 
1393  } else {
1394  crm_trace("v%d digest matched: expected %s, calculated %s",
1395  format, digest, new_digest);
1396  }
1397  free(new_digest);
1398  free(version);
1399  }
1400  free_xml(old);
1401  return rc;
1402 }
1403 
1404 void
1405 purge_diff_markers(xmlNode *a_node)
1406 {
1407  xmlNode *child = NULL;
1408 
1409  CRM_CHECK(a_node != NULL, return);
1410 
1412  for (child = pcmk__xml_first_child(a_node); child != NULL;
1413  child = pcmk__xml_next(child)) {
1414  purge_diff_markers(child);
1415  }
1416 }
1417 
1418 xmlNode *
1419 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1420 {
1421  xmlNode *tmp1 = NULL;
1422  xmlNode *diff = create_xml_node(NULL, "diff");
1423  xmlNode *removed = create_xml_node(diff, "diff-removed");
1424  xmlNode *added = create_xml_node(diff, "diff-added");
1425 
1427 
1428  tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
1429  if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1430  free_xml(tmp1);
1431  }
1432 
1433  tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
1434  if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1435  free_xml(tmp1);
1436  }
1437 
1438  if ((added->children == NULL) && (removed->children == NULL)) {
1439  free_xml(diff);
1440  diff = NULL;
1441  }
1442 
1443  return diff;
1444 }
1445 
1446 static xmlNode *
1447 subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
1448  gboolean *changed)
1449 {
1450  CRM_CHECK(left != NULL, return NULL);
1451  CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
1452 
1453  if ((right == NULL) || !pcmk__str_eq((const char *)left->content,
1454  (const char *)right->content,
1455  pcmk__str_casei)) {
1456  xmlNode *deleted = NULL;
1457 
1458  deleted = add_node_copy(parent, left);
1459  *changed = TRUE;
1460 
1461  return deleted;
1462  }
1463 
1464  return NULL;
1465 }
1466 
1467 xmlNode *
1468 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1469  gboolean full, gboolean *changed, const char *marker)
1470 {
1471  gboolean dummy = FALSE;
1472  xmlNode *diff = NULL;
1473  xmlNode *right_child = NULL;
1474  xmlNode *left_child = NULL;
1475  xmlAttrPtr xIter = NULL;
1476 
1477  const char *id = NULL;
1478  const char *name = NULL;
1479  const char *value = NULL;
1480  const char *right_val = NULL;
1481 
1482  if (changed == NULL) {
1483  changed = &dummy;
1484  }
1485 
1486  if (left == NULL) {
1487  return NULL;
1488  }
1489 
1490  if (left->type == XML_COMMENT_NODE) {
1491  return subtract_xml_comment(parent, left, right, changed);
1492  }
1493 
1494  id = ID(left);
1495  if (right == NULL) {
1496  xmlNode *deleted = NULL;
1497 
1498  crm_trace("Processing <%s id=%s> (complete copy)",
1499  crm_element_name(left), id);
1500  deleted = add_node_copy(parent, left);
1501  crm_xml_add(deleted, XML_DIFF_MARKER, marker);
1502 
1503  *changed = TRUE;
1504  return deleted;
1505  }
1506 
1507  name = crm_element_name(left);
1508  CRM_CHECK(name != NULL, return NULL);
1509  CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right),
1510  pcmk__str_casei),
1511  return NULL);
1512 
1513  // Check for XML_DIFF_MARKER in a child
1514  value = crm_element_value(right, XML_DIFF_MARKER);
1515  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
1516  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1517  *changed = TRUE;
1518  return NULL;
1519  }
1520 
1521  // @TODO Avoiding creating the full hierarchy would save work here
1522  diff = create_xml_node(parent, name);
1523 
1524  // Changes to child objects
1525  for (left_child = pcmk__xml_first_child(left); left_child != NULL;
1526  left_child = pcmk__xml_next(left_child)) {
1527  gboolean child_changed = FALSE;
1528 
1529  right_child = pcmk__xml_match(right, left_child, false);
1530  subtract_xml_object(diff, left_child, right_child, full, &child_changed,
1531  marker);
1532  if (child_changed) {
1533  *changed = TRUE;
1534  }
1535  }
1536 
1537  if (!*changed) {
1538  /* Nothing to do */
1539 
1540  } else if (full) {
1541  xmlAttrPtr pIter = NULL;
1542 
1543  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1544  pIter = pIter->next) {
1545  const char *p_name = (const char *)pIter->name;
1546  const char *p_value = pcmk__xml_attr_value(pIter);
1547 
1548  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1549  }
1550 
1551  // We have everything we need
1552  goto done;
1553  }
1554 
1555  // Changes to name/value pairs
1556  for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
1557  xIter = xIter->next) {
1558  const char *prop_name = (const char *) xIter->name;
1559  xmlAttrPtr right_attr = NULL;
1560  xml_private_t *p = NULL;
1561 
1562  if (strcmp(prop_name, XML_ATTR_ID) == 0) {
1563  // id already obtained when present ~ this case, so just reuse
1564  xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_ID, (pcmkXmlStr) id);
1565  continue;
1566  }
1567 
1568  if (pcmk__xa_filterable(prop_name)) {
1569  continue;
1570  }
1571 
1572  right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
1573  if (right_attr) {
1574  p = right_attr->_private;
1575  }
1576 
1577  right_val = crm_element_value(right, prop_name);
1578  if ((right_val == NULL) || (p && pcmk_is_set(p->flags, pcmk__xf_deleted))) {
1579  /* new */
1580  *changed = TRUE;
1581  if (full) {
1582  xmlAttrPtr pIter = NULL;
1583 
1584  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1585  pIter = pIter->next) {
1586  const char *p_name = (const char *) pIter->name;
1587  const char *p_value = pcmk__xml_attr_value(pIter);
1588 
1589  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1590  }
1591  break;
1592 
1593  } else {
1594  const char *left_value = crm_element_value(left, prop_name);
1595 
1596  xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
1597  crm_xml_add(diff, prop_name, left_value);
1598  }
1599 
1600  } else {
1601  /* Only now do we need the left value */
1602  const char *left_value = crm_element_value(left, prop_name);
1603 
1604  if (strcmp(left_value, right_val) == 0) {
1605  /* unchanged */
1606 
1607  } else {
1608  *changed = TRUE;
1609  if (full) {
1610  xmlAttrPtr pIter = NULL;
1611 
1612  crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
1613  crm_element_name(left), id);
1614  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1615  pIter = pIter->next) {
1616  const char *p_name = (const char *) pIter->name;
1617  const char *p_value = pcmk__xml_attr_value(pIter);
1618 
1619  xmlSetProp(diff, (pcmkXmlStr) p_name,
1620  (pcmkXmlStr) p_value);
1621  }
1622  break;
1623 
1624  } else {
1625  crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
1626  prop_name, left_value, right_val,
1627  crm_element_name(left), id);
1628  crm_xml_add(diff, prop_name, left_value);
1629  }
1630  }
1631  }
1632  }
1633 
1634  if (!*changed) {
1635  free_xml(diff);
1636  return NULL;
1637 
1638  } else if (!full && (id != NULL)) {
1639  crm_xml_add(diff, XML_ATTR_ID, id);
1640  }
1641  done:
1642  return diff;
1643 }
1644 
1645 // Deprecated functions kept only for backward API compatibility
1646 // LCOV_EXCL_START
1647 
1648 #include <crm/common/xml_compat.h>
1649 
1650 gboolean
1651 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1652 {
1653  gboolean result = TRUE;
1654  int root_nodes_seen = 0;
1655  static struct qb_log_callsite *digest_cs = NULL;
1656  const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
1657  const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
1658 
1659  xmlNode *child_diff = NULL;
1660  xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
1661  xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
1662 
1663  CRM_CHECK(new_xml != NULL, return FALSE);
1664  if (digest_cs == NULL) {
1665  digest_cs = qb_log_callsite_get(__func__, __FILE__, "diff-digest",
1666  LOG_TRACE, __LINE__, crm_trace_nonlog);
1667  }
1668 
1669  crm_trace("Subtraction Phase");
1670  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1671  child_diff = pcmk__xml_next(child_diff)) {
1672  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1673  if (root_nodes_seen == 0) {
1674  *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
1675  NULL, NULL);
1676  }
1677  root_nodes_seen++;
1678  }
1679 
1680  if (root_nodes_seen == 0) {
1681  *new_xml = copy_xml(old_xml);
1682 
1683  } else if (root_nodes_seen > 1) {
1684  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1685  root_nodes_seen);
1686  result = FALSE;
1687  }
1688 
1689  root_nodes_seen = 0;
1690  crm_trace("Addition Phase");
1691  if (result) {
1692  xmlNode *child_diff = NULL;
1693 
1694  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1695  child_diff = pcmk__xml_next(child_diff)) {
1696  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1697  if (root_nodes_seen == 0) {
1698  pcmk__xml_update(NULL, *new_xml, child_diff, true);
1699  }
1700  root_nodes_seen++;
1701  }
1702  }
1703 
1704  if (root_nodes_seen > 1) {
1705  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1706  root_nodes_seen);
1707  result = FALSE;
1708 
1709  } else if (result && (digest != NULL)) {
1710  char *new_digest = NULL;
1711 
1712  purge_diff_markers(*new_xml); // Purge now so diff is ok
1713  new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1714  version);
1715  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1716  crm_info("Digest mis-match: expected %s, calculated %s",
1717  digest, new_digest);
1718  result = FALSE;
1719 
1720  crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
1721  if ((digest_cs != NULL) && digest_cs->targets) {
1722  save_xml_to_file(old_xml, "diff:original", NULL);
1723  save_xml_to_file(diff, "diff:input", NULL);
1724  save_xml_to_file(*new_xml, "diff:new", NULL);
1725  }
1726 
1727  } else {
1728  crm_trace("Digest matched: expected %s, calculated %s",
1729  digest, new_digest);
1730  }
1731  free(new_digest);
1732 
1733  } else if (result) {
1734  purge_diff_markers(*new_xml); // Purge now so diff is ok
1735  }
1736 
1737  return result;
1738 }
1739 
1740 // LCOV_EXCL_STOP
1741 // End deprecated API
#define LOG_TRACE
Definition: logging.h:37
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
xmlNode * find_xml_node(xmlNode *cib, const char *node_path, gboolean must_find)
Definition: xml.c:445
#define XML_DIFF_RESULT
Definition: msg_xml.h:455
A dumping ground.
#define crm_notice(fmt, args...)
Definition: logging.h:360
#define XML_TAG_DIFF
Definition: msg_xml.h:448
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition: patchset.c:426
#define XML_ATTR_NUMUPDATES
Definition: msg_xml.h:128
int pcmk_rc2legacy(int rc)
Definition: results.c:415
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:801
const char * name
Definition: cib.c:24
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: xpath.c:269
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:2794
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:431
#define CRM_FEATURE_SET
Definition: crm.h:69
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:323
#define XML_NVPAIR_ATTR_NAME
Definition: msg_xml.h:383
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:210
#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:281
unsigned int crm_trace_nonlog
Definition: logging.c:46
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:565
#define LOG_NEVER
Definition: logging.h:47
Deprecated Pacemaker XML API.
#define XML_ATTR_GENERATION
Definition: msg_xml.h:126
void xml_log_patchset(uint8_t log_level, const char *function, xmlNode *patchset)
Definition: patchset.c:457
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
#define crm_warn(fmt, args...)
Definition: logging.h:359
#define pcmk_err_diff_failed
Definition: results.h:76
G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
Definition: xml.c:2484
#define XML_DIFF_OP
Definition: msg_xml.h:456
#define crm_debug(fmt, args...)
Definition: logging.h:363
#define XML_DIFF_ATTR
Definition: msg_xml.h:454
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:726
#define XML_DIFF_VERSION
Definition: msg_xml.h:449
#define XML_ATTR_ID
Definition: msg_xml.h:135
struct xml_change_obj_s xml_change_obj_t
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:529
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml.c:629
#define crm_trace(fmt, args...)
Definition: logging.h:364
#define crm_log_xml_explicit(xml, text)
Definition: logging.h:374
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:122
xmlNode * add_node_copy(xmlNode *new_parent, xmlNode *xml_node)
Definition: xml.c:674
Wrappers for and extensions to libxml2.
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:297
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:696
#define PCMK__NELEM(a)
Definition: internal.h:42
#define XML_DIFF_POSITION
Definition: msg_xml.h:458
void purge_diff_markers(xmlNode *a_node)
Definition: patchset.c:1405
void free_xml(xmlNode *child)
Definition: xml.c:824
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition: patchset.c:369
G_GNUC_INTERNAL xmlNode * pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact)
Definition: xml.c:369
const xmlChar * pcmkXmlStr
Definition: xml.h:52
G_GNUC_INTERNAL void pcmk__xe_log(int log_level, const char *file, const char *function, int line, const char *prefix, xmlNode *data, int depth, int options)
Definition: xml.c:1444
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:252
gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
Definition: logging.c:635
#define XML_DIFF_VSOURCE
Definition: msg_xml.h:450
const char * target
Definition: pcmk_fence.c:28
#define XML_TAG_CIB
Definition: msg_xml.h:115
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: patchset.c:1651
#define XML_DIFF_CHANGE
Definition: msg_xml.h:452
#define XML_DIFF_PATH
Definition: msg_xml.h:457
#define PCMK__BUFFER_SIZE
#define XML_DIFF_VTARGET
Definition: msg_xml.h:451
#define XML_DIFF_LIST
Definition: msg_xml.h:453
xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
Definition: patchset.c:1419
pcmk__action_result_t result
Definition: pcmk_fence.c:34
#define XML_DIFF_MARKER
Definition: msg_xml.h:114
const char * path
Definition: cib.c:26
#define crm_err(fmt, args...)
Definition: logging.h:358
#define CRM_ASSERT(expr)
Definition: results.h:42
#define ENOTUNIQ
Definition: portability.h:120
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:2037
pe_resource_t * dummy
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:187
gboolean can_prune_leaf(xmlNode *xml_node)
Definition: xml.c:2392
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:587
int compare_version(const char *version1, const char *version2)
Definition: utils.c:160
#define crm_log_xml_info(xml, text)
Definition: logging.h:370
#define XML_ATTR_GENERATION_ADMIN
Definition: msg_xml.h:127
#define XML_NVPAIR_ATTR_VALUE
Definition: msg_xml.h:384
#define XML_ATTR_CRM_VERSION
Definition: msg_xml.h:118
void save_xml_to_file(xmlNode *xml, const char *desc, const char *filename)
Definition: xml.c:2056
#define pcmk_ok
Definition: results.h:68
#define XML_CIB_TAG_CONFIGURATION
Definition: msg_xml.h:184
#define ID(x)
Definition: msg_xml.h:460
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: patchset.c:1468
const char * parent
Definition: cib.c:25
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:1325
G_GNUC_INTERNAL void pcmk__mark_xml_created(xmlNode *xml)
Definition: xml.c:155
G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff)
Definition: xml.c:2515
#define LOG_STDOUT
Definition: logging.h:42
#define crm_info(fmt, args...)
Definition: logging.h:361
G_GNUC_INTERNAL int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:314
uint32_t version
Definition: remote.c:147
#define XML_ATTR_DIGEST
Definition: msg_xml.h:119