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

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