pacemaker  2.1.9-49aab99839
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  pcmk__xe_set_attr_force(cib, (const char *) a->name, 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 
279  pcmk__assert(target != NULL);
280  if (!xml_document_dirty(target)) {
281  return NULL;
282  }
283 
284  pcmk__assert(target->doc != NULL);
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  pcmk__xe_set_attr_force(diff, p_name, 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
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  pcmk__xe_set_attr_force(diff, p_name, p_value);
560  }
561  break;
562 
563  } else {
564  const char *left_value = pcmk__xml_attr_value(xIter);
565 
566  pcmk__xe_set_attr_force(diff, prop_name, 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  pcmk__xe_set_attr_force(diff, p_name, p_value);
590  }
591  break;
592 
593  } else {
594  crm_trace("Changes detected to %s (%s -> %s) in "
595  "<%s " PCMK_XA_ID "=%s>",
596  prop_name, left_value, right_val, name, id);
597  crm_xml_add(diff, prop_name, left_value);
598  }
599  }
600  }
601  }
602 
603  if (!*changed) {
604  free_xml(diff);
605  return NULL;
606 
607  } else if (!full && (id != NULL)) {
608  crm_xml_add(diff, PCMK_XA_ID, id);
609  }
610  done:
611  return diff;
612 }
613 
614 /* @COMPAT Remove when v1 patchsets are removed.
615  *
616  * Return true if attribute name is not \c PCMK_XML_ID.
617  */
618 static bool
619 not_id(xmlAttrPtr attr, void *user_data)
620 {
621  return strcmp((const char *) attr->name, PCMK_XA_ID) != 0;
622 }
623 
624 /* @COMPAT Remove when v1 patchsets are removed.
625  *
626  * Apply the removals section of a v1 patchset to an XML node.
627  */
628 static void
629 process_v1_removals(xmlNode *target, xmlNode *patch)
630 {
631  xmlNode *patch_child = NULL;
632  xmlNode *cIter = NULL;
633 
634  char *id = NULL;
635  const char *value = NULL;
636 
637  if ((target == NULL) || (patch == NULL)) {
638  return;
639  }
640 
641  if (target->type == XML_COMMENT_NODE) {
642  gboolean dummy;
643 
644  subtract_v1_xml_comment(target->parent, target, patch, &dummy);
645  }
646 
647  CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return);
648  CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
650  return);
651 
652  // Check for PCMK__XA_CRM_DIFF_MARKER in a child
655  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
656  crm_trace("We are the root of the deletion: %s.id=%s",
657  target->name, id);
658  free_xml(target);
659  free(id);
660  return;
661  }
662 
663  // Removing then restoring id would change ordering of properties
664  pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
665 
666  // Changes to child objects
667  cIter = pcmk__xml_first_child(target);
668  while (cIter) {
669  xmlNode *target_child = cIter;
670 
671  cIter = pcmk__xml_next(cIter);
672  patch_child = pcmk__xml_match(patch, target_child, false);
673  process_v1_removals(target_child, patch_child);
674  }
675  free(id);
676 }
677 
678 /* @COMPAT Remove when v1 patchsets are removed.
679  *
680  * Apply the additions section of a v1 patchset to an XML node.
681  */
682 static void
683 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
684 {
685  xmlNode *patch_child = NULL;
686  xmlNode *target_child = NULL;
687  xmlAttrPtr xIter = NULL;
688 
689  const char *id = NULL;
690  const char *name = NULL;
691  const char *value = NULL;
692 
693  if (patch == NULL) {
694  return;
695  } else if ((parent == NULL) && (target == NULL)) {
696  return;
697  }
698 
699  // Check for PCMK__XA_CRM_DIFF_MARKER in a child
700  name = (const char *) patch->name;
702  if ((target == NULL) && (value != NULL)
703  && (strcmp(value, "added:top") == 0)) {
704  id = pcmk__xe_id(patch);
705  crm_trace("We are the root of the addition: %s.id=%s", name, id);
706  pcmk__xml_copy(parent, patch);
707  return;
708 
709  } else if (target == NULL) {
710  id = pcmk__xe_id(patch);
711  crm_err("Could not locate: %s.id=%s", name, id);
712  return;
713  }
714 
715  if (target->type == XML_COMMENT_NODE) {
716  pcmk__xc_update(parent, target, patch);
717  }
718 
719  CRM_CHECK(pcmk__xe_is(target, name), return);
720  CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
722  return);
723 
724  for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
725  xIter = xIter->next) {
726  const char *p_name = (const char *) xIter->name;
727  const char *p_value = pcmk__xml_attr_value(xIter);
728 
729  pcmk__xe_remove_attr(target, p_name); // Preserve patch order
730  crm_xml_add(target, p_name, p_value);
731  }
732 
733  // Changes to child objects
734  for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
735  patch_child = pcmk__xml_next(patch_child)) {
736 
737  target_child = pcmk__xml_match(target, patch_child, false);
738  process_v1_additions(target, target_child, patch_child);
739  }
740 }
741 
753 static bool
754 find_patch_xml_node(const xmlNode *patchset, int format, bool added,
755  xmlNode **patch_node)
756 {
757  xmlNode *cib_node;
758  const char *label;
759 
760  switch (format) {
761  case 1:
762  // @COMPAT Remove when v1 patchsets are removed
764  *patch_node = pcmk__xe_first_child(patchset, label, NULL, NULL);
765  cib_node = pcmk__xe_first_child(*patch_node, PCMK_XE_CIB, NULL,
766  NULL);
767  if (cib_node != NULL) {
768  *patch_node = cib_node;
769  }
770  break;
771  case 2:
772  label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE;
773  *patch_node = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL,
774  NULL);
775  *patch_node = pcmk__xe_first_child(*patch_node, label, NULL, NULL);
776  break;
777  default:
778  crm_warn("Unknown patch format: %d", format);
779  *patch_node = NULL;
780  return FALSE;
781  }
782  return TRUE;
783 }
784 
785 // Get CIB versions used for additions and deletions in a patchset
786 bool
787 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
788 {
789  int lpc = 0;
790  int format = 1;
791  xmlNode *tmp = NULL;
792 
793  const char *vfields[] = {
797  };
798 
799 
800  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
801 
802  /* Process removals */
803  if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
804  return -EINVAL;
805  }
806  if (tmp != NULL) {
807  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
808  crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
809  crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
810  }
811  }
812 
813  /* Process additions */
814  if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
815  return -EINVAL;
816  }
817  if (tmp != NULL) {
818  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
819  crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
820  crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
821  }
822  }
823  return pcmk_ok;
824 }
825 
835 static int
836 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
837 {
838  int lpc = 0;
839  bool changed = FALSE;
840 
841  int this[] = { 0, 0, 0 };
842  int add[] = { 0, 0, 0 };
843  int del[] = { 0, 0, 0 };
844 
845  const char *vfields[] = {
849  };
850 
851  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
852  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
853  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
854  if (this[lpc] < 0) {
855  this[lpc] = 0;
856  }
857  }
858 
859  /* Set some defaults in case nothing is present */
860  add[0] = this[0];
861  add[1] = this[1];
862  add[2] = this[2] + 1;
863  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
864  del[lpc] = this[lpc];
865  }
866 
867  xml_patch_versions(patchset, add, del);
868 
869  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
870  if (this[lpc] < del[lpc]) {
871  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
872  vfields[lpc], this[0], this[1], this[2],
873  del[0], del[1], del[2], add[0], add[1], add[2]);
874  return pcmk_rc_diff_resync;
875 
876  } else if (this[lpc] > del[lpc]) {
877  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
878  vfields[lpc], this[0], this[1], this[2],
879  del[0], del[1], del[2], add[0], add[1], add[2], patchset);
880  crm_log_xml_info(patchset, "OldPatch");
881  return pcmk_rc_old_data;
882  }
883  }
884 
885  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
886  if (add[lpc] > del[lpc]) {
887  changed = TRUE;
888  }
889  }
890 
891  if (!changed) {
892  crm_notice("Versions did not change in patch %d.%d.%d",
893  add[0], add[1], add[2]);
894  return pcmk_rc_old_data;
895  }
896 
897  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
898  add[0], add[1], add[2], this[0], this[1], this[2]);
899  return pcmk_rc_ok;
900 }
901 
902 // @COMPAT Remove when v1 patchsets are removed
903 static void
904 purge_v1_diff_markers(xmlNode *node)
905 {
906  xmlNode *child = NULL;
907 
908  CRM_CHECK(node != NULL, return);
909 
911  for (child = pcmk__xml_first_child(node); child != NULL;
912  child = pcmk__xml_next(child)) {
913  purge_v1_diff_markers(child);
914  }
915 }
916 
917 // @COMPAT Remove when v1 patchsets are removed
927 static int
928 apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
929 {
930  int rc = pcmk_rc_ok;
931  int root_nodes_seen = 0;
932 
933  xmlNode *child_diff = NULL;
934  xmlNode *added = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL,
935  NULL);
936  xmlNode *removed = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED,
937  NULL, NULL);
938  xmlNode *old = pcmk__xml_copy(NULL, xml);
939 
940  crm_trace("Subtraction Phase");
941  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
942  child_diff = pcmk__xml_next(child_diff)) {
943  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
944  if (root_nodes_seen == 0) {
945  process_v1_removals(xml, child_diff);
946  }
947  root_nodes_seen++;
948  }
949 
950  if (root_nodes_seen > 1) {
951  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
952  root_nodes_seen);
953  rc = ENOTUNIQ;
954  }
955 
956  root_nodes_seen = 0;
957  crm_trace("Addition Phase");
958  if (rc == pcmk_rc_ok) {
959  xmlNode *child_diff = NULL;
960 
961  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
962  child_diff = pcmk__xml_next(child_diff)) {
963  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
964  if (root_nodes_seen == 0) {
965  process_v1_additions(NULL, xml, child_diff);
966  }
967  root_nodes_seen++;
968  }
969  }
970 
971  if (root_nodes_seen > 1) {
972  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
973  root_nodes_seen);
974  rc = ENOTUNIQ;
975  }
976 
977  purge_v1_diff_markers(xml); // Purge prior to checking digest
978 
979  free_xml(old);
980  return rc;
981 }
982 
983 // Return first child matching element name and optionally id or position
984 static xmlNode *
985 first_matching_xml_child(const xmlNode *parent, const char *name,
986  const char *id, int position)
987 {
988  xmlNode *cIter = NULL;
989 
990  for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
991  cIter = pcmk__xml_next(cIter)) {
992  if (strcmp((const char *) cIter->name, name) != 0) {
993  continue;
994  } else if (id) {
995  const char *cid = pcmk__xe_id(cIter);
996 
997  if ((cid == NULL) || (strcmp(cid, id) != 0)) {
998  continue;
999  }
1000  }
1001 
1002  // "position" makes sense only for XML comments for now
1003  if ((cIter->type == XML_COMMENT_NODE)
1004  && (position >= 0)
1005  && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
1006  continue;
1007  }
1008 
1009  return cIter;
1010  }
1011  return NULL;
1012 }
1013 
1027 static xmlNode *
1028 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
1029 {
1030  xmlNode *target = (xmlNode *) top->doc;
1031  const char *current = key;
1032  char *section;
1033  char *remainder;
1034  char *id;
1035  char *tag;
1036  char *path = NULL;
1037  int rc;
1038  size_t key_len;
1039 
1040  CRM_CHECK(key != NULL, return NULL);
1041  key_len = strlen(key);
1042 
1043  /* These are scanned from key after a slash, so they can't be bigger
1044  * than key_len - 1 characters plus a null terminator.
1045  */
1046 
1047  remainder = pcmk__assert_alloc(key_len, sizeof(char));
1048  section = pcmk__assert_alloc(key_len, sizeof(char));
1049  id = pcmk__assert_alloc(key_len, sizeof(char));
1050  tag = pcmk__assert_alloc(key_len, sizeof(char));
1051 
1052  do {
1053  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1054  rc = sscanf(current, "/%[^/]%s", section, remainder);
1055  if (rc > 0) {
1056  // Separate FIRST_COMPONENT into TAG[@id='ID']
1057  int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
1058  int current_position = -1;
1059 
1060  /* The target position is for the final component tag, so only use
1061  * it if there is nothing left to search after this component.
1062  */
1063  if ((rc == 1) && (target_position >= 0)) {
1064  current_position = target_position;
1065  }
1066 
1067  switch (f) {
1068  case 1:
1069  // @COMPAT Remove when v1 patchsets are removed
1070  target = first_matching_xml_child(target, tag, NULL,
1071  current_position);
1072  break;
1073  case 2:
1074  target = first_matching_xml_child(target, tag, id,
1075  current_position);
1076  break;
1077  default:
1078  // This should not be possible
1079  target = NULL;
1080  break;
1081  }
1082  current = remainder;
1083  }
1084 
1085  // Continue if something remains to search, and we've matched so far
1086  } while ((rc == 2) && target);
1087 
1088  if (target) {
1089  crm_trace("Found %s for %s",
1090  (path = (char *) xmlGetNodePath(target)), key);
1091  free(path);
1092  } else {
1093  crm_debug("No match for %s", key);
1094  }
1095 
1096  free(remainder);
1097  free(section);
1098  free(tag);
1099  free(id);
1100  return target;
1101 }
1102 
1103 typedef struct xml_change_obj_s {
1104  const xmlNode *change;
1105  xmlNode *match;
1107 
1108 static gint
1109 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
1110 {
1111  const xml_change_obj_t *change_obj_a = a;
1112  const xml_change_obj_t *change_obj_b = b;
1113  int position_a = -1;
1114  int position_b = -1;
1115 
1116  crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
1117  crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
1118 
1119  if (position_a < position_b) {
1120  return -1;
1121 
1122  } else if (position_a > position_b) {
1123  return 1;
1124  }
1125 
1126  return 0;
1127 }
1128 
1138 static int
1139 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
1140 {
1141  int rc = pcmk_rc_ok;
1142  const xmlNode *change = NULL;
1143  GList *change_objs = NULL;
1144  GList *gIter = NULL;
1145 
1146  for (change = pcmk__xml_first_child(patchset); change != NULL;
1147  change = pcmk__xml_next(change)) {
1148  xmlNode *match = NULL;
1149  const char *op = crm_element_value(change, PCMK_XA_OPERATION);
1150  const char *xpath = crm_element_value(change, PCMK_XA_PATH);
1151  int position = -1;
1152 
1153  if (op == NULL) {
1154  continue;
1155  }
1156 
1157  crm_trace("Processing %s %s", change->name, op);
1158 
1159  /* PCMK_VALUE_DELETE changes for XML comments are generated with
1160  * PCMK_XE_POSITION
1161  */
1162  if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1163  crm_element_value_int(change, PCMK_XE_POSITION, &position);
1164  }
1165  match = search_v2_xpath(xml, xpath, position);
1166  crm_trace("Performing %s on %s with %p", op, xpath, match);
1167 
1168  if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
1169  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1170  continue;
1171 
1172  } else if (match == NULL) {
1173  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1174  rc = pcmk_rc_diff_failed;
1175  continue;
1176 
1177  } else if (pcmk__str_any_of(op,
1179  // Delay the adding of a PCMK_VALUE_CREATE object
1180  xml_change_obj_t *change_obj =
1182 
1183  change_obj->change = change;
1184  change_obj->match = match;
1185 
1186  change_objs = g_list_append(change_objs, change_obj);
1187 
1188  if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1189  // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
1190  if ((match->parent != NULL) && (match->parent->last != NULL)) {
1191  xmlAddNextSibling(match->parent->last, match);
1192  }
1193  }
1194 
1195  } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1196  free_xml(match);
1197 
1198  } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
1199  const xmlNode *child = pcmk__xe_first_child(change,
1201  NULL, NULL);
1202  const xmlNode *attrs = pcmk__xml_first_child(child);
1203 
1204  if (attrs == NULL) {
1205  rc = ENOMSG;
1206  continue;
1207  }
1208  pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
1209 
1210  for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
1211  pIter = pIter->next) {
1212  const char *name = (const char *) pIter->name;
1213  const char *value = pcmk__xml_attr_value(pIter);
1214 
1215  crm_xml_add(match, name, value);
1216  }
1217 
1218  } else {
1219  crm_err("Unknown operation: %s", op);
1220  rc = pcmk_rc_diff_failed;
1221  }
1222  }
1223 
1224  // Changes should be generated in the right order. Double checking.
1225  change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
1226 
1227  for (gIter = change_objs; gIter; gIter = gIter->next) {
1228  xml_change_obj_t *change_obj = gIter->data;
1229  xmlNode *match = change_obj->match;
1230  const char *op = NULL;
1231  const char *xpath = NULL;
1232 
1233  change = change_obj->change;
1234 
1235  op = crm_element_value(change, PCMK_XA_OPERATION);
1236  xpath = crm_element_value(change, PCMK_XA_PATH);
1237 
1238  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1239 
1240  if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
1241  int position = 0;
1242  xmlNode *child = NULL;
1243  xmlNode *match_child = NULL;
1244 
1245  match_child = match->children;
1246  crm_element_value_int(change, PCMK_XE_POSITION, &position);
1247 
1248  while ((match_child != NULL)
1249  && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1250  match_child = match_child->next;
1251  }
1252 
1253  child = pcmk__xml_copy(match, change->children);
1254 
1255  if (match_child != NULL) {
1256  crm_trace("Adding %s at position %d", child->name, position);
1257  xmlAddPrevSibling(match_child, child);
1258 
1259  } else {
1260  crm_trace("Adding %s at position %d (end)",
1261  child->name, position);
1262  }
1263 
1264  } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1265  int position = 0;
1266 
1267  crm_element_value_int(change, PCMK_XE_POSITION, &position);
1268  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1269  xmlNode *match_child = NULL;
1270  int p = position;
1271 
1272  if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1273  p++; // Skip ourselves
1274  }
1275 
1276  pcmk__assert(match->parent != NULL);
1277  match_child = match->parent->children;
1278 
1279  while ((match_child != NULL)
1280  && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1281  match_child = match_child->next;
1282  }
1283 
1284  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1285  match->name, position,
1287  match->prev, (match_child? "next":"last"),
1288  (match_child? match_child : match->parent->last));
1289 
1290  if (match_child) {
1291  xmlAddPrevSibling(match_child, match);
1292 
1293  } else {
1294  pcmk__assert(match->parent->last != NULL);
1295  xmlAddNextSibling(match->parent->last, match);
1296  }
1297 
1298  } else {
1299  crm_trace("%s is already in position %d",
1300  match->name, position);
1301  }
1302 
1303  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1304  crm_err("Moved %s.%s to position %d instead of %d (%p)",
1305  match->name, pcmk__xe_id(match),
1307  position, match->prev);
1308  rc = pcmk_rc_diff_failed;
1309  }
1310  }
1311  }
1312 
1313  g_list_free_full(change_objs, free);
1314  return rc;
1315 }
1316 
1317 int
1318 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1319 {
1320  int format = 1;
1321  int rc = pcmk_ok;
1322  xmlNode *old = NULL;
1323  const char *digest = NULL;
1324 
1325  if (patchset == NULL) {
1326  return rc;
1327  }
1328 
1329  pcmk__log_xml_patchset(LOG_TRACE, patchset);
1330 
1331  if (check_version) {
1332  rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
1333  if (rc != pcmk_ok) {
1334  return rc;
1335  }
1336  }
1337 
1338  digest = crm_element_value(patchset, PCMK__XA_DIGEST);
1339  if (digest != NULL) {
1340  /* Make original XML available for logging in case result doesn't have
1341  * expected digest
1342  */
1343  pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
1344  }
1345 
1346  if (rc == pcmk_ok) {
1347  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
1348  switch (format) {
1349  case 1:
1350  // @COMPAT Remove when v1 patchsets are removed
1351  rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1352  break;
1353  case 2:
1354  rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1355  break;
1356  default:
1357  crm_err("Unknown patch format: %d", format);
1358  rc = -EINVAL;
1359  }
1360  }
1361 
1362  if ((rc == pcmk_ok) && (digest != NULL)) {
1363  char *new_digest = NULL;
1365 
1366  new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1367  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1368  crm_info("v%d digest mis-match: expected %s, calculated %s",
1369  format, digest, new_digest);
1370  rc = -pcmk_err_diff_failed;
1372  {
1373  save_xml_to_file(old, "PatchDigest:input", NULL);
1374  save_xml_to_file(xml, "PatchDigest:result", NULL);
1375  save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1376  },
1377  {}
1378  );
1379 
1380  } else {
1381  crm_trace("v%d digest matched: expected %s, calculated %s",
1382  format, digest, new_digest);
1383  }
1384  free(new_digest);
1385  free(version);
1386  }
1387  free_xml(old);
1388  return rc;
1389 }
1390 
1391 // @COMPAT Remove when v1 patchsets are removed
1392 static bool
1393 can_prune_leaf_v1(xmlNode *node)
1394 {
1395  xmlNode *cIter = NULL;
1396  bool can_prune = true;
1397 
1398  CRM_CHECK(node != NULL, return false);
1399 
1400  /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed for
1401  * rolling upgrades)
1402  */
1403  if (pcmk__strcase_any_of((const char *) node->name,
1406  NULL)) {
1407  return false;
1408  }
1409 
1410  for (xmlAttrPtr a = pcmk__xe_first_attr(node); a != NULL; a = a->next) {
1411  const char *p_name = (const char *) a->name;
1412 
1413  if (strcmp(p_name, PCMK_XA_ID) == 0) {
1414  continue;
1415  }
1416  can_prune = false;
1417  }
1418 
1419  cIter = pcmk__xml_first_child(node);
1420  while (cIter) {
1421  xmlNode *child = cIter;
1422 
1423  cIter = pcmk__xml_next(cIter);
1424  if (can_prune_leaf_v1(child)) {
1425  free_xml(child);
1426  } else {
1427  can_prune = false;
1428  }
1429  }
1430  return can_prune;
1431 }
1432 
1433 // @COMPAT Remove when v1 patchsets are removed
1434 xmlNode *
1435 pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
1436 {
1437  xmlNode *tmp1 = NULL;
1438  xmlNode *diff = pcmk__xe_create(NULL, PCMK_XE_DIFF);
1439  xmlNode *removed = pcmk__xe_create(diff, PCMK__XE_DIFF_REMOVED);
1440  xmlNode *added = pcmk__xe_create(diff, PCMK__XE_DIFF_ADDED);
1441 
1443 
1444  tmp1 = subtract_v1_xml_object(removed, old, new, false, NULL,
1445  "removed:top");
1446  if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1447  free_xml(tmp1);
1448  }
1449 
1450  tmp1 = subtract_v1_xml_object(added, new, old, true, NULL, "added:top");
1451  if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1452  free_xml(tmp1);
1453  }
1454 
1455  if ((added->children == NULL) && (removed->children == NULL)) {
1456  free_xml(diff);
1457  diff = NULL;
1458  }
1459 
1460  return diff;
1461 }
1462 
1463 // Deprecated functions kept only for backward API compatibility
1464 // LCOV_EXCL_START
1465 
1466 #include <crm/common/xml_compat.h>
1467 
1468 gboolean
1469 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1470 {
1471  gboolean result = TRUE;
1472  int root_nodes_seen = 0;
1473  const char *digest = crm_element_value(diff, PCMK__XA_DIGEST);
1474  const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET);
1475 
1476  xmlNode *child_diff = NULL;
1477  xmlNode *added = pcmk__xe_first_child(diff, PCMK__XE_DIFF_ADDED, NULL,
1478  NULL);
1479  xmlNode *removed = pcmk__xe_first_child(diff, PCMK__XE_DIFF_REMOVED, NULL,
1480  NULL);
1481 
1482  CRM_CHECK(new_xml != NULL, return FALSE);
1483 
1484  crm_trace("Subtraction Phase");
1485  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1486  child_diff = pcmk__xml_next(child_diff)) {
1487  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1488  if (root_nodes_seen == 0) {
1489  *new_xml = subtract_v1_xml_object(NULL, old_xml, child_diff, false,
1490  NULL, NULL);
1491  }
1492  root_nodes_seen++;
1493  }
1494 
1495  if (root_nodes_seen == 0) {
1496  *new_xml = pcmk__xml_copy(NULL, old_xml);
1497 
1498  } else if (root_nodes_seen > 1) {
1499  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1500  root_nodes_seen);
1501  result = FALSE;
1502  }
1503 
1504  root_nodes_seen = 0;
1505  crm_trace("Addition Phase");
1506  if (result) {
1507  xmlNode *child_diff = NULL;
1508 
1509  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1510  child_diff = pcmk__xml_next(child_diff)) {
1511  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1512  if (root_nodes_seen == 0) {
1513  pcmk__xml_update(NULL, *new_xml, child_diff, pcmk__xaf_none,
1514  true);
1515  }
1516  root_nodes_seen++;
1517  }
1518  }
1519 
1520  if (root_nodes_seen > 1) {
1521  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1522  root_nodes_seen);
1523  result = FALSE;
1524 
1525  } else if (result && (digest != NULL)) {
1526  char *new_digest = NULL;
1527 
1528  purge_v1_diff_markers(*new_xml); // Purge now so diff is ok
1529  new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1530  version);
1531  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1532  crm_info("Digest mis-match: expected %s, calculated %s",
1533  digest, new_digest);
1534  result = FALSE;
1535 
1537  {
1538  save_xml_to_file(old_xml, "diff:original", NULL);
1539  save_xml_to_file(diff, "diff:input", NULL);
1540  save_xml_to_file(*new_xml, "diff:new", NULL);
1541  },
1542  {}
1543  );
1544 
1545  } else {
1546  crm_trace("Digest matched: expected %s, calculated %s",
1547  digest, new_digest);
1548  }
1549  free(new_digest);
1550 
1551  } else if (result) {
1552  purge_v1_diff_markers(*new_xml); // Purge now so diff is ok
1553  }
1554 
1555  return result;
1556 }
1557 
1558 void
1559 purge_diff_markers(xmlNode *a_node)
1560 {
1561  purge_v1_diff_markers(a_node);
1562 }
1563 
1564 xmlNode *
1565 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1566 {
1567  return pcmk__diff_v1_xml_object(old, new, suppress);
1568 }
1569 
1570 xmlNode *
1571 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1572  gboolean full, gboolean *changed, const char *marker)
1573 {
1574  return subtract_v1_xml_object(parent, left, right, full, changed, marker);
1575 }
1576 
1577 gboolean
1578 can_prune_leaf(xmlNode *xml_node)
1579 {
1580  return can_prune_leaf_v1(xml_node);
1581 }
1582 
1583 // LCOV_EXCL_STOP
1584 // 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:974
#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:330
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:548
pcmk_resource_t * dummy
#define PCMK_XA_PATH
Definition: xml_names.h:355
#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:736
bool pcmk__strcase_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1038
#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:177
G_GNUC_INTERNAL int pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:383
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:360
#define CRM_FEATURE_SET
Definition: crm.h:72
#define PCMK_XA_NUM_UPDATES
Definition: xml_names.h:341
#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:313
G_GNUC_INTERNAL xmlAttr * pcmk__xe_set_attr_force(xmlNode *node, const char *name, const char *value)
Definition: nvpair.c:290
#define PCMK_XA_FORMAT
Definition: xml_names.h:291
#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:494
#define PCMK_XA_OPERATION
Definition: xml_names.h:349
Deprecated Pacemaker XML API.
#define crm_warn(fmt, args...)
Definition: logging.h:394
#define pcmk_err_diff_failed
Definition: results.h:76
G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
Definition: xml_comment.c:99
#define PCMK_XE_VERSION
Definition: xml_names.h:220
#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:758
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:458
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml.c:481
#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:742
#define PCMK_XE_CHANGE
Definition: xml_names.h:74
#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:94
#define PCMK_XE_TARGET
Definition: xml_names.h:210
#define PCMK_XA_EPOCH
Definition: xml_names.h:268
xmlNode * pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
Definition: patchset.c:1435
Wrappers for and extensions to libxml2.
void pcmk__xe_remove_attr(xmlNode *element, const char *name)
Definition: xml.c:702
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:366
#define PCMK__NELEM(a)
Definition: internal.h:48
Flag has no effect.
Definition: xml_internal.h:440
#define PCMK_XA_ID
Definition: xml_names.h:301
void purge_diff_markers(xmlNode *a_node)
Definition: patchset.c:1559
#define PCMK_XA_VALUE
Definition: xml_names.h:442
void free_xml(xmlNode *child)
Definition: xml.c:958
#define PCMK_XE_CONFIGURATION
Definition: xml_names.h:87
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1062
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:426
#define PCMK_XE_SOURCE
Definition: xml_names.h:200
const xmlChar * pcmkXmlStr
Definition: xml.h:41
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:232
#define pcmk__assert(expr)
#define PCMK_XA_CRM_FEATURE_SET
Definition: xml_names.h:254
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:1692
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: patchset.c:1469
xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
Definition: patchset.c:1565
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 ENOTUNIQ
Definition: portability.h:81
#define PCMK_XE_DIFF
Definition: xml_names.h:100
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:145
int compare_version(const char *version1, const char *version2)
Definition: utils.c:186
#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:787
#define pcmk_ok
Definition: results.h:65
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:183
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: patchset.c:1571
const char * parent
Definition: cib.c:27
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:1318
#define PCMK_XA_ADMIN_EPOCH
Definition: xml_names.h:232
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml.c:770
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:297
#define PCMK_XE_POSITION
Definition: xml_names.h:163
#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:1578