root/lib/common/patchset.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. add_xml_changes_to_patchset
  2. is_config_change
  3. xml_repair_v1_diff
  4. xml_create_patchset_v1
  5. xml_create_patchset_v2
  6. xml_create_patchset
  7. patchset_process_digest
  8. subtract_v1_xml_comment
  9. subtract_v1_xml_object
  10. not_id
  11. process_v1_removals
  12. process_v1_additions
  13. find_patch_xml_node
  14. xml_patch_versions
  15. xml_patch_version_check
  16. purge_v1_diff_markers
  17. apply_v1_patchset
  18. first_matching_xml_child
  19. search_v2_xpath
  20. sort_change_obj_by_position
  21. apply_v2_patchset
  22. xml_apply_patchset
  23. can_prune_leaf_v1
  24. pcmk__diff_v1_xml_object
  25. apply_xml_diff
  26. purge_diff_markers
  27. diff_xml_object
  28. subtract_xml_object
  29. can_prune_leaf

   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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
  57             crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE);
  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 
  83                 crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY);
  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),
 133                   pcmk__xml_position(xml, pcmk__xf_skip));
 134 
 135         if (xpath != NULL) {
 136             change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
 137 
 138             crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE);
 139             crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
 140             crm_xml_add_int(change, PCMK_XE_POSITION,
 141                             pcmk__xml_position(xml, pcmk__xf_deleted));
 142             g_string_free(xpath, TRUE);
 143         }
 144     }
 145 }
 146 
 147 static bool
 148 is_config_change(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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[] = {
 189         PCMK_XA_ADMIN_EPOCH,
 190         PCMK_XA_EPOCH,
 191         PCMK_XA_NUM_UPDATES,
 192     };
 193 
 194     if (local_diff == NULL) {
 195         crm_trace("Nothing to do");
 196         return;
 197     }
 198 
 199     tag = PCMK__XE_DIFF_REMOVED;
 200     diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL);
 201     if (diff_child == NULL) {
 202         diff_child = pcmk__xe_create(local_diff, tag);
 203     }
 204 
 205     tag = PCMK_XE_CIB;
 206     cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL);
 207     if (cib == NULL) {
 208         cib = pcmk__xe_create(diff_child, tag);
 209     }
 210 
 211     for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) {
 212         const char *value = crm_element_value(last, vfields[lpc]);
 213 
 214         crm_xml_add(diff_child, vfields[lpc], value);
 215         if (changed || lpc == 2) {
 216             crm_xml_add(cib, vfields[lpc], value);
 217         }
 218     }
 219 
 220     tag = PCMK__XE_DIFF_ADDED;
 221     diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL);
 222     if (diff_child == NULL) {
 223         diff_child = pcmk__xe_create(local_diff, tag);
 224     }
 225 
 226     tag = PCMK_XE_CIB;
 227     cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL);
 228     if (cib == NULL) {
 229         cib = pcmk__xe_create(diff_child, tag);
 230     }
 231 
 232     for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) {
 233         const char *value = crm_element_value(next, vfields[lpc]);
 234 
 235         crm_xml_add(diff_child, vfields[lpc], value);
 236     }
 237 
 238     for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) {
 239         
 240         const char *p_value = pcmk__xml_attr_value(a);
 241 
 242         xmlSetProp(cib, a->name, (pcmkXmlStr) p_value);
 243     }
 244 
 245     crm_log_xml_explicit(local_diff, "Repaired-diff");
 246 }
 247 
 248 // @COMPAT Remove when v1 patchsets are removed
 249 static xmlNode *
 250 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
     /* [previous][next][first][last][top][bottom][index][help] */
 251                        bool suppress)
 252 {
 253     xmlNode *patchset = pcmk__diff_v1_xml_object(source, target, suppress);
 254 
 255     if (patchset) {
 256         CRM_LOG_ASSERT(xml_document_dirty(target));
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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[] = {
 274         PCMK_XA_ADMIN_EPOCH,
 275         PCMK_XA_EPOCH,
 276         PCMK_XA_NUM_UPDATES,
 277     };
 278 
 279     CRM_ASSERT(target);
 280     if (!xml_document_dirty(target)) {
 281         return NULL;
 282     }
 283 
 284     CRM_ASSERT(target->doc);
 285     docpriv = target->doc->_private;
 286 
 287     patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
 288     crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
 289 
 290     version = pcmk__xe_create(patchset, PCMK_XE_VERSION);
 291 
 292     v = pcmk__xe_create(version, PCMK_XE_SOURCE);
 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 
 302     v = pcmk__xe_create(version, PCMK_XE_TARGET);
 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 
 316         crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE);
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 336     xml_acl_disable(target);
 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);
 349         crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0");
 350 
 351         crm_element_value_int(target, PCMK_XA_EPOCH, &counter);
 352         crm_xml_add_int(target, PCMK_XA_EPOCH, counter+1);
 353 
 354     } else if (manage_version) {
 355         crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter);
 356         crm_trace("Status changed %d - %d %s", format, counter,
 357                   crm_element_value(source, PCMK_XA_NUM_UPDATES));
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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      */
 400     CRM_LOG_ASSERT(!xml_document_dirty(target));
 401 
 402     crm_element_value_int(patch, PCMK_XA_FORMAT, &format);
 403     if ((format > 1) && !with_digest) {
 404         return;
 405     }
 406 
 407     version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET);
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 484     value = crm_element_value(right, PCMK__XA_CRM_DIFF_MARKER);
 485     if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
 486         crm_trace("We are the root of the deletion: %s.id=%s", name, id);
 487         *changed = TRUE;
 488         return NULL;
 489     }
 490 
 491     // @TODO Avoiding creating the full hierarchy would save work here
 492     diff = pcmk__xe_create(parent, name);
 493 
 494     // Changes to child objects
 495     for (left_child = pcmk__xml_first_child(left); left_child != NULL;
 496          left_child = pcmk__xml_next(left_child)) {
 497         gboolean child_changed = FALSE;
 498 
 499         right_child = pcmk__xml_match(right, left_child, false);
 500         subtract_v1_xml_object(diff, left_child, right_child, full,
 501                                &child_changed, marker);
 502         if (child_changed) {
 503             *changed = TRUE;
 504         }
 505     }
 506 
 507     if (!*changed) {
 508         /* Nothing to do */
 509 
 510     } else if (full) {
 511         xmlAttrPtr pIter = NULL;
 512 
 513         for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
 514              pIter = pIter->next) {
 515             const char *p_name = (const char *)pIter->name;
 516             const char *p_value = pcmk__xml_attr_value(pIter);
 517 
 518             xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
 519         }
 520 
 521         // We have everything we need
 522         goto done;
 523     }
 524 
 525     // Changes to name/value pairs
 526     for (xIter = pcmk__xe_first_attr(left); xIter != NULL;
 527          xIter = xIter->next) {
 528         const char *prop_name = (const char *) xIter->name;
 529         xmlAttrPtr right_attr = NULL;
 530         xml_node_private_t *nodepriv = NULL;
 531 
 532         if (strcmp(prop_name, PCMK_XA_ID) == 0) {
 533             // id already obtained when present ~ this case, so just reuse
 534             xmlSetProp(diff, (pcmkXmlStr) PCMK_XA_ID, (pcmkXmlStr) id);
 535             continue;
 536         }
 537 
 538         if (pcmk__xa_filterable(prop_name)) {
 539             continue;
 540         }
 541 
 542         right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name);
 543         if (right_attr) {
 544             nodepriv = right_attr->_private;
 545         }
 546 
 547         right_val = crm_element_value(right, prop_name);
 548         if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) {
 549             /* new */
 550             *changed = TRUE;
 551             if (full) {
 552                 xmlAttrPtr pIter = NULL;
 553 
 554                 for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
 555                      pIter = pIter->next) {
 556                     const char *p_name = (const char *) pIter->name;
 557                     const char *p_value = pcmk__xml_attr_value(pIter);
 558 
 559                     xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value);
 560                 }
 561                 break;
 562 
 563             } else {
 564                 const char *left_value = pcmk__xml_attr_value(xIter);
 565 
 566                 xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value);
 567                 crm_xml_add(diff, prop_name, left_value);
 568             }
 569 
 570         } else {
 571             /* Only now do we need the left value */
 572             const char *left_value = pcmk__xml_attr_value(xIter);
 573 
 574             if (strcmp(left_value, right_val) == 0) {
 575                 /* unchanged */
 576 
 577             } else {
 578                 *changed = TRUE;
 579                 if (full) {
 580                     xmlAttrPtr pIter = NULL;
 581 
 582                     crm_trace("Changes detected to %s in "
 583                               "<%s " PCMK_XA_ID "=%s>", prop_name, name, id);
 584                     for (pIter = pcmk__xe_first_attr(left); pIter != NULL;
 585                          pIter = pIter->next) {
 586                         const char *p_name = (const char *) pIter->name;
 587                         const char *p_value = pcmk__xml_attr_value(pIter);
 588 
 589                         xmlSetProp(diff, (pcmkXmlStr) p_name,
 590                                    (pcmkXmlStr) p_value);
 591                     }
 592                     break;
 593 
 594                 } else {
 595                     crm_trace("Changes detected to %s (%s -> %s) in "
 596                               "<%s " PCMK_XA_ID "=%s>",
 597                               prop_name, left_value, right_val, name, id);
 598                     crm_xml_add(diff, prop_name, left_value);
 599                 }
 600             }
 601         }
 602     }
 603 
 604     if (!*changed) {
 605         free_xml(diff);
 606         return NULL;
 607 
 608     } else if (!full && (id != NULL)) {
 609         crm_xml_add(diff, PCMK_XA_ID, id);
 610     }
 611   done:
 612     return diff;
 613 }
 614 
 615 /* @COMPAT Remove when v1 patchsets are removed.
 616  *
 617  * Return true if attribute name is not \c PCMK_XML_ID.
 618  */
 619 static bool
 620 not_id(xmlAttrPtr attr, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 621 {
 622     return strcmp((const char *) attr->name, PCMK_XA_ID) != 0;
 623 }
 624 
 625 /* @COMPAT Remove when v1 patchsets are removed.
 626  *
 627  * Apply the removals section of a v1 patchset to an XML node.
 628  */
 629 static void
 630 process_v1_removals(xmlNode *target, xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 631 {
 632     xmlNode *patch_child = NULL;
 633     xmlNode *cIter = NULL;
 634 
 635     char *id = NULL;
 636     const char *value = NULL;
 637 
 638     if ((target == NULL) || (patch == NULL)) {
 639         return;
 640     }
 641 
 642     if (target->type == XML_COMMENT_NODE) {
 643         gboolean dummy;
 644 
 645         subtract_v1_xml_comment(target->parent, target, patch, &dummy);
 646     }
 647 
 648     CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return);
 649     CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
 650                            pcmk__str_none),
 651               return);
 652 
 653     // Check for PCMK__XA_CRM_DIFF_MARKER in a child
 654     id = crm_element_value_copy(target, PCMK_XA_ID);
 655     value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER);
 656     if ((value != NULL) && (strcmp(value, "removed:top") == 0)) {
 657         crm_trace("We are the root of the deletion: %s.id=%s",
 658                   target->name, id);
 659         free_xml(target);
 660         free(id);
 661         return;
 662     }
 663 
 664     // Removing then restoring id would change ordering of properties
 665     pcmk__xe_remove_matching_attrs(patch, not_id, NULL);
 666 
 667     // Changes to child objects
 668     cIter = pcmk__xml_first_child(target);
 669     while (cIter) {
 670         xmlNode *target_child = cIter;
 671 
 672         cIter = pcmk__xml_next(cIter);
 673         patch_child = pcmk__xml_match(patch, target_child, false);
 674         process_v1_removals(target_child, patch_child);
 675     }
 676     free(id);
 677 }
 678 
 679 /* @COMPAT Remove when v1 patchsets are removed.
 680  *
 681  * Apply the additions section of a v1 patchset to an XML node.
 682  */
 683 static void
 684 process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch)
     /* [previous][next][first][last][top][bottom][index][help] */
 685 {
 686     xmlNode *patch_child = NULL;
 687     xmlNode *target_child = NULL;
 688     xmlAttrPtr xIter = NULL;
 689 
 690     const char *id = NULL;
 691     const char *name = NULL;
 692     const char *value = NULL;
 693 
 694     if (patch == NULL) {
 695         return;
 696     } else if ((parent == NULL) && (target == NULL)) {
 697         return;
 698     }
 699 
 700     // Check for PCMK__XA_CRM_DIFF_MARKER in a child
 701     name = (const char *) patch->name;
 702     value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER);
 703     if ((target == NULL) && (value != NULL)
 704         && (strcmp(value, "added:top") == 0)) {
 705         id = pcmk__xe_id(patch);
 706         crm_trace("We are the root of the addition: %s.id=%s", name, id);
 707         pcmk__xml_copy(parent, patch);
 708         return;
 709 
 710     } else if (target == NULL) {
 711         id = pcmk__xe_id(patch);
 712         crm_err("Could not locate: %s.id=%s", name, id);
 713         return;
 714     }
 715 
 716     if (target->type == XML_COMMENT_NODE) {
 717         pcmk__xc_update(parent, target, patch);
 718     }
 719 
 720     CRM_CHECK(pcmk__xe_is(target, name), return);
 721     CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch),
 722                            pcmk__str_none),
 723               return);
 724 
 725     for (xIter = pcmk__xe_first_attr(patch); xIter != NULL;
 726          xIter = xIter->next) {
 727         const char *p_name = (const char *) xIter->name;
 728         const char *p_value = pcmk__xml_attr_value(xIter);
 729 
 730         pcmk__xe_remove_attr(target, p_name);   // Preserve patch order
 731         crm_xml_add(target, p_name, p_value);
 732     }
 733 
 734     // Changes to child objects
 735     for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL;
 736          patch_child = pcmk__xml_next(patch_child)) {
 737 
 738         target_child = pcmk__xml_match(target, patch_child, false);
 739         process_v1_additions(target, target_child, patch_child);
 740     }
 741 }
 742 
 743 /*!
 744  * \internal
 745  * \brief Find additions or removals in a patch set
 746  *
 747  * \param[in]     patchset   XML of patch
 748  * \param[in]     format     Patch version
 749  * \param[in]     added      TRUE if looking for additions, FALSE if removals
 750  * \param[in,out] patch_node Will be set to node if found
 751  *
 752  * \return TRUE if format is valid, FALSE if invalid
 753  */
 754 static bool
 755 find_patch_xml_node(const xmlNode *patchset, int format, bool added,
     /* [previous][next][first][last][top][bottom][index][help] */
 756                     xmlNode **patch_node)
 757 {
 758     xmlNode *cib_node;
 759     const char *label;
 760 
 761     switch (format) {
 762         case 1:
 763             // @COMPAT Remove when v1 patchsets are removed
 764             label = added? PCMK__XE_DIFF_ADDED : PCMK__XE_DIFF_REMOVED;
 765             *patch_node = pcmk__xe_first_child(patchset, label, NULL, NULL);
 766             cib_node = pcmk__xe_first_child(*patch_node, PCMK_XE_CIB, NULL,
 767                                             NULL);
 768             if (cib_node != NULL) {
 769                 *patch_node = cib_node;
 770             }
 771             break;
 772         case 2:
 773             label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE;
 774             *patch_node = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL,
 775                                                NULL);
 776             *patch_node = pcmk__xe_first_child(*patch_node, label, NULL, NULL);
 777             break;
 778         default:
 779             crm_warn("Unknown patch format: %d", format);
 780             *patch_node = NULL;
 781             return FALSE;
 782     }
 783     return TRUE;
 784 }
 785 
 786 // Get CIB versions used for additions and deletions in a patchset
 787 bool
 788 xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
     /* [previous][next][first][last][top][bottom][index][help] */
 789 {
 790     int lpc = 0;
 791     int format = 1;
 792     xmlNode *tmp = NULL;
 793 
 794     const char *vfields[] = {
 795         PCMK_XA_ADMIN_EPOCH,
 796         PCMK_XA_EPOCH,
 797         PCMK_XA_NUM_UPDATES,
 798     };
 799 
 800 
 801     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
 802 
 803     /* Process removals */
 804     if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
 805         return -EINVAL;
 806     }
 807     if (tmp != NULL) {
 808         for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 809             crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
 810             crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
 811         }
 812     }
 813 
 814     /* Process additions */
 815     if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
 816         return -EINVAL;
 817     }
 818     if (tmp != NULL) {
 819         for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 820             crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
 821             crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
 822         }
 823     }
 824     return pcmk_ok;
 825 }
 826 
 827 /*!
 828  * \internal
 829  * \brief Check whether patchset can be applied to current CIB
 830  *
 831  * \param[in] xml       Root of current CIB
 832  * \param[in] patchset  Patchset to check
 833  *
 834  * \return Standard Pacemaker return code
 835  */
 836 static int
 837 xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 838 {
 839     int lpc = 0;
 840     bool changed = FALSE;
 841 
 842     int this[] = { 0, 0, 0 };
 843     int add[] = { 0, 0, 0 };
 844     int del[] = { 0, 0, 0 };
 845 
 846     const char *vfields[] = {
 847         PCMK_XA_ADMIN_EPOCH,
 848         PCMK_XA_EPOCH,
 849         PCMK_XA_NUM_UPDATES,
 850     };
 851 
 852     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 853         crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
 854         crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
 855         if (this[lpc] < 0) {
 856             this[lpc] = 0;
 857         }
 858     }
 859 
 860     /* Set some defaults in case nothing is present */
 861     add[0] = this[0];
 862     add[1] = this[1];
 863     add[2] = this[2] + 1;
 864     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 865         del[lpc] = this[lpc];
 866     }
 867 
 868     xml_patch_versions(patchset, add, del);
 869 
 870     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 871         if (this[lpc] < del[lpc]) {
 872             crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
 873                       vfields[lpc], this[0], this[1], this[2],
 874                       del[0], del[1], del[2], add[0], add[1], add[2]);
 875             return pcmk_rc_diff_resync;
 876 
 877         } else if (this[lpc] > del[lpc]) {
 878             crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
 879                      vfields[lpc], this[0], this[1], this[2],
 880                      del[0], del[1], del[2], add[0], add[1], add[2], patchset);
 881             crm_log_xml_info(patchset, "OldPatch");
 882             return pcmk_rc_old_data;
 883         }
 884     }
 885 
 886     for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
 887         if (add[lpc] > del[lpc]) {
 888             changed = TRUE;
 889         }
 890     }
 891 
 892     if (!changed) {
 893         crm_notice("Versions did not change in patch %d.%d.%d",
 894                    add[0], add[1], add[2]);
 895         return pcmk_rc_old_data;
 896     }
 897 
 898     crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
 899               add[0], add[1], add[2], this[0], this[1], this[2]);
 900     return pcmk_rc_ok;
 901 }
 902 
 903 // @COMPAT Remove when v1 patchsets are removed
 904 static void
 905 purge_v1_diff_markers(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 906 {
 907     xmlNode *child = NULL;
 908 
 909     CRM_CHECK(node != NULL, return);
 910 
 911     pcmk__xe_remove_attr(node, PCMK__XA_CRM_DIFF_MARKER);
 912     for (child = pcmk__xml_first_child(node); child != NULL;
 913          child = pcmk__xml_next(child)) {
 914         purge_v1_diff_markers(child);
 915     }
 916 }
 917 
 918 // @COMPAT Remove when v1 patchsets are removed
 919 /*!
 920  * \internal
 921  * \brief Apply a version 1 patchset to an XML node
 922  *
 923  * \param[in,out] xml       XML to apply patchset to
 924  * \param[in]     patchset  Patchset to apply
 925  *
 926  * \return Standard Pacemaker return code
 927  */
 928 static int
 929 apply_v1_patchset(xmlNode *xml, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 930 {
 931     int rc = pcmk_rc_ok;
 932     int root_nodes_seen = 0;
 933 
 934     xmlNode *child_diff = NULL;
 935     xmlNode *added = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL,
 936                                           NULL);
 937     xmlNode *removed = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED,
 938                                             NULL, NULL);
 939     xmlNode *old = pcmk__xml_copy(NULL, xml);
 940 
 941     crm_trace("Subtraction Phase");
 942     for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
 943          child_diff = pcmk__xml_next(child_diff)) {
 944         CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
 945         if (root_nodes_seen == 0) {
 946             process_v1_removals(xml, child_diff);
 947         }
 948         root_nodes_seen++;
 949     }
 950 
 951     if (root_nodes_seen > 1) {
 952         crm_err("(-) Diffs cannot contain more than one change set... saw %d",
 953                 root_nodes_seen);
 954         rc = ENOTUNIQ;
 955     }
 956 
 957     root_nodes_seen = 0;
 958     crm_trace("Addition Phase");
 959     if (rc == pcmk_rc_ok) {
 960         xmlNode *child_diff = NULL;
 961 
 962         for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
 963              child_diff = pcmk__xml_next(child_diff)) {
 964             CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
 965             if (root_nodes_seen == 0) {
 966                 process_v1_additions(NULL, xml, child_diff);
 967             }
 968             root_nodes_seen++;
 969         }
 970     }
 971 
 972     if (root_nodes_seen > 1) {
 973         crm_err("(+) Diffs cannot contain more than one change set... saw %d",
 974                 root_nodes_seen);
 975         rc = ENOTUNIQ;
 976     }
 977 
 978     purge_v1_diff_markers(xml); // Purge prior to checking digest
 979 
 980     free_xml(old);
 981     return rc;
 982 }
 983 
 984 // Return first child matching element name and optionally id or position
 985 static xmlNode *
 986 first_matching_xml_child(const xmlNode *parent, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 987                          const char *id, int position)
 988 {
 989     xmlNode *cIter = NULL;
 990 
 991     for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
 992          cIter = pcmk__xml_next(cIter)) {
 993         if (strcmp((const char *) cIter->name, name) != 0) {
 994             continue;
 995         } else if (id) {
 996             const char *cid = pcmk__xe_id(cIter);
 997 
 998             if ((cid == NULL) || (strcmp(cid, id) != 0)) {
 999                 continue;
1000             }
1001         }
1002 
1003         // "position" makes sense only for XML comments for now
1004         if ((cIter->type == XML_COMMENT_NODE)
1005             && (position >= 0)
1006             && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
1007             continue;
1008         }
1009 
1010         return cIter;
1011     }
1012     return NULL;
1013 }
1014 
1015 /*!
1016  * \internal
1017  * \brief Simplified, more efficient alternative to get_xpath_object()
1018  *
1019  * \param[in] top              Root of XML to search
1020  * \param[in] key              Search xpath
1021  * \param[in] target_position  If deleting, where to delete
1022  *
1023  * \return XML child matching xpath if found, NULL otherwise
1024  *
1025  * \note This only works on simplified xpaths found in v2 patchset diffs,
1026  *       i.e. the only allowed search predicate is [@id='XXX'].
1027  */
1028 static xmlNode *
1029 search_v2_xpath(const xmlNode *top, const char *key, int target_position)
     /* [previous][next][first][last][top][bottom][index][help] */
1030 {
1031     xmlNode *target = (xmlNode *) top->doc;
1032     const char *current = key;
1033     char *section;
1034     char *remainder;
1035     char *id;
1036     char *tag;
1037     char *path = NULL;
1038     int rc;
1039     size_t key_len;
1040 
1041     CRM_CHECK(key != NULL, return NULL);
1042     key_len = strlen(key);
1043 
1044     /* These are scanned from key after a slash, so they can't be bigger
1045      * than key_len - 1 characters plus a null terminator.
1046      */
1047 
1048     remainder = pcmk__assert_alloc(key_len, sizeof(char));
1049     section = pcmk__assert_alloc(key_len, sizeof(char));
1050     id = pcmk__assert_alloc(key_len, sizeof(char));
1051     tag = pcmk__assert_alloc(key_len, sizeof(char));
1052 
1053     do {
1054         // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
1055         rc = sscanf(current, "/%[^/]%s", section, remainder);
1056         if (rc > 0) {
1057             // Separate FIRST_COMPONENT into TAG[@id='ID']
1058             int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
1059             int current_position = -1;
1060 
1061             /* The target position is for the final component tag, so only use
1062              * it if there is nothing left to search after this component.
1063              */
1064             if ((rc == 1) && (target_position >= 0)) {
1065                 current_position = target_position;
1066             }
1067 
1068             switch (f) {
1069                 case 1:
1070                     // @COMPAT Remove when v1 patchsets are removed
1071                     target = first_matching_xml_child(target, tag, NULL,
1072                                                       current_position);
1073                     break;
1074                 case 2:
1075                     target = first_matching_xml_child(target, tag, id,
1076                                                       current_position);
1077                     break;
1078                 default:
1079                     // This should not be possible
1080                     target = NULL;
1081                     break;
1082             }
1083             current = remainder;
1084         }
1085 
1086     // Continue if something remains to search, and we've matched so far
1087     } while ((rc == 2) && target);
1088 
1089     if (target) {
1090         crm_trace("Found %s for %s",
1091                   (path = (char *) xmlGetNodePath(target)), key);
1092         free(path);
1093     } else {
1094         crm_debug("No match for %s", key);
1095     }
1096 
1097     free(remainder);
1098     free(section);
1099     free(tag);
1100     free(id);
1101     return target;
1102 }
1103 
1104 typedef struct xml_change_obj_s {
1105     const xmlNode *change;
1106     xmlNode *match;
1107 } xml_change_obj_t;
1108 
1109 static gint
1110 sort_change_obj_by_position(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
1111 {
1112     const xml_change_obj_t *change_obj_a = a;
1113     const xml_change_obj_t *change_obj_b = b;
1114     int position_a = -1;
1115     int position_b = -1;
1116 
1117     crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
1118     crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
1119 
1120     if (position_a < position_b) {
1121         return -1;
1122 
1123     } else if (position_a > position_b) {
1124         return 1;
1125     }
1126 
1127     return 0;
1128 }
1129 
1130 /*!
1131  * \internal
1132  * \brief Apply a version 2 patchset to an XML node
1133  *
1134  * \param[in,out] xml       XML to apply patchset to
1135  * \param[in]     patchset  Patchset to apply
1136  *
1137  * \return Standard Pacemaker return code
1138  */
1139 static int
1140 apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
1141 {
1142     int rc = pcmk_rc_ok;
1143     const xmlNode *change = NULL;
1144     GList *change_objs = NULL;
1145     GList *gIter = NULL;
1146 
1147     for (change = pcmk__xml_first_child(patchset); change != NULL;
1148          change = pcmk__xml_next(change)) {
1149         xmlNode *match = NULL;
1150         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
1151         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
1152         int position = -1;
1153 
1154         if (op == NULL) {
1155             continue;
1156         }
1157 
1158         crm_trace("Processing %s %s", change->name, op);
1159 
1160         /* PCMK_VALUE_DELETE changes for XML comments are generated with
1161          * PCMK_XE_POSITION
1162          */
1163         if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1164             crm_element_value_int(change, PCMK_XE_POSITION, &position);
1165         }
1166         match = search_v2_xpath(xml, xpath, position);
1167         crm_trace("Performing %s on %s with %p", op, xpath, match);
1168 
1169         if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
1170             crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
1171             continue;
1172 
1173         } else if (match == NULL) {
1174             crm_err("No %s match for %s in %p", op, xpath, xml->doc);
1175             rc = pcmk_rc_diff_failed;
1176             continue;
1177 
1178         } else if (pcmk__str_any_of(op,
1179                                     PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) {
1180             // Delay the adding of a PCMK_VALUE_CREATE object
1181             xml_change_obj_t *change_obj =
1182                 pcmk__assert_alloc(1, sizeof(xml_change_obj_t));
1183 
1184             change_obj->change = change;
1185             change_obj->match = match;
1186 
1187             change_objs = g_list_append(change_objs, change_obj);
1188 
1189             if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1190                 // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
1191                 if ((match->parent != NULL) && (match->parent->last != NULL)) {
1192                     xmlAddNextSibling(match->parent->last, match);
1193                 }
1194             }
1195 
1196         } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
1197             free_xml(match);
1198 
1199         } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
1200             const xmlNode *child = pcmk__xe_first_child(change,
1201                                                         PCMK_XE_CHANGE_RESULT,
1202                                                         NULL, NULL);
1203             const xmlNode *attrs = pcmk__xml_first_child(child);
1204 
1205             if (attrs == NULL) {
1206                 rc = ENOMSG;
1207                 continue;
1208             }
1209             pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all
1210 
1211             for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
1212                  pIter = pIter->next) {
1213                 const char *name = (const char *) pIter->name;
1214                 const char *value = pcmk__xml_attr_value(pIter);
1215 
1216                 crm_xml_add(match, name, value);
1217             }
1218 
1219         } else {
1220             crm_err("Unknown operation: %s", op);
1221             rc = pcmk_rc_diff_failed;
1222         }
1223     }
1224 
1225     // Changes should be generated in the right order. Double checking.
1226     change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
1227 
1228     for (gIter = change_objs; gIter; gIter = gIter->next) {
1229         xml_change_obj_t *change_obj = gIter->data;
1230         xmlNode *match = change_obj->match;
1231         const char *op = NULL;
1232         const char *xpath = NULL;
1233 
1234         change = change_obj->change;
1235 
1236         op = crm_element_value(change, PCMK_XA_OPERATION);
1237         xpath = crm_element_value(change, PCMK_XA_PATH);
1238 
1239         crm_trace("Continue performing %s on %s with %p", op, xpath, match);
1240 
1241         if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
1242             int position = 0;
1243             xmlNode *child = NULL;
1244             xmlNode *match_child = NULL;
1245 
1246             match_child = match->children;
1247             crm_element_value_int(change, PCMK_XE_POSITION, &position);
1248 
1249             while ((match_child != NULL)
1250                    && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1251                 match_child = match_child->next;
1252             }
1253 
1254             child = xmlDocCopyNode(change->children, match->doc, 1);
1255             if (child == NULL) {
1256                 return ENOMEM;
1257             }
1258 
1259             if (match_child) {
1260                 crm_trace("Adding %s at position %d", child->name, position);
1261                 xmlAddPrevSibling(match_child, child);
1262 
1263             } else if (match->last) {
1264                 crm_trace("Adding %s at position %d (end)",
1265                           child->name, position);
1266                 xmlAddNextSibling(match->last, child);
1267 
1268             } else {
1269                 crm_trace("Adding %s at position %d (first)",
1270                           child->name, position);
1271                 CRM_LOG_ASSERT(position == 0);
1272                 xmlAddChild(match, child);
1273             }
1274             pcmk__xml_mark_created(child);
1275 
1276         } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
1277             int position = 0;
1278 
1279             crm_element_value_int(change, PCMK_XE_POSITION, &position);
1280             if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1281                 xmlNode *match_child = NULL;
1282                 int p = position;
1283 
1284                 if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
1285                     p++; // Skip ourselves
1286                 }
1287 
1288                 CRM_ASSERT(match->parent != NULL);
1289                 match_child = match->parent->children;
1290 
1291                 while ((match_child != NULL)
1292                        && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
1293                     match_child = match_child->next;
1294                 }
1295 
1296                 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
1297                           match->name, position,
1298                           pcmk__xml_position(match, pcmk__xf_skip),
1299                           match->prev, (match_child? "next":"last"),
1300                           (match_child? match_child : match->parent->last));
1301 
1302                 if (match_child) {
1303                     xmlAddPrevSibling(match_child, match);
1304 
1305                 } else {
1306                     CRM_ASSERT(match->parent->last != NULL);
1307                     xmlAddNextSibling(match->parent->last, match);
1308                 }
1309 
1310             } else {
1311                 crm_trace("%s is already in position %d",
1312                           match->name, position);
1313             }
1314 
1315             if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
1316                 crm_err("Moved %s.%s to position %d instead of %d (%p)",
1317                         match->name, pcmk__xe_id(match),
1318                         pcmk__xml_position(match, pcmk__xf_skip),
1319                         position, match->prev);
1320                 rc = pcmk_rc_diff_failed;
1321             }
1322         }
1323     }
1324 
1325     g_list_free_full(change_objs, free);
1326     return rc;
1327 }
1328 
1329 int
1330 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
     /* [previous][next][first][last][top][bottom][index][help] */
1331 {
1332     int format = 1;
1333     int rc = pcmk_ok;
1334     xmlNode *old = NULL;
1335     const char *digest = NULL;
1336 
1337     if (patchset == NULL) {
1338         return rc;
1339     }
1340 
1341     pcmk__log_xml_patchset(LOG_TRACE, patchset);
1342 
1343     if (check_version) {
1344         rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
1345         if (rc != pcmk_ok) {
1346             return rc;
1347         }
1348     }
1349 
1350     digest = crm_element_value(patchset, PCMK__XA_DIGEST);
1351     if (digest != NULL) {
1352         /* Make original XML available for logging in case result doesn't have
1353          * expected digest
1354          */
1355         pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
1356     }
1357 
1358     if (rc == pcmk_ok) {
1359         crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
1360         switch (format) {
1361             case 1:
1362                 // @COMPAT Remove when v1 patchsets are removed
1363                 rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset));
1364                 break;
1365             case 2:
1366                 rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
1367                 break;
1368             default:
1369                 crm_err("Unknown patch format: %d", format);
1370                 rc = -EINVAL;
1371         }
1372     }
1373 
1374     if ((rc == pcmk_ok) && (digest != NULL)) {
1375         char *new_digest = NULL;
1376         char *version = crm_element_value_copy(xml, PCMK_XA_CRM_FEATURE_SET);
1377 
1378         new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
1379         if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1380             crm_info("v%d digest mis-match: expected %s, calculated %s",
1381                      format, digest, new_digest);
1382             rc = -pcmk_err_diff_failed;
1383             pcmk__if_tracing(
1384                 {
1385                     save_xml_to_file(old, "PatchDigest:input", NULL);
1386                     save_xml_to_file(xml, "PatchDigest:result", NULL);
1387                     save_xml_to_file(patchset, "PatchDigest:diff", NULL);
1388                 },
1389                 {}
1390             );
1391 
1392         } else {
1393             crm_trace("v%d digest matched: expected %s, calculated %s",
1394                       format, digest, new_digest);
1395         }
1396         free(new_digest);
1397         free(version);
1398     }
1399     free_xml(old);
1400     return rc;
1401 }
1402 
1403 // @COMPAT Remove when v1 patchsets are removed
1404 static bool
1405 can_prune_leaf_v1(xmlNode *node)
     /* [previous][next][first][last][top][bottom][index][help] */
1406 {
1407     xmlNode *cIter = NULL;
1408     bool can_prune = true;
1409 
1410     CRM_CHECK(node != NULL, return false);
1411 
1412     /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed for
1413      * rolling upgrades)
1414      */
1415     if (pcmk__strcase_any_of((const char *) node->name,
1416                              PCMK_XE_RESOURCE_REF, PCMK_XE_OBJ_REF,
1417                              PCMK_XE_ROLE, PCMK__XE_ROLE_REF,
1418                              NULL)) {
1419         return false;
1420     }
1421 
1422     for (xmlAttrPtr a = pcmk__xe_first_attr(node); a != NULL; a = a->next) {
1423         const char *p_name = (const char *) a->name;
1424 
1425         if (strcmp(p_name, PCMK_XA_ID) == 0) {
1426             continue;
1427         }
1428         can_prune = false;
1429     }
1430 
1431     cIter = pcmk__xml_first_child(node);
1432     while (cIter) {
1433         xmlNode *child = cIter;
1434 
1435         cIter = pcmk__xml_next(cIter);
1436         if (can_prune_leaf_v1(child)) {
1437             free_xml(child);
1438         } else {
1439             can_prune = false;
1440         }
1441     }
1442     return can_prune;
1443 }
1444 
1445 // @COMPAT Remove when v1 patchsets are removed
1446 xmlNode *
1447 pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress)
     /* [previous][next][first][last][top][bottom][index][help] */
1448 {
1449     xmlNode *tmp1 = NULL;
1450     xmlNode *diff = pcmk__xe_create(NULL, PCMK_XE_DIFF);
1451     xmlNode *removed = pcmk__xe_create(diff, PCMK__XE_DIFF_REMOVED);
1452     xmlNode *added = pcmk__xe_create(diff, PCMK__XE_DIFF_ADDED);
1453 
1454     crm_xml_add(diff, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
1455 
1456     tmp1 = subtract_v1_xml_object(removed, old, new, false, NULL,
1457                                   "removed:top");
1458     if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1459         free_xml(tmp1);
1460     }
1461 
1462     tmp1 = subtract_v1_xml_object(added, new, old, true, NULL, "added:top");
1463     if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) {
1464         free_xml(tmp1);
1465     }
1466 
1467     if ((added->children == NULL) && (removed->children == NULL)) {
1468         free_xml(diff);
1469         diff = NULL;
1470     }
1471 
1472     return diff;
1473 }
1474 
1475 // Deprecated functions kept only for backward API compatibility
1476 // LCOV_EXCL_START
1477 
1478 #include <crm/common/xml_compat.h>
1479 
1480 gboolean
1481 apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1482 {
1483     gboolean result = TRUE;
1484     int root_nodes_seen = 0;
1485     const char *digest = crm_element_value(diff, PCMK__XA_DIGEST);
1486     const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET);
1487 
1488     xmlNode *child_diff = NULL;
1489     xmlNode *added = pcmk__xe_first_child(diff, PCMK__XE_DIFF_ADDED, NULL,
1490                                           NULL);
1491     xmlNode *removed = pcmk__xe_first_child(diff, PCMK__XE_DIFF_REMOVED, NULL,
1492                                             NULL);
1493 
1494     CRM_CHECK(new_xml != NULL, return FALSE);
1495 
1496     crm_trace("Subtraction Phase");
1497     for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL;
1498          child_diff = pcmk__xml_next(child_diff)) {
1499         CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1500         if (root_nodes_seen == 0) {
1501             *new_xml = subtract_v1_xml_object(NULL, old_xml, child_diff, false,
1502                                               NULL, NULL);
1503         }
1504         root_nodes_seen++;
1505     }
1506 
1507     if (root_nodes_seen == 0) {
1508         *new_xml = pcmk__xml_copy(NULL, old_xml);
1509 
1510     } else if (root_nodes_seen > 1) {
1511         crm_err("(-) Diffs cannot contain more than one change set... saw %d",
1512                 root_nodes_seen);
1513         result = FALSE;
1514     }
1515 
1516     root_nodes_seen = 0;
1517     crm_trace("Addition Phase");
1518     if (result) {
1519         xmlNode *child_diff = NULL;
1520 
1521         for (child_diff = pcmk__xml_first_child(added); child_diff != NULL;
1522              child_diff = pcmk__xml_next(child_diff)) {
1523             CRM_CHECK(root_nodes_seen == 0, result = FALSE);
1524             if (root_nodes_seen == 0) {
1525                 pcmk__xml_update(NULL, *new_xml, child_diff, pcmk__xaf_none,
1526                                  true);
1527             }
1528             root_nodes_seen++;
1529         }
1530     }
1531 
1532     if (root_nodes_seen > 1) {
1533         crm_err("(+) Diffs cannot contain more than one change set... saw %d",
1534                 root_nodes_seen);
1535         result = FALSE;
1536 
1537     } else if (result && (digest != NULL)) {
1538         char *new_digest = NULL;
1539 
1540         purge_v1_diff_markers(*new_xml);    // Purge now so diff is ok
1541         new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
1542                                                     version);
1543         if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
1544             crm_info("Digest mis-match: expected %s, calculated %s",
1545                      digest, new_digest);
1546             result = FALSE;
1547 
1548             pcmk__if_tracing(
1549                 {
1550                     save_xml_to_file(old_xml, "diff:original", NULL);
1551                     save_xml_to_file(diff, "diff:input", NULL);
1552                     save_xml_to_file(*new_xml, "diff:new", NULL);
1553                 },
1554                 {}
1555             );
1556 
1557         } else {
1558             crm_trace("Digest matched: expected %s, calculated %s",
1559                       digest, new_digest);
1560         }
1561         free(new_digest);
1562 
1563     } else if (result) {
1564         purge_v1_diff_markers(*new_xml);    // Purge now so diff is ok
1565     }
1566 
1567     return result;
1568 }
1569 
1570 void
1571 purge_diff_markers(xmlNode *a_node)
     /* [previous][next][first][last][top][bottom][index][help] */
1572 {
1573     purge_v1_diff_markers(a_node);
1574 }
1575 
1576 xmlNode *
1577 diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
     /* [previous][next][first][last][top][bottom][index][help] */
1578 {
1579     return pcmk__diff_v1_xml_object(old, new, suppress);
1580 }
1581 
1582 xmlNode *
1583 subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
     /* [previous][next][first][last][top][bottom][index][help] */
1584                     gboolean full, gboolean *changed, const char *marker)
1585 {
1586     return subtract_v1_xml_object(parent, left, right, full, changed, marker);
1587 }
1588 
1589 gboolean
1590 can_prune_leaf(xmlNode *xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
1591 {
1592     return can_prune_leaf_v1(xml_node);
1593 }
1594 
1595 // LCOV_EXCL_STOP
1596 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */