pacemaker  3.0.0-d8340737c4
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>
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 /* Add changes for specified XML to patchset.
30  * For patchset format, refer to diff schema.
31  */
32 static void
33 add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
34 {
35  xmlNode *cIter = NULL;
36  xmlAttr *pIter = NULL;
37  xmlNode *change = NULL;
38  xml_node_private_t *nodepriv = xml->_private;
39  const char *value = NULL;
40 
41  if (nodepriv == NULL) {
42  /* Elements that shouldn't occur in a CIB don't have _private set. They
43  * should be stripped out, ignored, or have an error thrown by any code
44  * that processes their parent, so we ignore any changes to them.
45  */
46  return;
47  }
48 
49  // If this XML node is new, just report that
50  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
51  GString *xpath = pcmk__element_xpath(xml->parent);
52 
53  if (xpath != NULL) {
54  int position = pcmk__xml_position(xml, pcmk__xf_deleted);
55 
56  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
57 
59  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
60  crm_xml_add_int(change, PCMK_XE_POSITION, position);
61  pcmk__xml_copy(change, xml);
62  g_string_free(xpath, TRUE);
63  }
64 
65  return;
66  }
67 
68  // Check each of the XML node's attributes for changes
69  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
70  pIter = pIter->next) {
71  xmlNode *attr = NULL;
72 
73  nodepriv = pIter->_private;
74  if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
75  continue;
76  }
77 
78  if (change == NULL) {
79  GString *xpath = pcmk__element_xpath(xml);
80 
81  if (xpath != NULL) {
82  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
83 
85  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
86 
87  change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
88  g_string_free(xpath, TRUE);
89  }
90  }
91 
92  attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR);
93 
94  crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
95  if (nodepriv->flags & pcmk__xf_deleted) {
96  crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
97 
98  } else {
99  crm_xml_add(attr, PCMK_XA_OPERATION, "set");
100 
101  value = pcmk__xml_attr_value(pIter);
102  crm_xml_add(attr, PCMK_XA_VALUE, value);
103  }
104  }
105 
106  if (change) {
107  xmlNode *result = NULL;
108 
109  change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
110  result = pcmk__xe_create(change, (const char *)xml->name);
111 
112  for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
113  pIter = pIter->next) {
114  nodepriv = pIter->_private;
115  if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
116  value = crm_element_value(xml, (const char *) pIter->name);
117  crm_xml_add(result, (const char *)pIter->name, value);
118  }
119  }
120  }
121 
122  // Now recursively do the same for each child node of this node
123  for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
124  cIter = pcmk__xml_next(cIter)) {
125  add_xml_changes_to_patchset(cIter, patchset);
126  }
127 
128  nodepriv = xml->_private;
129  if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
130  GString *xpath = pcmk__element_xpath(xml);
131 
132  crm_trace("%s.%s moved to position %d",
133  xml->name, pcmk__xe_id(xml),
135 
136  if (xpath != NULL) {
137  change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
138 
140  crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
143  g_string_free(xpath, TRUE);
144  }
145  }
146 }
147 
148 static bool
149 is_config_change(xmlNode *xml)
150 {
151  GList *gIter = NULL;
152  xml_node_private_t *nodepriv = NULL;
153  xml_doc_private_t *docpriv;
154  xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
155  NULL);
156 
157  if (config) {
158  nodepriv = config->_private;
159  }
160  if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
161  return TRUE;
162  }
163 
164  if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
165  docpriv = xml->doc->_private;
166  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
167  pcmk__deleted_xml_t *deleted_obj = gIter->data;
168 
169  if (strstr(deleted_obj->path,
170  "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
171  return TRUE;
172  }
173  }
174  }
175  return FALSE;
176 }
177 
178 static xmlNode *
179 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
180 {
181  int lpc = 0;
182  GList *gIter = NULL;
183  xml_doc_private_t *docpriv;
184 
185  xmlNode *v = NULL;
186  xmlNode *version = NULL;
187  xmlNode *patchset = NULL;
188  const char *vfields[] = {
192  };
193 
194  pcmk__assert(target != NULL);
195  if (!xml_document_dirty(target)) {
196  return NULL;
197  }
198 
199  pcmk__assert(target->doc != NULL);
200  docpriv = target->doc->_private;
201 
202  patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
203  crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
204 
206 
208  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
209  const char *value = crm_element_value(source, vfields[lpc]);
210 
211  if (value == NULL) {
212  value = "1";
213  }
214  crm_xml_add(v, vfields[lpc], value);
215  }
216 
218  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
219  const char *value = crm_element_value(target, vfields[lpc]);
220 
221  if (value == NULL) {
222  value = "1";
223  }
224  crm_xml_add(v, vfields[lpc], value);
225  }
226 
227  for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
228  pcmk__deleted_xml_t *deleted_obj = gIter->data;
229  xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
230 
232  crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
233  if (deleted_obj->position >= 0) {
234  crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
235  }
236  }
237 
238  add_xml_changes_to_patchset(target, patchset);
239  return patchset;
240 }
241 
242 xmlNode *
243 xml_create_patchset(int format, xmlNode *source, xmlNode *target,
244  bool *config_changed, bool manage_version)
245 {
246  bool local_config_changed = false;
247 
248  if (format == 0) {
249  format = 2;
250  }
251  if (format != 2) {
252  crm_err("Unknown patch format: %d", format);
253  return NULL;
254  }
255 
257  if (!xml_document_dirty(target)) {
258  crm_trace("No change %d", format);
259  return NULL; /* No change */
260  }
261 
262  if (config_changed == NULL) {
263  config_changed = &local_config_changed;
264  }
265  *config_changed = is_config_change(target);
266 
267  if (manage_version) {
268  int counter = 0;
269 
270  if (*config_changed) {
272 
274  crm_xml_add_int(target, PCMK_XA_EPOCH, counter + 1);
275 
276  } else {
279  }
280  }
281 
282  return xml_create_patchset_v2(source, target);
283 }
284 
285 void
286 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
287  bool with_digest)
288 {
289  char *digest = NULL;
290 
291  if ((patch == NULL) || (source == NULL) || (target == NULL)
292  || !with_digest) {
293  return;
294  }
295 
296  /* We should always call xml_accept_changes() before calculating a digest.
297  * Otherwise, with an on-tracking dirty target, we could get a wrong digest.
298  */
300 
301  digest = pcmk__digest_xml(target, true);
302 
303  crm_xml_add(patch, PCMK__XA_DIGEST, digest);
304  free(digest);
305 
306  return;
307 }
308 
309 // Get CIB versions used for additions and deletions in a patchset
310 bool
311 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
312 {
313  static const char *const vfields[] = {
317  };
318 
319  const xmlNode *version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION,
320  NULL, NULL);
321  const xmlNode *source = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL,
322  NULL);
323  const xmlNode *target = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL,
324  NULL);
325  int format = 1;
326 
327  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
328  if (format != 2) {
329  crm_err("Unknown patch format: %d", format);
330  return -EINVAL;
331  }
332 
333  if (source != NULL) {
334  for (int i = 0; i < PCMK__NELEM(vfields); i++) {
335  crm_element_value_int(source, vfields[i], &(del[i]));
336  crm_trace("Got %d for del[%s]", del[i], vfields[i]);
337  }
338  }
339 
340  if (target != NULL) {
341  for (int i = 0; i < PCMK__NELEM(vfields); i++) {
342  crm_element_value_int(target, vfields[i], &(add[i]));
343  crm_trace("Got %d for add[%s]", add[i], vfields[i]);
344  }
345  }
346  return pcmk_ok;
347 }
348 
358 static int
359 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
360 {
361  int lpc = 0;
362  bool changed = FALSE;
363 
364  int this[] = { 0, 0, 0 };
365  int add[] = { 0, 0, 0 };
366  int del[] = { 0, 0, 0 };
367 
368  const char *vfields[] = {
372  };
373 
374  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
375  crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
376  crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
377  if (this[lpc] < 0) {
378  this[lpc] = 0;
379  }
380  }
381 
382  /* Set some defaults in case nothing is present */
383  add[0] = this[0];
384  add[1] = this[1];
385  add[2] = this[2] + 1;
386  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
387  del[lpc] = this[lpc];
388  }
389 
390  xml_patch_versions(patchset, add, del);
391 
392  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
393  if (this[lpc] < del[lpc]) {
394  crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
395  vfields[lpc], this[0], this[1], this[2],
396  del[0], del[1], del[2], add[0], add[1], add[2]);
397  return pcmk_rc_diff_resync;
398 
399  } else if (this[lpc] > del[lpc]) {
400  crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
401  vfields[lpc], this[0], this[1], this[2],
402  del[0], del[1], del[2], add[0], add[1], add[2], patchset);
403  crm_log_xml_info(patchset, "OldPatch");
404  return pcmk_rc_old_data;
405  }
406  }
407 
408  for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
409  if (add[lpc] > del[lpc]) {
410  changed = TRUE;
411  }
412  }
413 
414  if (!changed) {
415  crm_notice("Versions did not change in patch %d.%d.%d",
416  add[0], add[1], add[2]);
417  return pcmk_rc_old_data;
418  }
419 
420  crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
421  add[0], add[1], add[2], this[0], this[1], this[2]);
422  return pcmk_rc_ok;
423 }
424 
425 // Return first child matching element name and optionally id or position
426 static xmlNode *
427 first_matching_xml_child(const xmlNode *parent, const char *name,
428  const char *id, int position)
429 {
430  xmlNode *cIter = NULL;
431 
432  for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
433  cIter = pcmk__xml_next(cIter)) {
434  if (strcmp((const char *) cIter->name, name) != 0) {
435  continue;
436  } else if (id) {
437  const char *cid = pcmk__xe_id(cIter);
438 
439  if ((cid == NULL) || (strcmp(cid, id) != 0)) {
440  continue;
441  }
442  }
443 
444  // "position" makes sense only for XML comments for now
445  if ((cIter->type == XML_COMMENT_NODE)
446  && (position >= 0)
447  && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
448  continue;
449  }
450 
451  return cIter;
452  }
453  return NULL;
454 }
455 
469 static xmlNode *
470 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
471 {
472  xmlNode *target = (xmlNode *) top->doc;
473  const char *current = key;
474  char *section;
475  char *remainder;
476  char *id;
477  char *tag;
478  char *path = NULL;
479  int rc;
480  size_t key_len;
481 
482  CRM_CHECK(key != NULL, return NULL);
483  key_len = strlen(key);
484 
485  /* These are scanned from key after a slash, so they can't be bigger
486  * than key_len - 1 characters plus a null terminator.
487  */
488 
489  remainder = pcmk__assert_alloc(key_len, sizeof(char));
490  section = pcmk__assert_alloc(key_len, sizeof(char));
491  id = pcmk__assert_alloc(key_len, sizeof(char));
492  tag = pcmk__assert_alloc(key_len, sizeof(char));
493 
494  do {
495  // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
496  rc = sscanf(current, "/%[^/]%s", section, remainder);
497  if (rc > 0) {
498  // Separate FIRST_COMPONENT into TAG[@id='ID']
499  int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
500  int current_position = -1;
501 
502  /* The target position is for the final component tag, so only use
503  * it if there is nothing left to search after this component.
504  */
505  if ((rc == 1) && (target_position >= 0)) {
506  current_position = target_position;
507  }
508 
509  switch (f) {
510  case 1:
511  target = first_matching_xml_child(target, tag, NULL,
512  current_position);
513  break;
514  case 2:
515  target = first_matching_xml_child(target, tag, id,
516  current_position);
517  break;
518  default:
519  // This should not be possible
520  target = NULL;
521  break;
522  }
523  current = remainder;
524  }
525 
526  // Continue if something remains to search, and we've matched so far
527  } while ((rc == 2) && target);
528 
529  if (target) {
530  crm_trace("Found %s for %s",
531  (path = (char *) xmlGetNodePath(target)), key);
532  free(path);
533  } else {
534  crm_debug("No match for %s", key);
535  }
536 
537  free(remainder);
538  free(section);
539  free(tag);
540  free(id);
541  return target;
542 }
543 
544 typedef struct xml_change_obj_s {
545  const xmlNode *change;
546  xmlNode *match;
548 
549 static gint
550 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
551 {
552  const xml_change_obj_t *change_obj_a = a;
553  const xml_change_obj_t *change_obj_b = b;
554  int position_a = -1;
555  int position_b = -1;
556 
557  crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
558  crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
559 
560  if (position_a < position_b) {
561  return -1;
562 
563  } else if (position_a > position_b) {
564  return 1;
565  }
566 
567  return 0;
568 }
569 
579 static int
580 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
581 {
582  int rc = pcmk_rc_ok;
583  const xmlNode *change = NULL;
584  GList *change_objs = NULL;
585  GList *gIter = NULL;
586 
587  for (change = pcmk__xml_first_child(patchset); change != NULL;
588  change = pcmk__xml_next(change)) {
589  xmlNode *match = NULL;
590  const char *op = crm_element_value(change, PCMK_XA_OPERATION);
591  const char *xpath = crm_element_value(change, PCMK_XA_PATH);
592  int position = -1;
593 
594  if (op == NULL) {
595  continue;
596  }
597 
598  crm_trace("Processing %s %s", change->name, op);
599 
600  /* PCMK_VALUE_DELETE changes for XML comments are generated with
601  * PCMK_XE_POSITION
602  */
603  if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
604  crm_element_value_int(change, PCMK_XE_POSITION, &position);
605  }
606  match = search_v2_xpath(xml, xpath, position);
607  crm_trace("Performing %s on %s with %p", op, xpath, match);
608 
609  if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
610  crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
611  continue;
612 
613  } else if (match == NULL) {
614  crm_err("No %s match for %s in %p", op, xpath, xml->doc);
615  rc = pcmk_rc_diff_failed;
616  continue;
617 
618  } else if (pcmk__str_any_of(op,
620  // Delay the adding of a PCMK_VALUE_CREATE object
621  xml_change_obj_t *change_obj =
623 
624  change_obj->change = change;
625  change_obj->match = match;
626 
627  change_objs = g_list_append(change_objs, change_obj);
628 
629  if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
630  // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
631  if ((match->parent != NULL) && (match->parent->last != NULL)) {
632  xmlAddNextSibling(match->parent->last, match);
633  }
634  }
635 
636  } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
637  pcmk__xml_free(match);
638 
639  } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
640  const xmlNode *child = pcmk__xe_first_child(change,
642  NULL, NULL);
643  const xmlNode *attrs = pcmk__xml_first_child(child);
644 
645  if (attrs == NULL) {
646  rc = ENOMSG;
647  continue;
648  }
649  pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
650 
651  for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
652  pIter = pIter->next) {
653  const char *name = (const char *) pIter->name;
654  const char *value = pcmk__xml_attr_value(pIter);
655 
656  crm_xml_add(match, name, value);
657  }
658 
659  } else {
660  crm_err("Unknown operation: %s", op);
661  rc = pcmk_rc_diff_failed;
662  }
663  }
664 
665  // Changes should be generated in the right order. Double checking.
666  change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
667 
668  for (gIter = change_objs; gIter; gIter = gIter->next) {
669  xml_change_obj_t *change_obj = gIter->data;
670  xmlNode *match = change_obj->match;
671  const char *op = NULL;
672  const char *xpath = NULL;
673 
674  change = change_obj->change;
675 
676  op = crm_element_value(change, PCMK_XA_OPERATION);
677  xpath = crm_element_value(change, PCMK_XA_PATH);
678 
679  crm_trace("Continue performing %s on %s with %p", op, xpath, match);
680 
681  if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
682  int position = 0;
683  xmlNode *child = NULL;
684  xmlNode *match_child = NULL;
685 
686  match_child = match->children;
687  crm_element_value_int(change, PCMK_XE_POSITION, &position);
688 
689  while ((match_child != NULL)
690  && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
691  match_child = match_child->next;
692  }
693 
694  child = pcmk__xml_copy(match, change->children);
695 
696  if (match_child != NULL) {
697  crm_trace("Adding %s at position %d", child->name, position);
698  xmlAddPrevSibling(match_child, child);
699 
700  } else {
701  crm_trace("Adding %s at position %d (end)",
702  child->name, position);
703  }
704 
705  } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
706  int position = 0;
707 
708  crm_element_value_int(change, PCMK_XE_POSITION, &position);
709  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
710  xmlNode *match_child = NULL;
711  int p = position;
712 
713  if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
714  p++; // Skip ourselves
715  }
716 
717  pcmk__assert(match->parent != NULL);
718  match_child = match->parent->children;
719 
720  while ((match_child != NULL)
721  && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
722  match_child = match_child->next;
723  }
724 
725  crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
726  match->name, position,
728  match->prev, (match_child? "next":"last"),
729  (match_child? match_child : match->parent->last));
730 
731  if (match_child) {
732  xmlAddPrevSibling(match_child, match);
733 
734  } else {
735  pcmk__assert(match->parent->last != NULL);
736  xmlAddNextSibling(match->parent->last, match);
737  }
738 
739  } else {
740  crm_trace("%s is already in position %d",
741  match->name, position);
742  }
743 
744  if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
745  crm_err("Moved %s.%s to position %d instead of %d (%p)",
746  match->name, pcmk__xe_id(match),
748  position, match->prev);
749  rc = pcmk_rc_diff_failed;
750  }
751  }
752  }
753 
754  g_list_free_full(change_objs, free);
755  return rc;
756 }
757 
758 int
759 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
760 {
761  int format = 1;
762  int rc = pcmk_ok;
763  xmlNode *old = NULL;
764  const char *digest = NULL;
765 
766  if (patchset == NULL) {
767  return rc;
768  }
769 
771 
772  if (check_version) {
773  rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
774  if (rc != pcmk_ok) {
775  return rc;
776  }
777  }
778 
779  digest = crm_element_value(patchset, PCMK__XA_DIGEST);
780  if (digest != NULL) {
781  /* Make original XML available for logging in case result doesn't have
782  * expected digest
783  */
784  pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
785  }
786 
787  if (rc == pcmk_ok) {
788  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
789 
790  if (format != 2) {
791  crm_err("Unknown patch format: %d", format);
792  rc = -EINVAL;
793 
794  } else {
795  rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
796  }
797  }
798 
799  if ((rc == pcmk_ok) && (digest != NULL)) {
800  char *new_digest = NULL;
801 
802  new_digest = pcmk__digest_xml(xml, true);
803  if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
804  crm_info("v%d digest mis-match: expected %s, calculated %s",
805  format, digest, new_digest);
806  rc = -pcmk_err_diff_failed;
808  {
809  save_xml_to_file(old, "PatchDigest:input", NULL);
810  save_xml_to_file(xml, "PatchDigest:result", NULL);
811  save_xml_to_file(patchset, "PatchDigest:diff", NULL);
812  },
813  {}
814  );
815 
816  } else {
817  crm_trace("v%d digest matched: expected %s, calculated %s",
818  format, digest, new_digest);
819  }
820  free(new_digest);
821  }
822  pcmk__xml_free(old);
823  return rc;
824 }
825 
826 bool
827 pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
828 {
829  const char *element_xpath = pcmk__cib_abs_xpath_for(element);
830  const char *parent_xpath = pcmk_cib_parent_name_for(element);
831  char *element_regex = NULL;
832  bool rc = false;
833  int format = 1;
834 
835  pcmk__assert(patchset != NULL);
836 
837  crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
838  if (format != 2) {
839  crm_warn("Unknown patch format: %d", format);
840  return false;
841  }
842 
843  CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
844 
845  /* Matches if and only if element_xpath is part of a changed path
846  * (supported values for element never contain XML IDs with schema
847  * validation enabled)
848  *
849  * @TODO Use POSIX word boundary instead of (/|$), if it works:
850  * https://www.regular-expressions.info/wordboundaries.html.
851  */
852  element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
853 
854  for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
855  NULL, NULL);
856  change != NULL; change = pcmk__xe_next(change, PCMK_XE_CHANGE)) {
857 
858  const char *op = crm_element_value(change, PCMK_XA_OPERATION);
859  const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
860 
861  if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
862  // Change to an existing element
863  rc = true;
864  break;
865  }
866 
867  if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
868  && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
869  && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
870  element)) {
871  // Newly added element
872  rc = true;
873  break;
874  }
875  }
876 
877  free(element_regex);
878  return rc;
879 }
#define LOG_TRACE
Definition: logging.h:38
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
#define PCMK__XA_DIGEST
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
A dumping ground.
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml_element.c:42
#define crm_notice(fmt, args...)
Definition: logging.h:365
#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:286
#define pcmk__if_tracing(if_action, else_action)
bool pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
Definition: patchset.c:827
int pcmk_rc2legacy(int rc)
Definition: results.c:654
#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:645
#define PCMK_XE_CHANGE_ATTR
Definition: xml_names.h:75
#define PCMK_VALUE_CREATE
Definition: options.h:140
G_GNUC_INTERNAL int pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
Definition: xml.c:385
#define PCMK_XA_NUM_UPDATES
Definition: xml_names.h:341
#define PCMK_XE_CHANGE_LIST
Definition: xml_names.h:76
#define PCMK_XA_FORMAT
Definition: xml_names.h:291
#define CRM_LOG_ASSERT(expr)
Definition: logging.h:196
char * pcmk__digest_xml(xmlNode *input, bool filter)
Definition: digest.c:160
#define PCMK_XA_OPERATION
Definition: xml_names.h:349
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml_element.c:407
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
#define crm_warn(fmt, args...)
Definition: logging.h:362
#define pcmk_err_diff_failed
Definition: results.h:76
#define PCMK_XE_VERSION
Definition: xml_names.h:220
#define PCMK_VALUE_MOVE
Definition: options.h:176
#define crm_debug(fmt, args...)
Definition: logging.h:370
struct xml_change_obj_s xml_change_obj_t
#define PCMK_VALUE_MODIFY
Definition: options.h:175
#define PCMK_XE_CHANGE
Definition: xml_names.h:74
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define PCMK_XE_CHANGE_RESULT
Definition: xml_names.h:77
#define crm_trace(fmt, args...)
Definition: logging.h:372
#define PCMK_VALUE_DELETE
Definition: options.h:145
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
#define PCMK_XE_TARGET
Definition: xml_names.h:210
#define PCMK_XA_EPOCH
Definition: xml_names.h:268
Wrappers for and extensions to libxml2.
bool xml_document_dirty(xmlNode *xml)
Definition: xml.c:368
#define PCMK__NELEM(a)
Definition: internal.h:49
const char * pcmk_cib_parent_name_for(const char *element_name)
Get the parent element name of a given CIB element name.
Definition: cib.c:150
#define PCMK_XA_ID
Definition: xml_names.h:301
#define PCMK_XA_VALUE
Definition: xml_names.h:442
#define PCMK_XE_CONFIGURATION
Definition: xml_names.h:87
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1051
xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
Definition: patchset.c:243
#define PCMK_XE_SOURCE
Definition: xml_names.h:200
const char * pcmk__cib_abs_xpath_for(const char *element)
Definition: cib.c:133
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
Definition: xml_element.c:106
#define pcmk__assert(expr)
const char * target
Definition: pcmk_fence.c:31
pcmk__action_result_t result
Definition: pcmk_fence.c:37
const char * path
Definition: cib.c:28
#define crm_err(fmt, args...)
Definition: logging.h:359
#define PCMK_XE_DIFF
Definition: xml_names.h:100
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition: xml_element.c:1201
void xml_acl_disable(xmlNode *xml)
Definition: acl.c:624
#define crm_log_xml_info(xml, text)
Definition: logging.h:378
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
Definition: patchset.c:311
#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)
const char * parent
Definition: cib.c:27
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
Definition: patchset.c:759
#define PCMK_XA_ADMIN_EPOCH
Definition: xml_names.h:232
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
#define PCMK_XE_POSITION
Definition: xml_names.h:163
#define crm_info(fmt, args...)
Definition: logging.h:367
uint32_t version
Definition: remote.c:209
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml_element.c:379
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
const char * crm_xml_add_int(xmlNode *node, const char *name, int value)
Create an XML attribute with specified name and integer value.
Definition: xml_element.c:1070