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