LCOV - code coverage report
Current view: top level - common - patchset.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 739 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 24 0.0 %

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

Generated by: LCOV version 1.14