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_create_patchset_v2
  4. xml_create_patchset
  5. patchset_process_digest
  6. xml_patch_versions
  7. xml_patch_version_check
  8. first_matching_xml_child
  9. search_v2_xpath
  10. sort_change_obj_by_position
  11. apply_v2_patchset
  12. xml_apply_patchset
  13. pcmk__cib_element_in_patchset

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

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