root/lib/common/digest.c

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

DEFINITIONS

This source file includes following definitions.
  1. dump_xml_for_digest
  2. calculate_xml_digest_v1
  3. calculate_xml_digest_v2
  4. calculate_on_disk_digest
  5. calculate_operation_digest
  6. calculate_xml_versioned_digest
  7. pcmk__verify_digest
  8. pcmk__xa_filterable
  9. crm_md5sum

   1 /*
   2  * Copyright 2015-2020 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 <unistd.h>
  14 #include <string.h>
  15 #include <stdlib.h>
  16 #include <md5.h>
  17 
  18 #include <crm/crm.h>
  19 #include <crm/msg_xml.h>
  20 #include <crm/common/xml.h>
  21 #include "crmcommon_private.h"
  22 
  23 #define BEST_EFFORT_STATUS 0
  24 
  25 /*!
  26  * \brief Dump XML in a format used with v1 digests
  27  *
  28  * \param[in] an_xml_node Root of XML to dump
  29  *
  30  * \return Newly allocated buffer containing dumped XML
  31  */
  32 static char *
  33 dump_xml_for_digest(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
  34 {
  35     char *buffer = NULL;
  36     int offset = 0, max = 0;
  37 
  38     /* for compatibility with the old result which is used for v1 digests */
  39     pcmk__buffer_add_char(&buffer, &offset, &max, ' ');
  40     pcmk__xml2text(an_xml_node, 0, &buffer, &offset, &max, 0);
  41     pcmk__buffer_add_char(&buffer, &offset, &max, '\n');
  42 
  43     return buffer;
  44 }
  45 
  46 /*!
  47  * \brief Calculate and return v1 digest of XML tree
  48  *
  49  * \param[in] input Root of XML to digest
  50  * \param[in] sort Whether to sort the XML before calculating digest
  51  * \param[in] ignored Not used
  52  *
  53  * \return Newly allocated string containing digest
  54  * \note Example return value: "c048eae664dba840e1d2060f00299e9d"
  55  */
  56 static char *
  57 calculate_xml_digest_v1(xmlNode * input, gboolean sort, gboolean ignored)
     /* [previous][next][first][last][top][bottom][index][help] */
  58 {
  59     char *digest = NULL;
  60     char *buffer = NULL;
  61     xmlNode *copy = NULL;
  62 
  63     if (sort) {
  64         crm_trace("Sorting xml...");
  65         copy = sorted_xml(input, NULL, TRUE);
  66         crm_trace("Done");
  67         input = copy;
  68     }
  69 
  70     buffer = dump_xml_for_digest(input);
  71     CRM_CHECK(buffer != NULL && strlen(buffer) > 0, free_xml(copy);
  72               free(buffer);
  73               return NULL);
  74 
  75     digest = crm_md5sum(buffer);
  76     crm_log_xml_trace(input, "digest:source");
  77 
  78     free(buffer);
  79     free_xml(copy);
  80     return digest;
  81 }
  82 
  83 /*!
  84  * \brief Calculate and return v2 digest of XML tree
  85  *
  86  * \param[in] source Root of XML to digest
  87  * \param[in] do_filter Whether to filter certain XML attributes
  88  *
  89  * \return Newly allocated string containing digest
  90  */
  91 static char *
  92 calculate_xml_digest_v2(xmlNode * source, gboolean do_filter)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     char *digest = NULL;
  95     char *buffer = NULL;
  96     int offset, max;
  97 
  98     static struct qb_log_callsite *digest_cs = NULL;
  99 
 100     crm_trace("Begin digest %s", do_filter?"filtered":"");
 101     if (do_filter && BEST_EFFORT_STATUS) {
 102         /* Exclude the status calculation from the digest
 103          *
 104          * This doesn't mean it won't be sync'd, we just won't be paranoid
 105          * about it being an _exact_ copy
 106          *
 107          * We don't need it to be exact, since we throw it away and regenerate
 108          * from our peers whenever a new DC is elected anyway
 109          *
 110          * Importantly, this reduces the amount of XML to copy+export as
 111          * well as the amount of data for MD5 needs to operate on
 112          */
 113 
 114     } else {
 115         pcmk__xml2text(source, (do_filter? xml_log_option_filtered : 0),
 116                        &buffer, &offset, &max, 0);
 117     }
 118 
 119     CRM_ASSERT(buffer != NULL);
 120     digest = crm_md5sum(buffer);
 121 
 122     if (digest_cs == NULL) {
 123         digest_cs = qb_log_callsite_get(__func__, __FILE__, "cib-digest", LOG_TRACE, __LINE__,
 124                                         crm_trace_nonlog);
 125     }
 126     if (digest_cs && digest_cs->targets) {
 127         char *trace_file = crm_strdup_printf("%s/digest-%s",
 128                                              pcmk__get_tmpdir(), digest);
 129 
 130         crm_trace("Saving %s.%s.%s to %s",
 131                   crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
 132                   crm_element_value(source, XML_ATTR_GENERATION),
 133                   crm_element_value(source, XML_ATTR_NUMUPDATES), trace_file);
 134         save_xml_to_file(source, "digest input", trace_file);
 135         free(trace_file);
 136     }
 137 
 138     free(buffer);
 139     crm_trace("End digest");
 140     return digest;
 141 }
 142 
 143 /*!
 144  * \brief Calculate and return digest of XML tree, suitable for storing on disk
 145  *
 146  * \param[in] input Root of XML to digest
 147  *
 148  * \return Newly allocated string containing digest
 149  */
 150 char *
 151 calculate_on_disk_digest(xmlNode * input)
     /* [previous][next][first][last][top][bottom][index][help] */
 152 {
 153     /* Always use the v1 format for on-disk digests
 154      * a) it's a compatibility nightmare
 155      * b) we only use this once at startup, all other
 156      *    invocations are in a separate child process
 157      */
 158     return calculate_xml_digest_v1(input, FALSE, FALSE);
 159 }
 160 
 161 /*!
 162  * \brief Calculate and return digest of XML operation
 163  *
 164  * \param[in] input Root of XML to digest
 165  * \param[in] version Not used
 166  *
 167  * \return Newly allocated string containing digest
 168  */
 169 char *
 170 calculate_operation_digest(xmlNode *input, const char *version)
     /* [previous][next][first][last][top][bottom][index][help] */
 171 {
 172     /* We still need the sorting for operation digests */
 173     return calculate_xml_digest_v1(input, TRUE, FALSE);
 174 }
 175 
 176 /*!
 177  * \brief Calculate and return digest of XML tree
 178  *
 179  * \param[in] input Root of XML to digest
 180  * \param[in] sort Whether to sort XML before calculating digest
 181  * \param[in] do_filter Whether to filter certain XML attributes
 182  * \param[in] version CRM feature set version (used to select v1/v2 digest)
 183  *
 184  * \return Newly allocated string containing digest
 185  */
 186 char *
 187 calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
     /* [previous][next][first][last][top][bottom][index][help] */
 188                                const char *version)
 189 {
 190     /*
 191      * @COMPAT digests (on-disk or in diffs/patchsets) created <1.1.4;
 192      * removing this affects even full-restart upgrades from old versions
 193      *
 194      * The sorting associated with v1 digest creation accounted for 23% of
 195      * the CIB manager's CPU usage on the server. v2 drops this.
 196      *
 197      * The filtering accounts for an additional 2.5% and we may want to
 198      * remove it in future.
 199      *
 200      * v2 also uses the xmlBuffer contents directly to avoid additional copying
 201      */
 202     if (version == NULL || compare_version("3.0.5", version) > 0) {
 203         crm_trace("Using v1 digest algorithm for %s", crm_str(version));
 204         return calculate_xml_digest_v1(input, sort, do_filter);
 205     }
 206     crm_trace("Using v2 digest algorithm for %s", crm_str(version));
 207     return calculate_xml_digest_v2(input, do_filter);
 208 }
 209 
 210 /*!
 211  * \internal
 212  * \brief Check whether calculated digest of given XML matches expected digest
 213  *
 214  * \param[in] input     Root of XML tree to digest
 215  * \param[in] expected  Expected digest in on-disk format
 216  *
 217  * \return true if digests match, false on mismatch or error
 218  */
 219 bool
 220 pcmk__verify_digest(xmlNode *input, const char *expected)
     /* [previous][next][first][last][top][bottom][index][help] */
 221 {
 222     char *calculated = NULL;
 223     bool passed;
 224 
 225     if (input != NULL) {
 226         calculated = calculate_on_disk_digest(input);
 227         if (calculated == NULL) {
 228             crm_perror(LOG_ERR, "Could not calculate digest for comparison");
 229             return false;
 230         }
 231     }
 232     passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
 233     if (passed) {
 234         crm_trace("Digest comparison passed: %s", calculated);
 235     } else {
 236         crm_err("Digest comparison failed: expected %s, calculated %s",
 237                 expected, calculated);
 238     }
 239     free(calculated);
 240     return passed;
 241 }
 242 
 243 /*!
 244  * \internal
 245  * \brief Check whether an XML attribute should be excluded from CIB digests
 246  *
 247  * \param[in] name  XML attribute name
 248  *
 249  * \return true if XML attribute should be excluded from CIB digest calculation
 250  */
 251 bool
 252 pcmk__xa_filterable(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 253 {
 254     static const char *filter[] = {
 255         XML_ATTR_ORIGIN,
 256         XML_CIB_ATTR_WRITTEN,
 257         XML_ATTR_UPDATE_ORIG,
 258         XML_ATTR_UPDATE_CLIENT,
 259         XML_ATTR_UPDATE_USER,
 260     };
 261 
 262     for (int i = 0; i < PCMK__NELEM(filter); i++) {
 263         if (strcmp(name, filter[i]) == 0) {
 264             return true;
 265         }
 266     }
 267     return false;
 268 }
 269 
 270 char *
 271 crm_md5sum(const char *buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 272 {
 273     int lpc = 0, len = 0;
 274     char *digest = NULL;
 275     unsigned char raw_digest[MD5_DIGEST_SIZE];
 276 
 277     if (buffer == NULL) {
 278         buffer = "";
 279     }
 280     len = strlen(buffer);
 281 
 282     crm_trace("Beginning digest of %d bytes", len);
 283     digest = malloc(2 * MD5_DIGEST_SIZE + 1);
 284     if (digest) {
 285         md5_buffer(buffer, len, raw_digest);
 286         for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
 287             sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
 288         }
 289         digest[(2 * MD5_DIGEST_SIZE)] = 0;
 290         crm_trace("Digest %s.", digest);
 291 
 292     } else {
 293         crm_err("Could not create digest");
 294     }
 295     return digest;
 296 }

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