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. not_id
  9. process_v1_removals
  10. process_v1_additions
  11. find_patch_xml_node
  12. xml_patch_versions
  13. xml_patch_version_check
  14. apply_v1_patchset
  15. first_matching_xml_child
  16. search_v2_xpath
  17. sort_change_obj_by_position
  18. apply_v2_patchset
  19. xml_apply_patchset
  20. purge_diff_markers
  21. diff_xml_object
  22. subtract_xml_comment
  23. subtract_xml_object
  24. apply_xml_diff

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

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