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