pacemaker  2.1.8-3980678f03
Scalable High-Availability cluster resource manager
patchset.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2024 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/tree.h>
22 
23 #include <crm/crm.h>
24 #include <crm/common/xml.h>
25 #include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
26 #include "crmcommon_private.h"
27 
28 /* Add changes for specified XML to patchset.
29  * For patchset format, refer to diff schema.
30  */
31 static void
32 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
33 {
34  xmlNode *cIter = NULL;
35  xmlAttr *pIter = NULL;
36  xmlNode *change = NULL;
37  xml_node_private_t *nodepriv = xml->_private;
38  const char *value = NULL;
39 
40  if (nodepriv == NULL) {
41  /* Elements that shouldn't occur in a CIB don't have _private set. They
42  * should be stripped out, ignored, or have an error thrown by any code
43  * that processes their parent, so we ignore any changes to them.
44  */
45  return;
46  }
47 
48  // If this XML node is new, just report that
49  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
50  GString *xpath = pcmk__element_xpath(xml->parent);
51 
52  if (xpath != NULL) {
53  int position = pcmk__xml_position(xml, pcmk__xf_deleted);
54 
55  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
56 
58  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
59  crm_xml_add_int(change, PCMK_XE_POSITION, position);
60  pcmk__xml_copy(change, xml);
61  g_string_free(xpath, TRUE);
62  }
63 
64  return;
65  }
66 
67  // Check each of the XML node's attributes for changes
68  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
69  pIter = pIter->next) {
70  xmlNode *attr = NULL;
71 
72  nodepriv = pIter->_private;
73  if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
74  continue;
75  }
76 
77  if (change == NULL) {
78  GString *xpath = pcmk__element_xpath(xml);
79 
80  if (xpath != NULL) {
81  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
82 
84  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
85 
86  change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
87  g_string_free(xpath, TRUE);
88  }
89  }
90 
91  attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR);
92 
93  crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
94  if (nodepriv->flags & pcmk__xf_deleted) {
95  crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
96 
97  } else {
98  crm_xml_add(attr, PCMK_XA_OPERATION, "set");
99 
100  value = pcmk__xml_attr_value(pIter);
101  crm_xml_add(attr, PCMK_XA_VALUE, value);
102  }
103  }
104 
105  if (change) {
106  xmlNode *result = NULL;
107 
108  change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
109  result = pcmk__xe_create(change, (const char *)xml->name);
110 
111  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
112  pIter = pIter->next) {
113  nodepriv = pIter->_private;
114  if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
115  value = crm_element_value(xml, (const char *) pIter->name);
116  crm_xml_add(result, (const char *)pIter->name, value);
117  }
118  }
119  }
120 
121  // Now recursively do the same for each child node of this node
122  for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
123  cIter = pcmk__xml_next(cIter)) {
124  add_xml_changes_to_patchset(cIter, patchset);
125  }
126 
127  nodepriv = xml->_private;
128  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
129  GString *xpath = pcmk__element_xpath(xml);
130 
131  crm_trace("%s.%s moved to position %d",
132  xml->name, pcmk__xe_id(xml),
134 
135  if (xpath != NULL) {
136  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
137 
139  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
142  g_string_free(xpath, TRUE);
143  }
144  }
145 }
146 
147 static bool
148 is_config_change(xmlNode *xml)
149 {
150  GList *gIter = NULL;
151  xml_node_private_t *nodepriv = NULL;
152  xml_doc_private_t *docpriv;
153  xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
154  NULL);
155 
156  if (config) {
157  nodepriv = config->_private;
158  }
159  if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
160  return TRUE;
161  }
162 
163  if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
164  docpriv = xml->doc->_private;
165  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
166  pcmk__deleted_xml_t *deleted_obj = gIter->data;
167 
168  if (strstr(deleted_obj->path,
169  "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
170  return TRUE;
171  }
172  }
173  }
174  return FALSE;
175 }
176 
177 // @COMPAT Remove when v1 patchsets are removed
178 static void
179 xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
180  gboolean changed)
181 {
182  int lpc = 0;
183  xmlNode *cib = NULL;
184  xmlNode *diff_child = NULL;
185 
186  const char *tag = NULL;
187 
188  const char *vfields[] = {
192  };
193 
194  if (local_diff == NULL) {
195  crm_trace("Nothing to do");
196  return;
197  }
198 
199  tag = PCMK__XE_DIFF_REMOVED;
200  diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL);
201  if (diff_child == NULL) {
202  diff_child = pcmk__xe_create(local_diff, tag);
203  }
204 
205  tag = PCMK_XE_CIB;
206  cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL);
207  if (cib == NULL) {
208  cib = pcmk__xe_create(diff_child, tag);
209  }
210 
211  for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
212  const char *value = crm_element_value(last, vfields[lpc]);
213 
214  crm_xml_add(diff_child, vfields[lpc], value);
215  if (changed || lpc == 2) {
216  crm_xml_add(cib, vfields[lpc], value);
217  }
218  }
219 
220  tag = PCMK__XE_DIFF_ADDED;
221  diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL);
222  if (diff_child == NULL) {
223  diff_child = pcmk__xe_create(local_diff, tag);
224  }
225 
226  tag = PCMK_XE_CIB;
227  cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL);
228  if (cib == NULL) {
229  cib = pcmk__xe_create(diff_child, tag);
230  }
231 
232  for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
233  const char *value = crm_element_value(next, vfields[lpc]);
234 
235  crm_xml_add(diff_child, vfields[lpc], value);
236  }
237 
238  for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
239 
240  const char *p_value = pcmk__xml_attr_value(a);
241 
242  xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
243  }
244 
245  crm_log_xml_explicit(local_diff, "Repaired-diff");
246 }
247 
248 // @COMPAT Remove when v1 patchsets are removed
249 static xmlNode *
250 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
251  bool suppress)
252 {
253  xmlNode *patchset = pcmk__diff_v1_xml_object(source, target, suppress);
254 
255  if (patchset) {
257  xml_repair_v1_diff(source, target, patchset, config);
258  crm_xml_add(patchset, PCMK_XA_FORMAT, "1");
259  }
260  return patchset;
261 }
262 
263 static xmlNode *
264 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
265 {
266  int lpc = 0;
267  GList *gIter = NULL;
268  xml_doc_private_t *docpriv;
269 
270  xmlNode *v = NULL;
271  xmlNode *version = NULL;
272  xmlNode *patchset = NULL;
273  const char *vfields[] = {
277  };
278 
280  if (!xml_document_dirty(target)) {
281  return NULL;
282  }
283 
284  CRM_ASSERT(target->doc);
285  docpriv = target->doc->_private;
286 
287  patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
288  crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
289 
291 
293  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
294  const char *value = crm_element_value(source, vfields[lpc]);
295 
296  if (value == NULL) {
297  value = "1";
298  }
299  crm_xml_add(v, vfields[lpc], value);
300  }
301 
303  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
304  const char *value = crm_element_value(target, vfields[lpc]);
305 
306  if (value == NULL) {
307  value = "1";
308  }
309  crm_xml_add(v, vfields[lpc], value);
310  }
311 
312  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
313  pcmk__deleted_xml_t *deleted_obj = gIter->data;
314  xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
315 
317  crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
318  if (deleted_obj->position >= 0) {
319  crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
320  }
321  }
322 
323  add_xml_changes_to_patchset(target, patchset);
324  return patchset;
325 }
326 
327 xmlNode *
328 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
329  bool *config_changed, bool manage_version)
330 {
331  int counter = 0;
332  bool config = FALSE;
333  xmlNode *patch = NULL;
334  const char *version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET);
335 
337  if (!xml_document_dirty(target)) {
338  crm_trace("No change %d", format);
339  return NULL; /* No change */
340  }
341 
342  config = is_config_change(target);
343  if (config_changed) {
344  *config_changed = config;
345  }
346 
347  if (manage_version && config) {
348  crm_trace("Config changed %d", format);
350 
352  crm_xml_add_int(target, PCMK_XA_EPOCH, counter+1);
353 
354  } else if (manage_version) {
356  crm_trace("Status changed %d - %d %s", format, counter,
358  crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, (counter + 1));
359  }
360 
361  if (format == 0) {
362  if (compare_version("3.0.8", version) < 0) {
363  format = 2;
364  } else {
365  format = 1;
366  }
367  crm_trace("Using patch format %d for version: %s", format, version);
368  }
369 
370  switch (format) {
371  case 1:
372  // @COMPAT Remove when v1 patchsets are removed
373  patch = xml_create_patchset_v1(source, target, config, FALSE);
374  break;
375  case 2:
376  patch = xml_create_patchset_v2(source, target);
377  break;
378  default:
379  crm_err("Unknown patch format: %d", format);
380  return NULL;
381  }
382  return patch;
383 }
384 
385 void
386 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
387  bool with_digest)
388 {
389  int format = 1;
390  const char *version = NULL;
391  char *digest = NULL;
392 
393  if ((patch == NULL) || (source == NULL) || (target == NULL)) {
394  return;
395  }
396 
397  /* We should always call xml_accept_changes() before calculating a digest.
398  * Otherwise, with an on-tracking dirty target, we could get a wrong digest.
399  */
401 
402  crm_element_value_int(patch, PCMK_XA_FORMAT, &format);
403  if ((format > 1) && !with_digest) {
404  return;
405  }
406 
408  digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
409 
410  crm_xml_add(patch, PCMK__XA_DIGEST, digest);
411  free(digest);
412 
413  return;
414 }
415 
416 // @COMPAT Remove when v1 patchsets are removed
417 static xmlNode *
418 subtract_v1_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
419  gboolean *changed)
420 {
421  CRM_CHECK(left != NULL, return NULL);
422  CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
423 
424  if ((right == NULL) || !pcmk__str_eq((const char *)left->content,
425  (const char *)right->content,
426  pcmk__str_casei)) {
427  xmlNode *deleted = NULL;
428 
429  deleted = pcmk__xml_copy(parent, left);
430  *changed = TRUE;
431 
432  return deleted;
433  }
434 
435  return NULL;
436 }
437 
438 // @COMPAT Remove when v1 patchsets are removed
439 static xmlNode *
440 subtract_v1_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
441  bool full, gboolean *changed, const char *marker)
442 {
443  gboolean dummy = FALSE;
444  xmlNode *diff = NULL;
445  xmlNode *right_child = NULL;
446  xmlNode *left_child = NULL;
447  xmlAttrPtr xIter = NULL;
448 
449  const char *id = NULL;
450  const char *name = NULL;
451  const char *value = NULL;
452  const char *right_val = NULL;
453 
454  if (changed == NULL) {
455  changed = &dummy;
456  }
457 
458  if (left == NULL) {
459  return NULL;
460  }
461 
462  if (left->type == XML_COMMENT_NODE) {
463  return subtract_v1_xml_comment(parent, left, right, changed);
464  }
465 
466  id = pcmk__xe_id(left);
467  name = (const char *) left->name;
468  if (right == NULL) {
469  xmlNode *deleted = NULL;
470 
471  crm_trace("Processing <%s " PCMK_XA_ID "=%s> (complete copy)",
472  name, id);
473  deleted = pcmk__xml_copy(parent, left);
474  crm_xml_add(deleted, PCMK__XA_CRM_DIFF_MARKER, marker);
475 
476  *changed = TRUE;
477  return deleted;
478  }
479 
480  CRM_CHECK(name != NULL, return NULL);
481  CRM_CHECK(pcmk__xe_is(left, (const char *) right->name), return NULL);
482 
483  // Check for PCMK__XA_CRM_DIFF_MARKER in a child
485  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
486  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
487  *changed = TRUE;
488  return NULL;
489  }
490 
491  // @TODO Avoiding creating the full hierarchy would save work here
492  diff = pcmk__xe_create(parent, name);
493 
494  // Changes to child objects
495  for (left_child = pcmk__xml_first_child(left); left_child != NULL;
496  left_child = pcmk__xml_next(left_child)) {
497  gboolean child_changed = FALSE;
498 
499  right_child = pcmk__xml_match(right, left_child, false);
500  subtract_v1_xml_object(diff, left_child, right_child, full,
501  &child_changed, marker);
502  if (child_changed) {
503  *changed = TRUE;
504  }
505  }
506 
507  if (!*changed) {
508  /* Nothing to do */
509 
510  } else if (full) {
511  xmlAttrPtr pIter = NULL;
512 
513  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
514  pIter = pIter->next) {
515  const char *p_name = (const char *)pIter->name;
516  const char *p_value = pcmk__xml_attr_value(pIter);
517 
518  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
519  }
520 
521  // We have everything we need
522  goto done;
523  }
524 
525  // Changes to name/value pairs
526  for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
527  xIter = xIter->next) {
528  const char *prop_name = (const char *) xIter->name;
529  xmlAttrPtr right_attr = NULL;
530  xml_node_private_t *nodepriv = NULL;
531 
532  if (strcmp(prop_name, PCMK_XA_ID) == 0) {
533  // id already obtained when present ~ this case, so just reuse
534  xmlSetProp(diff, (pcmkXmlStr) PCMK_XA_ID, (pcmkXmlStr) id);
535  continue;
536  }
537 
538  if (pcmk__xa_filterable(prop_name)) {
539  continue;
540  }
541 
542  right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
543  if (right_attr) {
544  nodepriv = right_attr->_private;
545  }
546 
547  right_val = crm_element_value(right, prop_name);
548  if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) {
549  /* new */
550  *changed = TRUE;
551  if (full) {
552  xmlAttrPtr pIter = NULL;
553 
554  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
555  pIter = pIter->next) {
556  const char *p_name = (const char *) pIter->name;
557  const char *p_value = pcmk__xml_attr_value(pIter);
558 
559  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
560  }
561  break;
562 
563  } else {
564  const char *left_value = pcmk__xml_attr_value(xIter);
565 
566  xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
567  crm_xml_add(diff, prop_name, left_value);
568  }
569 
570  } else {
571  /* Only now do we need the left value */
572  const char *left_value = pcmk__xml_attr_value(xIter);
573 
574  if (strcmp(left_value, right_val) == 0) {
575  /* unchanged */
576 
577  } else {
578  *changed = TRUE;
579  if (full) {
580  xmlAttrPtr pIter = NULL;
581 
582  crm_trace("Changes detected to %s in "
583  "<%s " PCMK_XA_ID "=%s>", prop_name, name, id);
584  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
585  pIter = pIter->next) {
586  const char *p_name = (const char *) pIter->name;
587  const char *p_value = pcmk__xml_attr_value(pIter);
588 
589  xmlSetProp(diff, (pcmkXmlStr) p_name,
590  (pcmkXmlStr) p_value);
591  }
592  break;
593 
594  } else {
595  crm_trace("Changes detected to %s (%s -> %s) in "
596  "<%s " PCMK_XA_ID "=%s>",
597  prop_name, left_value, right_val, name, id);
598  crm_xml_add(diff, prop_name, left_value);
599  }
600  }
601  }
602  }
603 
604  if (!*changed) {
605  free_xml(diff);
606  return NULL;
607 
608  } else if (!full && (id != NULL)) {
609  crm_xml_add(diff, PCMK_XA_ID, id);
610  }
611  done:
612  return diff;
613 }
614 
615 /* @COMPAT Remove when v1 patchsets are removed.
616  *
617  * Return true if attribute name is not \c PCMK_XML_ID.
618  */
619 static bool
620 not_id(xmlAttrPtr attr, void *user_data)
621 {
622  return strcmp((const char *) attr->name, PCMK_XA_ID) != 0;
623 }
624 
625 /* @COMPAT Remove when v1 patchsets are removed.
626  *
627  * Apply the removals section of a v1 patchset to an XML node.
628  */
629 static void
630 process_v1_removals(xmlNode *target, xmlNode *patch)
631 {
632  xmlNode *patch_child = NULL;
633  xmlNode *cIter = NULL;
634 
635  char *id = NULL;
636  const char *value = NULL;
637 
638  if ((target == NULL) || (patch == NULL)) {
639  return;
640  }
641 
642  if (target->type == XML_COMMENT_NODE) {
643  gboolean dummy;
644 
645  subtract_v1_xml_comment(target->parent, target, patch, &dummy);
646  }
647 
648  CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return);
649  CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
651  return);
652 
653  // Check for PCMK__XA_CRM_DIFF_MARKER in a child
656  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
657  crm_trace("We are the root of the deletion: %s.id=%s",
658  target->name, id);
659  free_xml(target);
660  free(id);
661  return;
662  }
663 
664  // Removing then restoring id would change ordering of properties
665  pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
666 
667  // Changes to child objects
668  cIter = pcmk__xml_first_child(target);
669  while (cIter) {
670  xmlNode *target_child = cIter;
671 
672  cIter = pcmk__xml_next(cIter);
673  patch_child = pcmk__xml_match(patch, target_child, false);
674  process_v1_removals(target_child, patch_child);
675  }
676  free(id);
677 }
678 
679 /* @COMPAT Remove when v1 patchsets are removed.
680  *
681  * Apply the additions section of a v1 patchset to an XML node.
682  */
683 static void
684 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
685 {
686  xmlNode *patch_child = NULL;
687  xmlNode *target_child = NULL;
688  xmlAttrPtr xIter = NULL;
689 
690  const char *id = NULL;
691  const char *name = NULL;
692  const char *value = NULL;
693 
694  if (patch == NULL) {
695  return;
696  } else if ((parent == NULL) && (target == NULL)) {
697  return;
698  }
699 
700  // Check for PCMK__XA_CRM_DIFF_MARKER in a child
701  name = (const char *) patch->name;
703  if ((target == NULL) && (value != NULL)
704  && (strcmp(value, "added:top") == 0)) {
705  id = pcmk__xe_id(patch);
706  crm_trace("We are the root of the addition: %s.id=%s", name, id);
707  pcmk__xml_copy(parent, patch);
708  return;
709 
710  } else if (target == NULL) {
711  id = pcmk__xe_id(patch);
712  crm_err("Could not locate: %s.id=%s", name, id);
713  return;
714  }
715 
716  if (target->type == XML_COMMENT_NODE) {
717  pcmk__xc_update(parent, target, patch);
718  }
719 
720  CRM_CHECK(pcmk__xe_is(target, name), return);
721  CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
723  return);
724 
725  for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
726  xIter = xIter->next) {
727  const char *p_name = (const char *) xIter->name;
728  const char *p_value = pcmk__xml_attr_value(xIter);
729 
730  pcmk__xe_remove_attr(target, p_name); // Preserve patch order
731  crm_xml_add(target, p_name, p_value);
732  }
733 
734  // Changes to child objects
735  for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
736  patch_child = pcmk__xml_next(patch_child)) {
737 
738  target_child = pcmk__xml_match(target, patch_child, false);
739  process_v1_additions(target, target_child, patch_child);
740  }
741 }
742 
754 static bool
755 find_patch_xml_node(const xmlNode *patchset, int format, bool added,
756  xmlNode **patch_node)
757 {
758  xmlNode *cib_node;
759  const char *label;
760 
761  switch (format) {
762  case 1:
763  // @COMPAT Remove when v1 patchsets are removed
765  *patch_node = pcmk__xe_first_child(patchset, label, NULL, NULL);
766  cib_node = pcmk__xe_first_child(*patch_node, PCMK_XE_CIB, NULL,
767  NULL);
768  if (cib_node != NULL) {
769  *patch_node = cib_node;
770  }
771  break;
772  case 2:
773  label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE;
774  *patch_node = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL,
775  NULL);
776  *patch_node = pcmk__xe_first_child(*patch_node, label, NULL, NULL);
777  break;
778  default:
779  crm_warn("Unknown patch format: %d", format);
780  *patch_node = NULL;
781  return FALSE;
782  }
783  return TRUE;
784 }
785 
786 // Get CIB versions used for additions and deletions in a patchset
787 bool
788 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
789 {
790  int lpc = 0;
791  int format = 1;
792  xmlNode *tmp = NULL;
793 
794  const char *vfields[] = {
798  };
799 
800 
801  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
802 
803  /* Process removals */
804  if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
805  return -EINVAL;
806  }
807  if (tmp != NULL) {
808  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
809  crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
810  crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
811  }
812  }
813 
814  /* Process additions */
815  if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
816  return -EINVAL;
817  }
818  if (tmp != NULL) {
819  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
820  crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
821  crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
822  }
823  }
824  return pcmk_ok;
825 }
826 
836 static int
837 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
838 {
839  int lpc = 0;
840  bool changed = FALSE;
841 
842  int this[] = { 0, 0, 0 };
843  int add[] = { 0, 0, 0 };
844  int del[] = { 0, 0, 0 };
845 
846  const char *vfields[] = {
850  };
851 
852  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
853  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
854  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
855  if (this[lpc] < 0) {
856  this[lpc] = 0;
857  }
858  }
859 
860  /* Set some defaults in case nothing is present */
861  add[0] = this[0];
862  add[1] = this[1];
863  add[2] = this[2] + 1;
864  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
865  del[lpc] = this[lpc];
866  }
867 
868  xml_patch_versions(patchset, add, del);
869 
870  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
871  if (this[lpc] < del[lpc]) {
872  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
873  vfields[lpc], this[0], this[1], this[2],
874  del[0], del[1], del[2], add[0], add[1], add[2]);
875  return pcmk_rc_diff_resync;
876 
877  } else if (this[lpc] > del[lpc]) {
878  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
879  vfields[lpc], this[0], this[1], this[2],
880  del[0], del[1], del[2], add[0], add[1], add[2], patchset);
881  crm_log_xml_info(patchset, "OldPatch");
882  return pcmk_rc_old_data;
883  }
884  }
885 
886  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
887  if (add[lpc] > del[lpc]) {
888  changed = TRUE;
889  }
890  }
891 
892  if (!changed) {
893  crm_notice("Versions did not change in patch %d.%d.%d",
894  add[0], add[1], add[2]);
895  return pcmk_rc_old_data;
896  }
897 
898  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
899  add[0], add[1], add[2], this[0], this[1], this[2]);
900  return pcmk_rc_ok;
901 }
902 
903 // @COMPAT Remove when v1 patchsets are removed
904 static void
905 purge_v1_diff_markers(xmlNode *node)
906 {
907  xmlNode *child = NULL;
908 
909  CRM_CHECK(node != NULL, return);
910 
912  for (child = pcmk__xml_first_child(node); child != NULL;
913  child = pcmk__xml_next(child)) {
914  purge_v1_diff_markers(child);
915  }
916 }
917 
918 // @COMPAT Remove when v1 patchsets are removed
928 static int
929 apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
930 {
931  int rc = pcmk_rc_ok;
932  int root_nodes_seen = 0;
933 
934  xmlNode *child_diff = NULL;
935  xmlNode *added = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL,
936  NULL);
937  xmlNode *removed = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED,
938  NULL, NULL);
939  xmlNode *old = pcmk__xml_copy(NULL, xml);
940 
941  crm_trace("Subtraction Phase");
942  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
943  child_diff = pcmk__xml_next(child_diff)) {
944  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
945  if (root_nodes_seen == 0) {
946  process_v1_removals(xml, child_diff);
947  }
948  root_nodes_seen++;
949  }
950 
951  if (root_nodes_seen > 1) {
952  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
953  root_nodes_seen);
954  rc = ENOTUNIQ;
955  }
956 
957  root_nodes_seen = 0;
958  crm_trace("Addition Phase");
959  if (rc == pcmk_rc_ok) {
960  xmlNode *child_diff = NULL;
961 
962  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
963  child_diff = pcmk__xml_next(child_diff)) {
964  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
965  if (root_nodes_seen == 0) {
966  process_v1_additions(NULL, xml, child_diff);
967  }
968  root_nodes_seen++;
969  }
970  }
971 
972  if (root_nodes_seen > 1) {
973  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
974  root_nodes_seen);
975  rc = ENOTUNIQ;
976  }
977 
978  purge_v1_diff_markers(xml); // Purge prior to checking digest
979 
980  free_xml(old);
981  return rc;
982 }
983 
984 // Return first child matching element name and optionally id or position
985 static xmlNode *
986 first_matching_xml_child(const xmlNode *parent, const char *name,
987  const char *id, int position)
988 {
989  xmlNode *cIter = NULL;
990 
991  for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
992  cIter = pcmk__xml_next(cIter)) {
993  if (strcmp((const char *) cIter->name, name) != 0) {
994  continue;
995  } else if (id) {
996  const char *cid = pcmk__xe_id(cIter);
997 
998  if ((cid == NULL) || (strcmp(cid, id) != 0)) {
999  continue;
1000  }
1001  }
1002 
1003  // "position" makes sense only for XML comments for now
1004  if ((cIter->type == XML_COMMENT_NODE)
1005  && (position >= 0)
1006  && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
1007  continue;
1008  }
1009 
1010  return cIter;
1011  }
1012  return NULL;
1013 }
1014 
1028 static xmlNode *
1029 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
1030 {
1031  xmlNode *target = (xmlNode *) top->doc;
1032  const char *current = key;
1033  char *section;
1034  char *remainder;
1035  char *id;
1036  char *tag;
1037  char *path = NULL;
1038  int rc;
1039  size_t key_len;
1040 
1041  CRM_CHECK(key != NULL, return NULL);
1042  key_len = strlen(key);
1043 
1044  /* These are scanned from key after a slash, so they can't be bigger
1045  * than key_len - 1 characters plus a null terminator.
1046  */
1047 
1048  remainder = pcmk__assert_alloc(key_len, sizeof(char));
1049  section = pcmk__assert_alloc(key_len, sizeof(char));
1050  id = pcmk__assert_alloc(key_len, sizeof(char));
1051  tag = pcmk__assert_alloc(key_len, sizeof(char));
1052 
1053  do {
1054  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1055  rc = sscanf(current, "/%[^/]%s", section, remainder);
1056  if (rc > 0) {
1057  // Separate FIRST_COMPONENT into TAG[@id='ID']
1058  int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
1059  int current_position = -1;
1060 
1061  /* The target position is for the final component tag, so only use
1062  * it if there is nothing left to search after this component.
1063  */
1064  if ((rc == 1) && (target_position >= 0)) {
1065  current_position = target_position;
1066  }
1067 
1068  switch (f) {
1069  case 1:
1070  // @COMPAT Remove when v1 patchsets are removed
1071  target = first_matching_xml_child(target, tag, NULL,
1072  current_position);
1073  break;
1074  case 2:
1075  target = first_matching_xml_child(target, tag, id,
1076  current_position);
1077  break;
1078  default:
1079  // This should not be possible
1080  target = NULL;
1081  break;
1082  }
1083  current = remainder;
1084  }
1085 
1086  // Continue if something remains to search, and we've matched so far
1087  } while ((rc == 2) && target);
1088 
1089  if (target) {
1090  crm_trace("Found %s for %s",
1091  (path = (char *) xmlGetNodePath(target)), key);
1092  free(path);
1093  } else {
1094  crm_debug("No match for %s", key);
1095  }
1096 
1097  free(remainder);
1098  free(section);
1099  free(tag);
1100  free(id);
1101  return target;
1102 }
1103 
1104 typedef struct xml_change_obj_s {
1105  const xmlNode *change;
1106  xmlNode *match;
1108 
1109 static gint
1110 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
1111 {
1112  const xml_change_obj_t *change_obj_a = a;
1113  const xml_change_obj_t *change_obj_b = b;
1114  int position_a = -1;
1115  int position_b = -1;
1116 
1117  crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
1118  crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
1119 
1120  if (position_a < position_b) {
1121  return -1;
1122 
1123  } else if (position_a > position_b) {
1124  return 1;
1125  }
1126 
1127  return 0;
1128 }
1129 
1139 static int
1140 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
1141 {
1142  int rc = pcmk_rc_ok;
1143  const xmlNode *change = NULL;
1144  GList *change_objs = NULL;
1145  GList *gIter = NULL;
1146 
1147  for (change = pcmk__xml_first_child(patchset); change != NULL;
1148  change = pcmk__xml_next(change)) {
1149  xmlNode *match = NULL;
1150  const char *op = crm_element_value(change, PCMK_XA_OPERATION);
1151  const char *xpath = crm_element_value(change, PCMK_XA_PATH);
1152  int position = -1;
1153 
1154  if (op == NULL) {
1155  continue;
1156  }
1157 
1158  crm_trace("Processing %s %s", change->name, op);
1159 
1160  /* PCMK_VALUE_DELETE changes for XML comments are generated with
1161  * PCMK_XE_POSITION
1162  */
1163  if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1164  crm_element_value_int(change, PCMK_XE_POSITION, &position);
1165  }
1166  match = search_v2_xpath(xml, xpath, position);
1167  crm_trace("Performing %s on %s with %p", op, xpath, match);
1168 
1169  if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
1170  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1171  continue;
1172 
1173  } else if (match == NULL) {
1174  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1175  rc = pcmk_rc_diff_failed;
1176  continue;
1177 
1178  } else if (pcmk__str_any_of(op,
1180  // Delay the adding of a PCMK_VALUE_CREATE object
1181  xml_change_obj_t *change_obj =
1183 
1184  change_obj->change = change;
1185  change_obj->match = match;
1186 
1187  change_objs = g_list_append(change_objs, change_obj);
1188 
1189  if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1190  // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
1191  if ((match->parent != NULL) && (match->parent->last != NULL)) {
1192  xmlAddNextSibling(match->parent->last, match);
1193  }
1194  }
1195 
1196  } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1197  free_xml(match);
1198 
1199  } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
1200  const xmlNode *child = pcmk__xe_first_child(change,
1202  NULL, NULL);
1203  const xmlNode *attrs = pcmk__xml_first_child(child);
1204 
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 = pcmk__xml_attr_value(pIter);
1215 
1216  crm_xml_add(match, name, value);
1217  }
1218 
1219  } else {
1220  crm_err("Unknown operation: %s", op);
1221  rc = pcmk_rc_diff_failed;
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, PCMK_XA_OPERATION);
1237  xpath = crm_element_value(change, PCMK_XA_PATH);
1238 
1239  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1240 
1241  if (strcmp(op, PCMK_VALUE_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, PCMK_XE_POSITION, &position);
1248 
1249  while ((match_child != NULL)
1250  && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1251  match_child = match_child->next;
1252  }
1253 
1254  child = xmlDocCopyNode(change->children, match->doc, 1);
1255  if (child == NULL) {
1256  return ENOMEM;
1257  }
1258 
1259  if (match_child) {
1260  crm_trace("Adding %s at position %d", child->name, position);
1261  xmlAddPrevSibling(match_child, child);
1262 
1263  } else if (match->last) {
1264  crm_trace("Adding %s at position %d (end)",
1265  child->name, position);
1266  xmlAddNextSibling(match->last, child);
1267 
1268  } else {
1269  crm_trace("Adding %s at position %d (first)",
1270  child->name, position);
1271  CRM_LOG_ASSERT(position == 0);
1272  xmlAddChild(match, child);
1273  }
1274  pcmk__xml_mark_created(child);
1275 
1276  } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1277  int position = 0;
1278 
1279  crm_element_value_int(change, PCMK_XE_POSITION, &position);
1280  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1281  xmlNode *match_child = NULL;
1282  int p = position;
1283 
1284  if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1285  p++; // Skip ourselves
1286  }
1287 
1288  CRM_ASSERT(match->parent != NULL);
1289  match_child = match->parent->children;
1290 
1291  while ((match_child != NULL)
1292  && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1293  match_child = match_child->next;
1294  }
1295 
1296  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1297  match->name, position,
1299  match->prev, (match_child? "next":"last"),
1300  (match_child? match_child : match->parent->last));
1301 
1302  if (match_child) {
1303  xmlAddPrevSibling(match_child, match);
1304 
1305  } else {
1306  CRM_ASSERT(match->parent->last != NULL);
1307  xmlAddNextSibling(match->parent->last, match);
1308  }
1309 
1310  } else {
1311  crm_trace("%s is already in position %d",
1312  match->name, position);
1313  }
1314 
1315  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1316  crm_err("Moved %s.%s to position %d instead of %d (%p)",
1317  match->name, pcmk__xe_id(match),
1319  position, match->prev);
1320  rc = pcmk_rc_diff_failed;
1321  }
1322  }
1323  }
1324 
1325  g_list_free_full(change_objs, free);
1326  return rc;
1327 }
1328 
1329 int
1330 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1331 {
1332  int format = 1;
1333  int rc = pcmk_ok;
1334  xmlNode *old = NULL;
1335  const char *digest = NULL;
1336 
1337  if (patchset == NULL) {
1338  return rc;
1339  }
1340 
1341  pcmk__log_xml_patchset(LOG_TRACE, patchset);
1342 
1343  if (check_version) {
1344  rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
1345  if (rc != pcmk_ok) {
1346  return rc;
1347  }
1348  }
1349 
1350  digest = crm_element_value(patchset, PCMK__XA_DIGEST);
1351  if (digest != NULL) {
1352  /* Make original XML available for logging in case result doesn't have
1353  * expected digest
1354  */
1355  pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
1356  }
1357 
1358  if (rc == pcmk_ok) {
1359  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
1360  switch (format) {
1361  case 1:
1362  // @COMPAT Remove when v1 patchsets are removed
1363  rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1364  break;
1365  case 2:
1366  rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1367  break;
1368  default:
1369  crm_err("Unknown patch format: %d", format);
1370  rc = -EINVAL;
1371  }
1372  }
1373 
1374  if ((rc == pcmk_ok) && (digest != NULL)) {
1375  char *new_digest = NULL;
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);
1382  rc = -pcmk_err_diff_failed;
1384  {
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  {}
1390  );
1391 
1392  } else {
1393  crm_trace("v%d digest matched: expected %s, calculated %s",
1394  format, digest, new_digest);
1395  }
1396  free(new_digest);
1397  free(version);
1398  }
1399  free_xml(old);
1400  return rc;
1401 }
1402 
1403 // @COMPAT Remove when v1 patchsets are removed
1404 static bool
1405 can_prune_leaf_v1(xmlNode *node)
1406 {
1407  xmlNode *cIter = NULL;
1408  bool can_prune = true;
1409 
1410  CRM_CHECK(node != NULL, return false);
1411 
1412  /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed for
1413  * rolling upgrades)
1414  */
1415  if (pcmk__strcase_any_of((const char *) node->name,
1418  NULL)) {
1419  return false;
1420  }
1421 
1422  for (xmlAttrPtr a = pcmk__xe_first_attr(node); a != NULL; a = a->next) {
1423  const char *p_name = (const char *) a->name;
1424 
1425  if (strcmp(p_name, PCMK_XA_ID) == 0) {
1426  continue;
1427  }
1428  can_prune = false;
1429  }
1430 
1431  cIter = pcmk__xml_first_child(node);
1432  while (cIter) {
1433  xmlNode *child = cIter;
1434 
1435  cIter = pcmk__xml_next(cIter);
1436  if (can_prune_leaf_v1(child)) {
1437  free_xml(child);
1438  } else {
1439  can_prune = false;
1440  }
1441  }
1442  return can_prune;
1443 }
1444 
1445 // @COMPAT Remove when v1 patchsets are removed
1446 xmlNode *
1447 pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
1448 {
1449  xmlNode *tmp1 = NULL;
1450  xmlNode *diff = pcmk__xe_create(NULL, PCMK_XE_DIFF);
1451  xmlNode *removed = pcmk__xe_create(diff, PCMK__XE_DIFF_REMOVED);
1452  xmlNode *added = pcmk__xe_create(diff, PCMK__XE_DIFF_ADDED);
1453 
1455 
1456  tmp1 = subtract_v1_xml_object(removed, old, new, false, NULL,
1457  "removed:top");
1458  if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1459  free_xml(tmp1);
1460  }
1461 
1462  tmp1 = subtract_v1_xml_object(added, new, old, true, NULL, "added:top");
1463  if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1464  free_xml(tmp1);
1465  }
1466 
1467  if ((added->children == NULL) && (removed->children == NULL)) {
1468  free_xml(diff);
1469  diff = NULL;
1470  }
1471 
1472  return diff;
1473 }
1474 
1475 // Deprecated functions kept only for backward API compatibility
1476 // LCOV_EXCL_START
1477 
1478 #include <crm/common/xml_compat.h>
1479 
1480 gboolean
1481 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1482 {
1483  gboolean result = TRUE;
1484  int root_nodes_seen = 0;
1485  const char *digest = crm_element_value(diff, PCMK__XA_DIGEST);
1486  const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET);
1487 
1488  xmlNode *child_diff = NULL;
1489  xmlNode *added = pcmk__xe_first_child(diff, PCMK__XE_DIFF_ADDED, NULL,
1490  NULL);
1491  xmlNode *removed = pcmk__xe_first_child(diff, PCMK__XE_DIFF_REMOVED, NULL,
1492  NULL);
1493 
1494  CRM_CHECK(new_xml != NULL, return FALSE);
1495 
1496  crm_trace("Subtraction Phase");
1497  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1498  child_diff = pcmk__xml_next(child_diff)) {
1499  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1500  if (root_nodes_seen == 0) {
1501  *new_xml = subtract_v1_xml_object(NULL, old_xml, child_diff, false,
1502  NULL, NULL);
1503  }
1504  root_nodes_seen++;
1505  }
1506 
1507  if (root_nodes_seen == 0) {
1508  *new_xml = pcmk__xml_copy(NULL, old_xml);
1509 
1510  } else if (root_nodes_seen > 1) {
1511  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1512  root_nodes_seen);
1513  result = FALSE;
1514  }
1515 
1516  root_nodes_seen = 0;
1517  crm_trace("Addition Phase");
1518  if (result) {
1519  xmlNode *child_diff = NULL;
1520 
1521  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1522  child_diff = pcmk__xml_next(child_diff)) {
1523  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1524  if (root_nodes_seen == 0) {
1525  pcmk__xml_update(NULL, *new_xml, child_diff, pcmk__xaf_none,
1526  true);
1527  }
1528  root_nodes_seen++;
1529  }
1530  }
1531 
1532  if (root_nodes_seen > 1) {
1533  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1534  root_nodes_seen);
1535  result = FALSE;
1536 
1537  } else if (result && (digest != NULL)) {
1538  char *new_digest = NULL;
1539 
1540  purge_v1_diff_markers(*new_xml); // Purge now so diff is ok
1541  new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1542  version);
1543  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1544  crm_info("Digest mis-match: expected %s, calculated %s",
1545  digest, new_digest);
1546  result = FALSE;
1547 
1549  {
1550  save_xml_to_file(old_xml, "diff:original", NULL);
1551  save_xml_to_file(diff, "diff:input", NULL);
1552  save_xml_to_file(*new_xml, "diff:new", NULL);
1553  },
1554  {}
1555  );
1556 
1557  } else {
1558  crm_trace("Digest matched: expected %s, calculated %s",
1559  digest, new_digest);
1560  }
1561  free(new_digest);
1562 
1563  } else if (result) {
1564  purge_v1_diff_markers(*new_xml); // Purge now so diff is ok
1565  }
1566 
1567  return result;
1568 }
1569 
1570 void
1571 purge_diff_markers(xmlNode *a_node)
1572 {
1573  purge_v1_diff_markers(a_node);
1574 }
1575 
1576 xmlNode *
1577 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1578 {
1579  return pcmk__diff_v1_xml_object(old, new, suppress);
1580 }
1581 
1582 xmlNode *
1583 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1584  gboolean full, gboolean *changed, const char *marker)
1585 {
1586  return subtract_v1_xml_object(parent, left, right, full, changed, marker);
1587 }
1588 
1589 gboolean
1590 can_prune_leaf(xmlNode *xml_node)
1591 {
1592  return can_prune_leaf_v1(xml_node);
1593 }
1594 
1595 // LCOV_EXCL_STOP
1596 // End deprecated API
#define LOG_TRACE
Definition: logging.h:38
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
#define PCMK__XA_DIGEST
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:883
#define PCMK__XE_DIFF_REMOVED
A dumping ground.
#define crm_notice(fmt, args...)
Definition: logging.h:397
#define PCMK_XA_NAME
Definition: xml_names.h:325
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition: patchset.c:386
#define pcmk__if_tracing(if_action, else_action)
int pcmk_rc2legacy(int rc)
Definition: results.c:546
pcmk_resource_t * dummy
#define PCMK_XA_PATH
Definition: xml_names.h:350
#define PCMK_XE_CIB
Definition: xml_names.h:79
const char * name
Definition: cib.c:26
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition: xml_io.c:734
bool pcmk__strcase_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1026
#define PCMK_XE_CHANGE_ATTR
Definition: xml_names.h:75
#define PCMK_VALUE_CREATE
Definition: options.h:139
#define PCMK_XE_RESOURCE_REF
Definition: xml_names.h:173
G_GNUC_INTERNAL int pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:342
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:348
#define CRM_FEATURE_SET
Definition: crm.h:72
#define PCMK_XA_NUM_UPDATES
Definition: xml_names.h:336
#define PCMK_XE_CHANGE_LIST
Definition: xml_names.h:76
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:301
#define PCMK_XA_FORMAT
Definition: xml_names.h:286
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:228
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:482
#define PCMK_XA_OPERATION
Definition: xml_names.h:344
Deprecated Pacemaker XML API.
#define crm_warn(fmt, args...)
Definition: logging.h:394
#define pcmk_err_diff_failed
Definition: results.h:80
G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
Definition: xml.c:1622
#define PCMK_XE_VERSION
Definition: xml_names.h:215
#define PCMK_VALUE_MOVE
Definition: options.h:175
#define crm_debug(fmt, args...)
Definition: logging.h:402
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:674
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:446
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml.c:440
#define PCMK__XE_DIFF_ADDED
#define PCMK_VALUE_MODIFY
Definition: options.h:174
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml.c:692
#define PCMK_XE_CHANGE
Definition: xml_names.h:74
G_GNUC_INTERNAL void pcmk__xml_mark_created(xmlNode *xml)
Definition: xml.c:160
#define PCMK_XE_CHANGE_RESULT
Definition: xml_names.h:77
#define crm_trace(fmt, args...)
Definition: logging.h:404
#define PCMK_VALUE_DELETE
Definition: options.h:144
#define crm_log_xml_explicit(xml, text)
Definition: logging.h:414
#define PCMK__XE_ROLE_REF
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:98
#define PCMK_XE_TARGET
Definition: xml_names.h:205
#define PCMK_XA_EPOCH
Definition: xml_names.h:263
xmlNode * pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
Definition: patchset.c:1447
Wrappers for and extensions to libxml2.
void pcmk__xe_remove_attr(xmlNode *element, const char *name)
Definition: xml.c:652
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:325
#define PCMK__NELEM(a)
Definition: internal.h:48
Flag has no effect.
Definition: xml_internal.h:438
#define PCMK_XA_ID
Definition: xml_names.h:296
void purge_diff_markers(xmlNode *a_node)
Definition: patchset.c:1571
#define PCMK_XA_VALUE
Definition: xml_names.h:437
void free_xml(xmlNode *child)
Definition: xml.c:867
#define PCMK_XE_CONFIGURATION
Definition: xml_names.h:87
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1050
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition: patchset.c:328
G_GNUC_INTERNAL xmlNode * pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
Definition: xml.c:385
#define PCMK_XE_SOURCE
Definition: xml_names.h:196
const xmlChar * pcmkXmlStr
Definition: xml.h:41
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:232
#define PCMK_XA_CRM_FEATURE_SET
Definition: xml_names.h:249
const char * target
Definition: pcmk_fence.c:29
G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, uint32_t flags, bool as_diff)
Definition: xml.c:1675
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: patchset.c:1481
xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
Definition: patchset.c:1577
pcmk__action_result_t result
Definition: pcmk_fence.c:35
const char * path
Definition: cib.c:28
#define crm_err(fmt, args...)
Definition: logging.h:391
#define CRM_ASSERT(expr)
Definition: results.h:42
#define ENOTUNIQ
Definition: portability.h:81
#define PCMK_XE_DIFF
Definition: xml_names.h:99
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:165
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:627
#define PCMK_XE_OBJ_REF
Definition: xml_names.h:142
int compare_version(const char *version1, const char *version2)
Definition: utils.c:188
#define crm_log_xml_info(xml, text)
Definition: logging.h:410
#define PCMK__XA_CRM_DIFF_MARKER
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:788
#define pcmk_ok
Definition: results.h:69
GString * pcmk__element_xpath(const xmlNode *xml)
Definition: xpath.c:256
#define pcmk__log_xml_patchset(level, patchset)
#define PCMK_XE_ROLE
Definition: xml_names.h:179
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: patchset.c:1583
const char * parent
Definition: cib.c:27
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:1330
#define PCMK_XA_ADMIN_EPOCH
Definition: xml_names.h:227
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml.c:720
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:297
#define PCMK_XE_POSITION
Definition: xml_names.h:159
#define crm_info(fmt, args...)
Definition: logging.h:399
uint32_t version
Definition: remote.c:213
gboolean can_prune_leaf(xmlNode *xml_node)
Definition: patchset.c:1590