pacemaker  2.1.6-802a72226b
Scalable High-Availability cluster resource manager
patchset.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2023 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/msg_xml.h>
25 #include <crm/common/xml.h>
26 #include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
27 #include "crmcommon_private.h"
28 
29 static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
30  xmlNode *right, gboolean *changed);
31 
32 /* Add changes for specified XML to patchset.
33  * For patchset format, refer to diff schema.
34  */
35 static void
36 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
37 {
38  xmlNode *cIter = NULL;
39  xmlAttr *pIter = NULL;
40  xmlNode *change = NULL;
41  xml_node_private_t *nodepriv = xml->_private;
42  const char *value = NULL;
43 
44  // If this XML node is new, just report that
45  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
46  GString *xpath = pcmk__element_xpath(xml->parent);
47 
48  if (xpath != NULL) {
49  int position = pcmk__xml_position(xml, pcmk__xf_deleted);
50 
51  change = create_xml_node(patchset, XML_DIFF_CHANGE);
52 
53  crm_xml_add(change, XML_DIFF_OP, "create");
54  crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
55  crm_xml_add_int(change, XML_DIFF_POSITION, position);
56  add_node_copy(change, xml);
57  g_string_free(xpath, TRUE);
58  }
59 
60  return;
61  }
62 
63  // Check each of the XML node's attributes for changes
64  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
65  pIter = pIter->next) {
66  xmlNode *attr = NULL;
67 
68  nodepriv = pIter->_private;
69  if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
70  continue;
71  }
72 
73  if (change == NULL) {
74  GString *xpath = pcmk__element_xpath(xml);
75 
76  if (xpath != NULL) {
77  change = create_xml_node(patchset, XML_DIFF_CHANGE);
78 
79  crm_xml_add(change, XML_DIFF_OP, "modify");
80  crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
81 
82  change = create_xml_node(change, XML_DIFF_LIST);
83  g_string_free(xpath, TRUE);
84  }
85  }
86 
87  attr = create_xml_node(change, XML_DIFF_ATTR);
88 
89  crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
90  if (nodepriv->flags & pcmk__xf_deleted) {
91  crm_xml_add(attr, XML_DIFF_OP, "unset");
92 
93  } else {
94  crm_xml_add(attr, XML_DIFF_OP, "set");
95 
96  value = crm_element_value(xml, (const char *) pIter->name);
97  crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
98  }
99  }
100 
101  if (change) {
102  xmlNode *result = NULL;
103 
104  change = create_xml_node(change->parent, XML_DIFF_RESULT);
105  result = create_xml_node(change, (const char *)xml->name);
106 
107  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
108  pIter = pIter->next) {
109  nodepriv = pIter->_private;
110  if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
111  value = crm_element_value(xml, (const char *) pIter->name);
112  crm_xml_add(result, (const char *)pIter->name, value);
113  }
114  }
115  }
116 
117  // Now recursively do the same for each child node of this node
118  for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
119  cIter = pcmk__xml_next(cIter)) {
120  add_xml_changes_to_patchset(cIter, patchset);
121  }
122 
123  nodepriv = xml->_private;
124  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
125  GString *xpath = pcmk__element_xpath(xml);
126 
127  crm_trace("%s.%s moved to position %d",
128  xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
129 
130  if (xpath != NULL) {
131  change = create_xml_node(patchset, XML_DIFF_CHANGE);
132 
133  crm_xml_add(change, XML_DIFF_OP, "move");
134  crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
137  g_string_free(xpath, TRUE);
138  }
139  }
140 }
141 
142 static bool
143 is_config_change(xmlNode *xml)
144 {
145  GList *gIter = NULL;
146  xml_node_private_t *nodepriv = NULL;
147  xml_doc_private_t *docpriv;
148  xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
149 
150  if (config) {
151  nodepriv = config->_private;
152  }
153  if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
154  return TRUE;
155  }
156 
157  if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
158  docpriv = xml->doc->_private;
159  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
160  pcmk__deleted_xml_t *deleted_obj = gIter->data;
161 
162  if (strstr(deleted_obj->path,
163  "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION) != NULL) {
164  return TRUE;
165  }
166  }
167  }
168  return FALSE;
169 }
170 
171 static void
172 xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff,
173  gboolean changed)
174 {
175  int lpc = 0;
176  xmlNode *cib = NULL;
177  xmlNode *diff_child = NULL;
178 
179  const char *tag = NULL;
180 
181  const char *vfields[] = {
185  };
186 
187  if (local_diff == NULL) {
188  crm_trace("Nothing to do");
189  return;
190  }
191 
192  tag = "diff-removed";
193  diff_child = find_xml_node(local_diff, tag, FALSE);
194  if (diff_child == NULL) {
195  diff_child = create_xml_node(local_diff, tag);
196  }
197 
198  tag = XML_TAG_CIB;
199  cib = find_xml_node(diff_child, tag, FALSE);
200  if (cib == NULL) {
201  cib = create_xml_node(diff_child, tag);
202  }
203 
204  for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
205  const char *value = crm_element_value(last, vfields[lpc]);
206 
207  crm_xml_add(diff_child, vfields[lpc], value);
208  if (changed || lpc == 2) {
209  crm_xml_add(cib, vfields[lpc], value);
210  }
211  }
212 
213  tag = "diff-added";
214  diff_child = find_xml_node(local_diff, tag, FALSE);
215  if (diff_child == NULL) {
216  diff_child = create_xml_node(local_diff, tag);
217  }
218 
219  tag = XML_TAG_CIB;
220  cib = find_xml_node(diff_child, tag, FALSE);
221  if (cib == NULL) {
222  cib = create_xml_node(diff_child, tag);
223  }
224 
225  for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
226  const char *value = crm_element_value(next, vfields[lpc]);
227 
228  crm_xml_add(diff_child, vfields[lpc], value);
229  }
230 
231  for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
232  const char *p_value = crm_element_value(next, (const char *) a->name);
233 
234  xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
235  }
236 
237  crm_log_xml_explicit(local_diff, "Repaired-diff");
238 }
239 
240 static xmlNode *
241 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
242  bool suppress)
243 {
244  xmlNode *patchset = diff_xml_object(source, target, suppress);
245 
246  if (patchset) {
248  xml_repair_v1_diff(source, target, patchset, config);
249  crm_xml_add(patchset, "format", "1");
250  }
251  return patchset;
252 }
253 
254 static xmlNode *
255 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
256 {
257  int lpc = 0;
258  GList *gIter = NULL;
259  xml_doc_private_t *docpriv;
260 
261  xmlNode *v = NULL;
262  xmlNode *version = NULL;
263  xmlNode *patchset = NULL;
264  const char *vfields[] = {
268  };
269 
271  if (!xml_document_dirty(target)) {
272  return NULL;
273  }
274 
275  CRM_ASSERT(target->doc);
276  docpriv = target->doc->_private;
277 
278  patchset = create_xml_node(NULL, XML_TAG_DIFF);
279  crm_xml_add_int(patchset, "format", 2);
280 
282 
284  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
285  const char *value = crm_element_value(source, vfields[lpc]);
286 
287  if (value == NULL) {
288  value = "1";
289  }
290  crm_xml_add(v, vfields[lpc], value);
291  }
292 
294  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
295  const char *value = crm_element_value(target, vfields[lpc]);
296 
297  if (value == NULL) {
298  value = "1";
299  }
300  crm_xml_add(v, vfields[lpc], value);
301  }
302 
303  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
304  pcmk__deleted_xml_t *deleted_obj = gIter->data;
305  xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
306 
307  crm_xml_add(change, XML_DIFF_OP, "delete");
308  crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
309  if (deleted_obj->position >= 0) {
310  crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
311  }
312  }
313 
314  add_xml_changes_to_patchset(target, patchset);
315  return patchset;
316 }
317 
318 xmlNode *
319 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
320  bool *config_changed, bool manage_version)
321 {
322  int counter = 0;
323  bool config = FALSE;
324  xmlNode *patch = NULL;
325  const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
326 
328  if (!xml_document_dirty(target)) {
329  crm_trace("No change %d", format);
330  return NULL; /* No change */
331  }
332 
333  config = is_config_change(target);
334  if (config_changed) {
335  *config_changed = config;
336  }
337 
338  if (manage_version && config) {
339  crm_trace("Config changed %d", format);
341 
344 
345  } else if (manage_version) {
347  crm_trace("Status changed %d - %d %s", format, counter,
349  crm_xml_add_int(target, XML_ATTR_NUMUPDATES, (counter + 1));
350  }
351 
352  if (format == 0) {
353  if (compare_version("3.0.8", version) < 0) {
354  format = 2;
355  } else {
356  format = 1;
357  }
358  crm_trace("Using patch format %d for version: %s", format, version);
359  }
360 
361  switch (format) {
362  case 1:
363  patch = xml_create_patchset_v1(source, target, config, FALSE);
364  break;
365  case 2:
366  patch = xml_create_patchset_v2(source, target);
367  break;
368  default:
369  crm_err("Unknown patch format: %d", format);
370  return NULL;
371  }
372  return patch;
373 }
374 
375 void
376 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
377  bool with_digest)
378 {
379  int format = 1;
380  const char *version = NULL;
381  char *digest = NULL;
382 
383  if ((patch == NULL) || (source == NULL) || (target == NULL)) {
384  return;
385  }
386 
387  /* We should always call xml_accept_changes() before calculating a digest.
388  * Otherwise, with an on-tracking dirty target, we could get a wrong digest.
389  */
391 
392  crm_element_value_int(patch, "format", &format);
393  if ((format > 1) && !with_digest) {
394  return;
395  }
396 
398  digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
399 
400  crm_xml_add(patch, XML_ATTR_DIGEST, digest);
401  free(digest);
402 
403  return;
404 }
405 
406 // Return true if attribute name is not "id"
407 static bool
408 not_id(xmlAttrPtr attr, void *user_data)
409 {
410  return strcmp((const char *) attr->name, XML_ATTR_ID) != 0;
411 }
412 
413 // Apply the removals section of an v1 patchset to an XML node
414 static void
415 process_v1_removals(xmlNode *target, xmlNode *patch)
416 {
417  xmlNode *patch_child = NULL;
418  xmlNode *cIter = NULL;
419 
420  char *id = NULL;
421  const char *name = NULL;
422  const char *value = NULL;
423 
424  if ((target == NULL) || (patch == NULL)) {
425  return;
426  }
427 
428  if (target->type == XML_COMMENT_NODE) {
429  gboolean dummy;
430 
431  subtract_xml_comment(target->parent, target, patch, &dummy);
432  }
433 
434  name = crm_element_name(target);
435  CRM_CHECK(name != NULL, return);
436  CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
438  return);
439  CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
440 
441  // Check for XML_DIFF_MARKER in a child
443  value = crm_element_value(patch, XML_DIFF_MARKER);
444  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
445  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
446  free_xml(target);
447  free(id);
448  return;
449  }
450 
451  // Removing then restoring id would change ordering of properties
452  pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
453 
454  // Changes to child objects
455  cIter = pcmk__xml_first_child(target);
456  while (cIter) {
457  xmlNode *target_child = cIter;
458 
459  cIter = pcmk__xml_next(cIter);
460  patch_child = pcmk__xml_match(patch, target_child, false);
461  process_v1_removals(target_child, patch_child);
462  }
463  free(id);
464 }
465 
466 // Apply the additions section of an v1 patchset to an XML node
467 static void
468 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
469 {
470  xmlNode *patch_child = NULL;
471  xmlNode *target_child = NULL;
472  xmlAttrPtr xIter = NULL;
473 
474  const char *id = NULL;
475  const char *name = NULL;
476  const char *value = NULL;
477 
478  if (patch == NULL) {
479  return;
480  } else if ((parent == NULL) && (target == NULL)) {
481  return;
482  }
483 
484  // Check for XML_DIFF_MARKER in a child
485  value = crm_element_value(patch, XML_DIFF_MARKER);
486  if ((target == NULL) && (value != NULL)
487  && (strcmp(value, "added:top") == 0)) {
488  id = ID(patch);
489  name = crm_element_name(patch);
490  crm_trace("We are the root of the addition: %s.id=%s", name, id);
491  add_node_copy(parent, patch);
492  return;
493 
494  } else if (target == NULL) {
495  id = ID(patch);
496  name = crm_element_name(patch);
497  crm_err("Could not locate: %s.id=%s", name, id);
498  return;
499  }
500 
501  if (target->type == XML_COMMENT_NODE) {
502  pcmk__xc_update(parent, target, patch);
503  }
504 
505  name = crm_element_name(target);
506  CRM_CHECK(name != NULL, return);
507  CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch),
509  return);
510  CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
511 
512  for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
513  xIter = xIter->next) {
514  const char *p_name = (const char *) xIter->name;
515  const char *p_value = crm_element_value(patch, p_name);
516 
517  xml_remove_prop(target, p_name); // Preserve patch order
518  crm_xml_add(target, p_name, p_value);
519  }
520 
521  // Changes to child objects
522  for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
523  patch_child = pcmk__xml_next(patch_child)) {
524 
525  target_child = pcmk__xml_match(target, patch_child, false);
526  process_v1_additions(target, target_child, patch_child);
527  }
528 }
529 
541 static bool
542 find_patch_xml_node(const xmlNode *patchset, int format, bool added,
543  xmlNode **patch_node)
544 {
545  xmlNode *cib_node;
546  const char *label;
547 
548  switch (format) {
549  case 1:
550  label = added? "diff-added" : "diff-removed";
551  *patch_node = find_xml_node(patchset, label, FALSE);
552  cib_node = find_xml_node(*patch_node, "cib", FALSE);
553  if (cib_node != NULL) {
554  *patch_node = cib_node;
555  }
556  break;
557  case 2:
558  label = added? "target" : "source";
559  *patch_node = find_xml_node(patchset, "version", FALSE);
560  *patch_node = find_xml_node(*patch_node, label, FALSE);
561  break;
562  default:
563  crm_warn("Unknown patch format: %d", format);
564  *patch_node = NULL;
565  return FALSE;
566  }
567  return TRUE;
568 }
569 
570 // Get CIB versions used for additions and deletions in a patchset
571 bool
572 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
573 {
574  int lpc = 0;
575  int format = 1;
576  xmlNode *tmp = NULL;
577 
578  const char *vfields[] = {
582  };
583 
584 
585  crm_element_value_int(patchset, "format", &format);
586 
587  /* Process removals */
588  if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
589  return -EINVAL;
590  }
591  if (tmp != NULL) {
592  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
593  crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
594  crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
595  }
596  }
597 
598  /* Process additions */
599  if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
600  return -EINVAL;
601  }
602  if (tmp != NULL) {
603  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
604  crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
605  crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
606  }
607  }
608  return pcmk_ok;
609 }
610 
621 static int
622 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset, int format)
623 {
624  int lpc = 0;
625  bool changed = FALSE;
626 
627  int this[] = { 0, 0, 0 };
628  int add[] = { 0, 0, 0 };
629  int del[] = { 0, 0, 0 };
630 
631  const char *vfields[] = {
635  };
636 
637  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
638  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
639  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
640  if (this[lpc] < 0) {
641  this[lpc] = 0;
642  }
643  }
644 
645  /* Set some defaults in case nothing is present */
646  add[0] = this[0];
647  add[1] = this[1];
648  add[2] = this[2] + 1;
649  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
650  del[lpc] = this[lpc];
651  }
652 
653  xml_patch_versions(patchset, add, del);
654 
655  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
656  if (this[lpc] < del[lpc]) {
657  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
658  vfields[lpc], this[0], this[1], this[2],
659  del[0], del[1], del[2], add[0], add[1], add[2]);
660  return pcmk_rc_diff_resync;
661 
662  } else if (this[lpc] > del[lpc]) {
663  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
664  vfields[lpc], this[0], this[1], this[2],
665  del[0], del[1], del[2], add[0], add[1], add[2], patchset);
666  crm_log_xml_info(patchset, "OldPatch");
667  return pcmk_rc_old_data;
668  }
669  }
670 
671  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
672  if (add[lpc] > del[lpc]) {
673  changed = TRUE;
674  }
675  }
676 
677  if (!changed) {
678  crm_notice("Versions did not change in patch %d.%d.%d",
679  add[0], add[1], add[2]);
680  return pcmk_rc_old_data;
681  }
682 
683  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
684  add[0], add[1], add[2], this[0], this[1], this[2]);
685  return pcmk_rc_ok;
686 }
687 
697 static int
698 apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
699 {
700  int rc = pcmk_rc_ok;
701  int root_nodes_seen = 0;
702 
703  xmlNode *child_diff = NULL;
704  xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
705  xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
706  xmlNode *old = copy_xml(xml);
707 
708  crm_trace("Subtraction Phase");
709  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
710  child_diff = pcmk__xml_next(child_diff)) {
711  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
712  if (root_nodes_seen == 0) {
713  process_v1_removals(xml, child_diff);
714  }
715  root_nodes_seen++;
716  }
717 
718  if (root_nodes_seen > 1) {
719  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
720  root_nodes_seen);
721  rc = ENOTUNIQ;
722  }
723 
724  root_nodes_seen = 0;
725  crm_trace("Addition Phase");
726  if (rc == pcmk_rc_ok) {
727  xmlNode *child_diff = NULL;
728 
729  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
730  child_diff = pcmk__xml_next(child_diff)) {
731  CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
732  if (root_nodes_seen == 0) {
733  process_v1_additions(NULL, xml, child_diff);
734  }
735  root_nodes_seen++;
736  }
737  }
738 
739  if (root_nodes_seen > 1) {
740  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
741  root_nodes_seen);
742  rc = ENOTUNIQ;
743  }
744 
745  purge_diff_markers(xml); // Purge prior to checking digest
746 
747  free_xml(old);
748  return rc;
749 }
750 
751 // Return first child matching element name and optionally id or position
752 static xmlNode *
753 first_matching_xml_child(const xmlNode *parent, const char *name,
754  const char *id, int position)
755 {
756  xmlNode *cIter = NULL;
757 
758  for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
759  cIter = pcmk__xml_next(cIter)) {
760  if (strcmp((const char *) cIter->name, name) != 0) {
761  continue;
762  } else if (id) {
763  const char *cid = ID(cIter);
764 
765  if ((cid == NULL) || (strcmp(cid, id) != 0)) {
766  continue;
767  }
768  }
769 
770  // "position" makes sense only for XML comments for now
771  if ((cIter->type == XML_COMMENT_NODE)
772  && (position >= 0)
773  && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
774  continue;
775  }
776 
777  return cIter;
778  }
779  return NULL;
780 }
781 
795 static xmlNode *
796 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
797 {
798  xmlNode *target = (xmlNode *) top->doc;
799  const char *current = key;
800  char *section;
801  char *remainder;
802  char *id;
803  char *tag;
804  char *path = NULL;
805  int rc;
806  size_t key_len;
807 
808  CRM_CHECK(key != NULL, return NULL);
809  key_len = strlen(key);
810 
811  /* These are scanned from key after a slash, so they can't be bigger
812  * than key_len - 1 characters plus a null terminator.
813  */
814 
815  remainder = calloc(key_len, sizeof(char));
816  CRM_ASSERT(remainder != NULL);
817 
818  section = calloc(key_len, sizeof(char));
819  CRM_ASSERT(section != NULL);
820 
821  id = calloc(key_len, sizeof(char));
822  CRM_ASSERT(id != NULL);
823 
824  tag = calloc(key_len, sizeof(char));
825  CRM_ASSERT(tag != NULL);
826 
827  do {
828  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
829  rc = sscanf(current, "/%[^/]%s", section, remainder);
830  if (rc > 0) {
831  // Separate FIRST_COMPONENT into TAG[@id='ID']
832  int f = sscanf(section, "%[^[][@" XML_ATTR_ID "='%[^']", tag, id);
833  int current_position = -1;
834 
835  /* The target position is for the final component tag, so only use
836  * it if there is nothing left to search after this component.
837  */
838  if ((rc == 1) && (target_position >= 0)) {
839  current_position = target_position;
840  }
841 
842  switch (f) {
843  case 1:
844  target = first_matching_xml_child(target, tag, NULL,
845  current_position);
846  break;
847  case 2:
848  target = first_matching_xml_child(target, tag, id,
849  current_position);
850  break;
851  default:
852  // This should not be possible
853  target = NULL;
854  break;
855  }
856  current = remainder;
857  }
858 
859  // Continue if something remains to search, and we've matched so far
860  } while ((rc == 2) && target);
861 
862  if (target) {
863  crm_trace("Found %s for %s",
864  (path = (char *) xmlGetNodePath(target)), key);
865  free(path);
866  } else {
867  crm_debug("No match for %s", key);
868  }
869 
870  free(remainder);
871  free(section);
872  free(tag);
873  free(id);
874  return target;
875 }
876 
877 typedef struct xml_change_obj_s {
878  const xmlNode *change;
879  xmlNode *match;
881 
882 static gint
883 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
884 {
885  const xml_change_obj_t *change_obj_a = a;
886  const xml_change_obj_t *change_obj_b = b;
887  int position_a = -1;
888  int position_b = -1;
889 
890  crm_element_value_int(change_obj_a->change, XML_DIFF_POSITION, &position_a);
891  crm_element_value_int(change_obj_b->change, XML_DIFF_POSITION, &position_b);
892 
893  if (position_a < position_b) {
894  return -1;
895 
896  } else if (position_a > position_b) {
897  return 1;
898  }
899 
900  return 0;
901 }
902 
912 static int
913 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
914 {
915  int rc = pcmk_rc_ok;
916  const xmlNode *change = NULL;
917  GList *change_objs = NULL;
918  GList *gIter = NULL;
919 
920  for (change = pcmk__xml_first_child(patchset); change != NULL;
921  change = pcmk__xml_next(change)) {
922  xmlNode *match = NULL;
923  const char *op = crm_element_value(change, XML_DIFF_OP);
924  const char *xpath = crm_element_value(change, XML_DIFF_PATH);
925  int position = -1;
926 
927  if (op == NULL) {
928  continue;
929  }
930 
931  crm_trace("Processing %s %s", change->name, op);
932 
933  // "delete" changes for XML comments are generated with "position"
934  if (strcmp(op, "delete") == 0) {
935  crm_element_value_int(change, XML_DIFF_POSITION, &position);
936  }
937  match = search_v2_xpath(xml, xpath, position);
938  crm_trace("Performing %s on %s with %p", op, xpath, match);
939 
940  if ((match == NULL) && (strcmp(op, "delete") == 0)) {
941  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
942  continue;
943 
944  } else if (match == NULL) {
945  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
946  rc = pcmk_rc_diff_failed;
947  continue;
948 
949  } else if ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) {
950  // Delay the adding of a "create" object
951  xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
952 
953  CRM_ASSERT(change_obj != NULL);
954 
955  change_obj->change = change;
956  change_obj->match = match;
957 
958  change_objs = g_list_append(change_objs, change_obj);
959 
960  if (strcmp(op, "move") == 0) {
961  // Temporarily put the "move" object after the last sibling
962  if ((match->parent != NULL) && (match->parent->last != NULL)) {
963  xmlAddNextSibling(match->parent->last, match);
964  }
965  }
966 
967  } else if (strcmp(op, "delete") == 0) {
968  free_xml(match);
969 
970  } else if (strcmp(op, "modify") == 0) {
971  xmlNode *attrs = NULL;
972 
973  attrs = pcmk__xml_first_child(first_named_child(change,
974  XML_DIFF_RESULT));
975  if (attrs == NULL) {
976  rc = ENOMSG;
977  continue;
978  }
979  pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
980 
981  for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
982  pIter = pIter->next) {
983  const char *name = (const char *) pIter->name;
984  const char *value = crm_element_value(attrs, name);
985 
986  crm_xml_add(match, name, value);
987  }
988 
989  } else {
990  crm_err("Unknown operation: %s", op);
991  rc = pcmk_rc_diff_failed;
992  }
993  }
994 
995  // Changes should be generated in the right order. Double checking.
996  change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
997 
998  for (gIter = change_objs; gIter; gIter = gIter->next) {
999  xml_change_obj_t *change_obj = gIter->data;
1000  xmlNode *match = change_obj->match;
1001  const char *op = NULL;
1002  const char *xpath = NULL;
1003 
1004  change = change_obj->change;
1005 
1006  op = crm_element_value(change, XML_DIFF_OP);
1007  xpath = crm_element_value(change, XML_DIFF_PATH);
1008 
1009  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1010 
1011  if (strcmp(op, "create") == 0) {
1012  int position = 0;
1013  xmlNode *child = NULL;
1014  xmlNode *match_child = NULL;
1015 
1016  match_child = match->children;
1017  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1018 
1019  while ((match_child != NULL)
1020  && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1021  match_child = match_child->next;
1022  }
1023 
1024  child = xmlDocCopyNode(change->children, match->doc, 1);
1025  if (match_child) {
1026  crm_trace("Adding %s at position %d", child->name, position);
1027  xmlAddPrevSibling(match_child, child);
1028 
1029  } else if (match->last) {
1030  crm_trace("Adding %s at position %d (end)",
1031  child->name, position);
1032  xmlAddNextSibling(match->last, child);
1033 
1034  } else {
1035  crm_trace("Adding %s at position %d (first)",
1036  child->name, position);
1037  CRM_LOG_ASSERT(position == 0);
1038  xmlAddChild(match, child);
1039  }
1040  pcmk__mark_xml_created(child);
1041 
1042  } else if (strcmp(op, "move") == 0) {
1043  int position = 0;
1044 
1045  crm_element_value_int(change, XML_DIFF_POSITION, &position);
1046  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1047  xmlNode *match_child = NULL;
1048  int p = position;
1049 
1050  if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1051  p++; // Skip ourselves
1052  }
1053 
1054  CRM_ASSERT(match->parent != NULL);
1055  match_child = match->parent->children;
1056 
1057  while ((match_child != NULL)
1058  && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1059  match_child = match_child->next;
1060  }
1061 
1062  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1063  match->name, position,
1065  match->prev, (match_child? "next":"last"),
1066  (match_child? match_child : match->parent->last));
1067 
1068  if (match_child) {
1069  xmlAddPrevSibling(match_child, match);
1070 
1071  } else {
1072  CRM_ASSERT(match->parent->last != NULL);
1073  xmlAddNextSibling(match->parent->last, match);
1074  }
1075 
1076  } else {
1077  crm_trace("%s is already in position %d",
1078  match->name, position);
1079  }
1080 
1081  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1082  crm_err("Moved %s.%s to position %d instead of %d (%p)",
1083  match->name, ID(match),
1085  position, match->prev);
1086  rc = pcmk_rc_diff_failed;
1087  }
1088  }
1089  }
1090 
1091  g_list_free_full(change_objs, free);
1092  return rc;
1093 }
1094 
1095 int
1096 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
1097 {
1098  int format = 1;
1099  int rc = pcmk_ok;
1100  xmlNode *old = NULL;
1101  const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1102 
1103  if (patchset == NULL) {
1104  return rc;
1105  }
1106 
1108  {
1109  pcmk__output_t *logger_out = NULL;
1110 
1111  rc = pcmk_rc2legacy(pcmk__log_output_new(&logger_out));
1112  CRM_CHECK(rc == pcmk_ok, return rc);
1113 
1115  rc = logger_out->message(logger_out, "xml-patchset", patchset);
1116  logger_out->finish(logger_out, pcmk_rc2exitc(rc), true,
1117  NULL);
1118  pcmk__output_free(logger_out);
1119  rc = pcmk_ok;
1120  },
1121  {}
1122  );
1123 
1124  crm_element_value_int(patchset, "format", &format);
1125  if (check_version) {
1126  rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format));
1127  if (rc != pcmk_ok) {
1128  return rc;
1129  }
1130  }
1131 
1132  if (digest) {
1133  // Make it available for logging if result doesn't have expected digest
1134  old = copy_xml(xml);
1135  }
1136 
1137  if (rc == pcmk_ok) {
1138  switch (format) {
1139  case 1:
1140  rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1141  break;
1142  case 2:
1143  rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1144  break;
1145  default:
1146  crm_err("Unknown patch format: %d", format);
1147  rc = -EINVAL;
1148  }
1149  }
1150 
1151  if ((rc == pcmk_ok) && (digest != NULL)) {
1152  char *new_digest = NULL;
1154 
1155  new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1156  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1157  crm_info("v%d digest mis-match: expected %s, calculated %s",
1158  format, digest, new_digest);
1159  rc = -pcmk_err_diff_failed;
1161  {
1162  save_xml_to_file(old, "PatchDigest:input", NULL);
1163  save_xml_to_file(xml, "PatchDigest:result", NULL);
1164  save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1165  },
1166  {}
1167  );
1168 
1169  } else {
1170  crm_trace("v%d digest matched: expected %s, calculated %s",
1171  format, digest, new_digest);
1172  }
1173  free(new_digest);
1174  free(version);
1175  }
1176  free_xml(old);
1177  return rc;
1178 }
1179 
1180 void
1181 purge_diff_markers(xmlNode *a_node)
1182 {
1183  xmlNode *child = NULL;
1184 
1185  CRM_CHECK(a_node != NULL, return);
1186 
1188  for (child = pcmk__xml_first_child(a_node); child != NULL;
1189  child = pcmk__xml_next(child)) {
1190  purge_diff_markers(child);
1191  }
1192 }
1193 
1194 xmlNode *
1195 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
1196 {
1197  xmlNode *tmp1 = NULL;
1198  xmlNode *diff = create_xml_node(NULL, "diff");
1199  xmlNode *removed = create_xml_node(diff, "diff-removed");
1200  xmlNode *added = create_xml_node(diff, "diff-added");
1201 
1203 
1204  tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
1205  if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1206  free_xml(tmp1);
1207  }
1208 
1209  tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
1210  if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
1211  free_xml(tmp1);
1212  }
1213 
1214  if ((added->children == NULL) && (removed->children == NULL)) {
1215  free_xml(diff);
1216  diff = NULL;
1217  }
1218 
1219  return diff;
1220 }
1221 
1222 static xmlNode *
1223 subtract_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right,
1224  gboolean *changed)
1225 {
1226  CRM_CHECK(left != NULL, return NULL);
1227  CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
1228 
1229  if ((right == NULL) || !pcmk__str_eq((const char *)left->content,
1230  (const char *)right->content,
1231  pcmk__str_casei)) {
1232  xmlNode *deleted = NULL;
1233 
1234  deleted = add_node_copy(parent, left);
1235  *changed = TRUE;
1236 
1237  return deleted;
1238  }
1239 
1240  return NULL;
1241 }
1242 
1243 xmlNode *
1244 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
1245  gboolean full, gboolean *changed, const char *marker)
1246 {
1247  gboolean dummy = FALSE;
1248  xmlNode *diff = NULL;
1249  xmlNode *right_child = NULL;
1250  xmlNode *left_child = NULL;
1251  xmlAttrPtr xIter = NULL;
1252 
1253  const char *id = NULL;
1254  const char *name = NULL;
1255  const char *value = NULL;
1256  const char *right_val = NULL;
1257 
1258  if (changed == NULL) {
1259  changed = &dummy;
1260  }
1261 
1262  if (left == NULL) {
1263  return NULL;
1264  }
1265 
1266  if (left->type == XML_COMMENT_NODE) {
1267  return subtract_xml_comment(parent, left, right, changed);
1268  }
1269 
1270  id = ID(left);
1271  if (right == NULL) {
1272  xmlNode *deleted = NULL;
1273 
1274  crm_trace("Processing <%s " XML_ATTR_ID "=%s> (complete copy)",
1275  crm_element_name(left), id);
1276  deleted = add_node_copy(parent, left);
1277  crm_xml_add(deleted, XML_DIFF_MARKER, marker);
1278 
1279  *changed = TRUE;
1280  return deleted;
1281  }
1282 
1283  name = crm_element_name(left);
1284  CRM_CHECK(name != NULL, return NULL);
1285  CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right),
1286  pcmk__str_casei),
1287  return NULL);
1288 
1289  // Check for XML_DIFF_MARKER in a child
1290  value = crm_element_value(right, XML_DIFF_MARKER);
1291  if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
1292  crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1293  *changed = TRUE;
1294  return NULL;
1295  }
1296 
1297  // @TODO Avoiding creating the full hierarchy would save work here
1298  diff = create_xml_node(parent, name);
1299 
1300  // Changes to child objects
1301  for (left_child = pcmk__xml_first_child(left); left_child != NULL;
1302  left_child = pcmk__xml_next(left_child)) {
1303  gboolean child_changed = FALSE;
1304 
1305  right_child = pcmk__xml_match(right, left_child, false);
1306  subtract_xml_object(diff, left_child, right_child, full, &child_changed,
1307  marker);
1308  if (child_changed) {
1309  *changed = TRUE;
1310  }
1311  }
1312 
1313  if (!*changed) {
1314  /* Nothing to do */
1315 
1316  } else if (full) {
1317  xmlAttrPtr pIter = NULL;
1318 
1319  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1320  pIter = pIter->next) {
1321  const char *p_name = (const char *)pIter->name;
1322  const char *p_value = pcmk__xml_attr_value(pIter);
1323 
1324  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1325  }
1326 
1327  // We have everything we need
1328  goto done;
1329  }
1330 
1331  // Changes to name/value pairs
1332  for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
1333  xIter = xIter->next) {
1334  const char *prop_name = (const char *) xIter->name;
1335  xmlAttrPtr right_attr = NULL;
1336  xml_node_private_t *nodepriv = NULL;
1337 
1338  if (strcmp(prop_name, XML_ATTR_ID) == 0) {
1339  // id already obtained when present ~ this case, so just reuse
1340  xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_ID, (pcmkXmlStr) id);
1341  continue;
1342  }
1343 
1344  if (pcmk__xa_filterable(prop_name)) {
1345  continue;
1346  }
1347 
1348  right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
1349  if (right_attr) {
1350  nodepriv = right_attr->_private;
1351  }
1352 
1353  right_val = crm_element_value(right, prop_name);
1354  if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) {
1355  /* new */
1356  *changed = TRUE;
1357  if (full) {
1358  xmlAttrPtr pIter = NULL;
1359 
1360  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1361  pIter = pIter->next) {
1362  const char *p_name = (const char *) pIter->name;
1363  const char *p_value = pcmk__xml_attr_value(pIter);
1364 
1365  xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
1366  }
1367  break;
1368 
1369  } else {
1370  const char *left_value = crm_element_value(left, prop_name);
1371 
1372  xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
1373  crm_xml_add(diff, prop_name, left_value);
1374  }
1375 
1376  } else {
1377  /* Only now do we need the left value */
1378  const char *left_value = crm_element_value(left, prop_name);
1379 
1380  if (strcmp(left_value, right_val) == 0) {
1381  /* unchanged */
1382 
1383  } else {
1384  *changed = TRUE;
1385  if (full) {
1386  xmlAttrPtr pIter = NULL;
1387 
1388  crm_trace("Changes detected to %s in "
1389  "<%s " XML_ATTR_ID "=%s>",
1390  prop_name, crm_element_name(left), id);
1391  for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
1392  pIter = pIter->next) {
1393  const char *p_name = (const char *) pIter->name;
1394  const char *p_value = pcmk__xml_attr_value(pIter);
1395 
1396  xmlSetProp(diff, (pcmkXmlStr) p_name,
1397  (pcmkXmlStr) p_value);
1398  }
1399  break;
1400 
1401  } else {
1402  crm_trace("Changes detected to %s (%s -> %s) in "
1403  "<%s " XML_ATTR_ID "=%s>",
1404  prop_name, left_value, right_val,
1405  crm_element_name(left), id);
1406  crm_xml_add(diff, prop_name, left_value);
1407  }
1408  }
1409  }
1410  }
1411 
1412  if (!*changed) {
1413  free_xml(diff);
1414  return NULL;
1415 
1416  } else if (!full && (id != NULL)) {
1417  crm_xml_add(diff, XML_ATTR_ID, id);
1418  }
1419  done:
1420  return diff;
1421 }
1422 
1423 // Deprecated functions kept only for backward API compatibility
1424 // LCOV_EXCL_START
1425 
1426 #include <crm/common/xml_compat.h>
1427 
1428 gboolean
1429 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
1430 {
1431  gboolean result = TRUE;
1432  int root_nodes_seen = 0;
1433  const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
1434  const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
1435 
1436  xmlNode *child_diff = NULL;
1437  xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
1438  xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
1439 
1440  CRM_CHECK(new_xml != NULL, return FALSE);
1441 
1442  crm_trace("Subtraction Phase");
1443  for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1444  child_diff = pcmk__xml_next(child_diff)) {
1445  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1446  if (root_nodes_seen == 0) {
1447  *new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
1448  NULL, NULL);
1449  }
1450  root_nodes_seen++;
1451  }
1452 
1453  if (root_nodes_seen == 0) {
1454  *new_xml = copy_xml(old_xml);
1455 
1456  } else if (root_nodes_seen > 1) {
1457  crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1458  root_nodes_seen);
1459  result = FALSE;
1460  }
1461 
1462  root_nodes_seen = 0;
1463  crm_trace("Addition Phase");
1464  if (result) {
1465  xmlNode *child_diff = NULL;
1466 
1467  for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1468  child_diff = pcmk__xml_next(child_diff)) {
1469  CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1470  if (root_nodes_seen == 0) {
1471  pcmk__xml_update(NULL, *new_xml, child_diff, true);
1472  }
1473  root_nodes_seen++;
1474  }
1475  }
1476 
1477  if (root_nodes_seen > 1) {
1478  crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1479  root_nodes_seen);
1480  result = FALSE;
1481 
1482  } else if (result && (digest != NULL)) {
1483  char *new_digest = NULL;
1484 
1485  purge_diff_markers(*new_xml); // Purge now so diff is ok
1486  new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1487  version);
1488  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1489  crm_info("Digest mis-match: expected %s, calculated %s",
1490  digest, new_digest);
1491  result = FALSE;
1492 
1494  {
1495  save_xml_to_file(old_xml, "diff:original", NULL);
1496  save_xml_to_file(diff, "diff:input", NULL);
1497  save_xml_to_file(*new_xml, "diff:new", NULL);
1498  },
1499  {}
1500  );
1501 
1502  } else {
1503  crm_trace("Digest matched: expected %s, calculated %s",
1504  digest, new_digest);
1505  }
1506  free(new_digest);
1507 
1508  } else if (result) {
1509  purge_diff_markers(*new_xml); // Purge now so diff is ok
1510  }
1511 
1512  return result;
1513 }
1514 
1515 // LCOV_EXCL_STOP
1516 // End deprecated API
#define LOG_TRACE
Definition: logging.h:37
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:235
#define XML_DIFF_RESULT
Definition: msg_xml.h:475
A dumping ground.
#define crm_notice(fmt, args...)
Definition: logging.h:379
#define XML_TAG_DIFF
Definition: msg_xml.h:468
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
Definition: patchset.c:376
#define pcmk__if_tracing(if_action, else_action)
#define XML_ATTR_NUMUPDATES
Definition: msg_xml.h:140
int pcmk_rc2legacy(int rc)
Definition: results.c:533
const char * name
Definition: cib.c:24
int(* message)(pcmk__output_t *out, const char *message_id,...)
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:2521
G_GNUC_INTERNAL int pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:309
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:398
#define CRM_FEATURE_SET
Definition: crm.h:69
xmlNode * find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
Definition: xml.c:404
crm_exit_t pcmk_rc2exitc(int rc)
Map a function return code to the most similar exit code.
Definition: results.c:689
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:302
#define XML_NVPAIR_ATTR_NAME
Definition: msg_xml.h:403
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:219
void pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
Definition: output_log.c:345
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: nvpair.c:532
Deprecated Pacemaker XML API.
#define XML_ATTR_GENERATION
Definition: msg_xml.h:138
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:819
#define crm_warn(fmt, args...)
Definition: logging.h:378
#define pcmk_err_diff_failed
Definition: results.h:76
G_GNUC_INTERNAL void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
Definition: xml.c:2209
#define XML_DIFF_OP
Definition: msg_xml.h:476
#define crm_debug(fmt, args...)
Definition: logging.h:382
#define XML_DIFF_ATTR
Definition: msg_xml.h:474
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:693
#define XML_DIFF_VERSION
Definition: msg_xml.h:469
#define XML_ATTR_ID
Definition: msg_xml.h:147
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:496
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml.c:618
#define crm_trace(fmt, args...)
Definition: logging.h:383
#define crm_log_xml_explicit(xml, text)
Definition: logging.h:393
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:121
xmlNode * add_node_copy(xmlNode *new_parent, xmlNode *xml_node)
Definition: xml.c:663
Wrappers for and extensions to libxml2.
void(* finish)(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest)
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:292
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:677
#define PCMK__NELEM(a)
Definition: internal.h:44
#define XML_DIFF_POSITION
Definition: msg_xml.h:478
void purge_diff_markers(xmlNode *a_node)
Definition: patchset.c:1181
void free_xml(xmlNode *child)
Definition: xml.c:813
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition: patchset.c:319
G_GNUC_INTERNAL xmlNode * pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
Definition: xml.c:363
void pcmk__output_free(pcmk__output_t *out)
Definition: output.c:28
const xmlChar * pcmkXmlStr
Definition: xml.h:50
G_GNUC_INTERNAL bool pcmk__xa_filterable(const char *name)
Definition: digest.c:234
#define XML_DIFF_VSOURCE
Definition: msg_xml.h:470
const char * target
Definition: pcmk_fence.c:29
#define XML_TAG_CIB
Definition: msg_xml.h:128
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
Definition: patchset.c:1429
#define XML_DIFF_CHANGE
Definition: msg_xml.h:472
#define XML_DIFF_PATH
Definition: msg_xml.h:477
#define XML_DIFF_VTARGET
Definition: msg_xml.h:471
#define XML_DIFF_LIST
Definition: msg_xml.h:473
xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
Definition: patchset.c:1195
pcmk__action_result_t result
Definition: pcmk_fence.c:35
#define XML_DIFF_MARKER
Definition: msg_xml.h:127
const char * path
Definition: cib.c:26
#define crm_err(fmt, args...)
Definition: logging.h:377
#define CRM_ASSERT(expr)
Definition: results.h:42
#define ENOTUNIQ
Definition: portability.h:120
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:1735
pe_resource_t * dummy
char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version)
Calculate and return digest of XML tree.
Definition: digest.c:167
gboolean can_prune_leaf(xmlNode *xml_node)
Definition: xml.c:2117
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:619
This structure contains everything that makes up a single output formatter.
int compare_version(const char *version1, const char *version2)
Definition: utils.c:189
#define crm_log_xml_info(xml, text)
Definition: logging.h:389
#define XML_ATTR_GENERATION_ADMIN
Definition: msg_xml.h:139
#define XML_NVPAIR_ATTR_VALUE
Definition: msg_xml.h:404
#define XML_ATTR_CRM_VERSION
Definition: msg_xml.h:131
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:572
void save_xml_to_file(xmlNode *xml, const char *desc, const char *filename)
Definition: xml.c:1753
#define pcmk_ok
Definition: results.h:68
GString * pcmk__element_xpath(const xmlNode *xml)
Definition: xpath.c:281
#define XML_CIB_TAG_CONFIGURATION
Definition: msg_xml.h:197
#define ID(x)
Definition: msg_xml.h:480
xmlNode * subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean full, gboolean *changed, const char *marker)
Definition: patchset.c:1244
const char * parent
Definition: cib.c:25
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:1096
G_GNUC_INTERNAL void pcmk__mark_xml_created(xmlNode *xml)
Definition: xml.c:114
G_GNUC_INTERNAL void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff)
Definition: xml.c:2240
#define crm_info(fmt, args...)
Definition: logging.h:380
uint32_t version
Definition: remote.c:213
int pcmk__log_output_new(pcmk__output_t **out)
Definition: output.c:272
#define XML_ATTR_DIGEST
Definition: msg_xml.h:132