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

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