pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
digest.c
Go to the documentation of this file.
1/*
2 * Copyright 2015-2025 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 <stdbool.h>
13#include <stdio.h>
14#include <unistd.h>
15#include <string.h>
16#include <stdlib.h>
17
18#include <glib.h> // GString, etc.
19#include <gnutls/crypto.h> // gnutls_hash_fast(), gnutls_hash_get_len()
20#include <gnutls/gnutls.h> // gnutls_strerror()
21
22#include <crm/crm.h>
23#include <crm/common/xml.h>
24#include "crmcommon_private.h"
25
26#define BEST_EFFORT_STATUS 0
27
28/*
29 * Pacemaker uses digests (MD5 hashes) of stringified XML to detect changes in
30 * the CIB as a whole, a particular resource's agent parameters, and the device
31 * parameters last used to unfence a particular node.
32 *
33 * "v2" digests hash pcmk__xml_string() directly, while less efficient "v1"
34 * digests do the same with a prefixed space, suffixed newline, and optional
35 * pre-sorting.
36 *
37 * On-disk CIB digests use v1 without sorting.
38 *
39 * Operation digests use v1 with sorting, and are stored in a resource's
40 * operation history in the CIB status section. They come in three flavors:
41 * - a digest of (nearly) all resource parameters and options, used to detect
42 * any resource configuration change;
43 * - a digest of resource parameters marked as nonreloadable, used to decide
44 * whether a reload or full restart is needed after a configuration change;
45 * - and a digest of resource parameters not marked as private, used in
46 * simulations where private parameters have been removed from the input.
47 *
48 * Unfencing digests are set as node attributes, and are used to require
49 * that nodes be unfenced again after a device's configuration changes.
50 */
51
60static GString *
61dump_xml_for_digest(xmlNodePtr xml)
62{
63 GString *buffer = g_string_sized_new(1024);
64
65 /* for compatibility with the old result which is used for v1 digests */
66 g_string_append_c(buffer, ' ');
67 pcmk__xml_string(xml, 0, buffer, 0);
68 g_string_append_c(buffer, '\n');
69
70 return buffer;
71}
72
83static char *
84calculate_xml_digest_v1(xmlNode *input)
85{
86 GString *buffer = dump_xml_for_digest(input);
87 char *digest = NULL;
88
89 // buffer->len > 2 for initial space and trailing newline
90 CRM_CHECK(buffer->len > 2,
91 g_string_free(buffer, TRUE);
92 return NULL);
93
94 digest = crm_md5sum((const char *) buffer->str);
95 crm_log_xml_trace(input, "digest:source");
96
97 g_string_free(buffer, TRUE);
98 return digest;
99}
100
109char *
111{
112 /* Always use the v1 format for on-disk digests.
113 * * Switching to v2 affects even full-restart upgrades, so it would be a
114 * compatibility nightmare.
115 * * We only use this once at startup. All other invocations are in a
116 * separate child process.
117 */
118 return calculate_xml_digest_v1(input);
119}
120
132char *
134{
135 /* Switching to v2 digests would likely cause restarts during rolling
136 * upgrades.
137 *
138 * @TODO Confirm this. Switch to v2 if safe, or drop this TODO otherwise.
139 */
140 xmlNode *sorted = pcmk__xml_copy(NULL, input);
141 char *digest = NULL;
142
143 pcmk__xe_sort_attrs(sorted);
144 digest = calculate_xml_digest_v1(sorted);
145
146 pcmk__xml_free(sorted);
147 return digest;
148}
149
159char *
160pcmk__digest_xml(xmlNode *xml, bool filter)
161{
162 /* @TODO Filtering accounts for significant CPU usage. Consider removing if
163 * possible.
164 */
165 char *digest = NULL;
166 GString *buf = g_string_sized_new(1024);
167
168 pcmk__xml_string(xml, (filter? pcmk__xml_fmt_filtered : 0), buf, 0);
169 digest = crm_md5sum(buf->str);
170
172 {
173 char *trace_file = crm_strdup_printf("%s/digest-%s",
174 pcmk__get_tmpdir(), digest);
175
176 crm_trace("Saving %s.%s.%s to %s",
180 trace_file);
181 save_xml_to_file(xml, "digest input", trace_file);
182 free(trace_file);
183 },
184 {}
185 );
186 g_string_free(buf, TRUE);
187 return digest;
188}
189
199bool
200pcmk__verify_digest(xmlNode *input, const char *expected)
201{
202 char *calculated = NULL;
203 bool passed;
204
205 if (input != NULL) {
206 calculated = pcmk__digest_on_disk_cib(input);
207 if (calculated == NULL) {
208 crm_perror(LOG_ERR, "Could not calculate digest for comparison");
209 return false;
210 }
211 }
212 passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
213 if (passed) {
214 crm_trace("Digest comparison passed: %s", calculated);
215 } else {
216 crm_err("Digest comparison failed: expected %s, calculated %s",
217 expected, calculated);
218 }
219 free(calculated);
220 return passed;
221}
222
231bool
233{
234 static const char *filter[] = {
240 };
241
242 for (int i = 0; i < PCMK__NELEM(filter); i++) {
243 if (strcmp(name, filter[i]) == 0) {
244 return true;
245 }
246 }
247 return false;
248}
249
250char *
251crm_md5sum(const char *buffer)
252{
253 char *digest = NULL;
254 gchar *raw_digest = NULL;
255
256 if (buffer == NULL) {
257 return NULL;
258 }
259
260 raw_digest = g_compute_checksum_for_string(G_CHECKSUM_MD5, buffer, -1);
261
262 if (raw_digest == NULL) {
263 crm_err("Failed to calculate hash");
264 return NULL;
265 }
266
267 digest = pcmk__str_copy(raw_digest);
268 g_free(raw_digest);
269
270 crm_trace("Digest %s.", digest);
271 return digest;
272}
273
274// Return true if a is an attribute that should be filtered
275static bool
276should_filter_for_digest(xmlAttrPtr a, void *user_data)
277{
278 if (strncmp((const char *) a->name, CRM_META "_",
279 sizeof(CRM_META " ") - 1) == 0) {
280 return true;
281 }
282 return pcmk__str_any_of((const char *) a->name,
288 "pcmk_external_ip",
289 NULL);
290}
291
298void
299pcmk__filter_op_for_digest(xmlNode *param_set)
300{
301 char *key = NULL;
302 char *timeout = NULL;
303 guint interval_ms = 0;
304
305 if (param_set == NULL) {
306 return;
307 }
308
309 /* Timeout is useful for recurring operation digests, so grab it before
310 * removing meta-attributes
311 */
313 if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
314 interval_ms = 0;
315 }
316 free(key);
317 key = NULL;
318 if (interval_ms != 0) {
320 timeout = crm_element_value_copy(param_set, key);
321 }
322
323 // Remove all CRM_meta_* attributes and certain other attributes
324 pcmk__xe_remove_matching_attrs(param_set, false, should_filter_for_digest,
325 NULL);
326
327 // Add timeout back for recurring operation digests
328 if (timeout != NULL) {
329 crm_xml_add(param_set, key, timeout);
330 }
331 free(timeout);
332 free(key);
333}
334
335// Deprecated functions kept only for backward API compatibility
336// LCOV_EXCL_START
337
340
341char *
343{
344 return calculate_xml_digest_v1(input);
345}
346
347char *
349{
350 xmlNode *sorted = sorted_xml(input, NULL, true);
351 char *digest = calculate_xml_digest_v1(sorted);
352
353 pcmk__xml_free(sorted);
354 return digest;
355}
356
357char *
358calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
359 gboolean do_filter, const char *version)
360{
361 if ((version == NULL) || (compare_version("3.0.5", version) > 0)) {
362 xmlNode *sorted = NULL;
363 char *digest = NULL;
364
365 if (sort) {
366 xmlNode *sorted = sorted_xml(input, NULL, true);
367
368 input = sorted;
369 }
370
371 crm_trace("Using v1 digest algorithm for %s",
372 pcmk__s(version, "unknown feature set"));
373
374 digest = calculate_xml_digest_v1(input);
375
376 pcmk__xml_free(sorted);
377 return digest;
378 }
379 crm_trace("Using v2 digest algorithm for %s", version);
380 return pcmk__digest_xml(input, do_filter);
381}
382
383// LCOV_EXCL_STOP
384// End deprecated API
const char * name
Definition cib.c:26
#define PCMK__NELEM(a)
Definition internal.h:50
uint32_t version
Definition remote.c:1
int compare_version(const char *version1, const char *version2)
Definition utils.c:206
A dumping ground.
#define CRM_META
Definition crm.h:75
void pcmk__filter_op_for_digest(xmlNode *param_set)
Definition digest.c:299
char * crm_md5sum(const char *buffer)
Definition digest.c:251
char * pcmk__digest_xml(xmlNode *xml, bool filter)
Definition digest.c:160
char * calculate_operation_digest(xmlNode *input, const char *version)
Definition digest.c:348
bool pcmk__xa_filterable(const char *name)
Definition digest.c:232
char * calculate_on_disk_digest(xmlNode *input)
Definition digest.c:342
char * pcmk__digest_operation(xmlNode *input)
Definition digest.c:133
char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version)
Definition digest.c:358
char * pcmk__digest_on_disk_cib(xmlNode *input)
Definition digest.c:110
bool pcmk__verify_digest(xmlNode *input, const char *expected)
Definition digest.c:200
const char * pcmk__get_tmpdir(void)
Definition io.c:548
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:299
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define crm_log_xml_trace(xml, text)
Definition logging.h:378
#define crm_trace(fmt, args...)
Definition logging.h:370
#define pcmk__if_tracing(if_action, else_action)
xmlNode * input
char * crm_meta_name(const char *field)
Get the environment variable equivalent of a meta-attribute name.
Definition nvpair.c:525
#define PCMK_META_INTERVAL
Definition options.h:92
#define PCMK_META_TIMEOUT
Definition options.h:115
#define PCMK__META_ON_NODE_UUID
#define PCMK__META_ON_NODE
unsigned int timeout
Definition pcmk_fence.c:34
#define pcmk_ok
Definition results.h:65
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
@ pcmk__str_casei
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1053
#define pcmk__str_copy(str)
Wrappers for and extensions to libxml2.
Deprecated Pacemaker XML API.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
Retrieve the millisecond value of an XML attribute.
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Deprecated Pacemaker XML element API.
void xmlNode * sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool force, bool(*match)(xmlAttrPtr, void *), void *user_data)
void pcmk__xe_sort_attrs(xmlNode *xml)
@ pcmk__xml_fmt_filtered
Exclude certain XML attributes (for calculating digests)
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition xml.c:832
void pcmk__xml_free(xmlNode *xml)
Definition xml.c:816
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition xml_io.c:604
void pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, int depth)
Definition xml_io.c:370
#define PCMK_XA_UPDATE_ORIGIN
Definition xml_names.h:437
#define PCMK_XA_CIB_LAST_WRITTEN
Definition xml_names.h:244
#define PCMK_XA_EPOCH
Definition xml_names.h:268
#define PCMK_XA_ID
Definition xml_names.h:301
#define PCMK_XA_ADMIN_EPOCH
Definition xml_names.h:232
#define PCMK_XA_CRM_FEATURE_SET
Definition xml_names.h:254
#define PCMK_XA_CRM_DEBUG_ORIGIN
Definition xml_names.h:253
#define PCMK_XA_UPDATE_USER
Definition xml_names.h:438
#define PCMK_XA_NUM_UPDATES
Definition xml_names.h:341
#define PCMK_XA_UPDATE_CLIENT
Definition xml_names.h:436
#define PCMK__XA_OP_DIGEST