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