1 /*
2 * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <crm_internal.h>
20
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include <crm/crm.h>
27 #include <crm/msg_xml.h>
28 #include <crm/common/xml.h>
29
30 #define BEST_EFFORT_STATUS 0
31
32 /*!
33 * \brief Dump XML in a format used with v1 digests
34 *
35 * \param[in] an_xml_node Root of XML to dump
36 *
37 * \return Newly allocated buffer containing dumped XML
38 */
39 static char *
40 dump_xml_for_digest(xmlNode * an_xml_node)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
41 {
42 char *buffer = NULL;
43 int offset = 0, max = 0;
44
45 /* for compatibility with the old result which is used for v1 digests */
46 crm_buffer_add_char(&buffer, &offset, &max, ' ');
47 crm_xml_dump(an_xml_node, 0, &buffer, &offset, &max, 0);
48 crm_buffer_add_char(&buffer, &offset, &max, '\n');
49
50 return buffer;
51 }
52
53 /*!
54 * \brief Calculate and return v1 digest of XML tree
55 *
56 * \param[in] input Root of XML to digest
57 * \param[in] sort Whether to sort the XML before calculating digest
58 * \param[in] ignored Not used
59 *
60 * \return Newly allocated string containing digest
61 * \note Example return value: "c048eae664dba840e1d2060f00299e9d"
62 */
63 static char *
64 calculate_xml_digest_v1(xmlNode * input, gboolean sort, gboolean ignored)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
65 {
66 char *digest = NULL;
67 char *buffer = NULL;
68 xmlNode *copy = NULL;
69
70 if (sort) {
71 crm_trace("Sorting xml...");
72 copy = sorted_xml(input, NULL, TRUE);
73 crm_trace("Done");
74 input = copy;
75 }
76
77 buffer = dump_xml_for_digest(input);
78 CRM_CHECK(buffer != NULL && strlen(buffer) > 0, free_xml(copy);
79 free(buffer);
80 return NULL);
81
82 digest = crm_md5sum(buffer);
83 crm_log_xml_trace(input, "digest:source");
84
85 free(buffer);
86 free_xml(copy);
87 return digest;
88 }
89
90 /*!
91 * \brief Calculate and return v2 digest of XML tree
92 *
93 * \param[in] source Root of XML to digest
94 * \param[in] do_filter Whether to filter certain XML attributes
95 *
96 * \return Newly allocated string containing digest
97 */
98 static char *
99 calculate_xml_digest_v2(xmlNode * source, gboolean do_filter)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
100 {
101 char *digest = NULL;
102 char *buffer = NULL;
103 int offset, max;
104
105 static struct qb_log_callsite *digest_cs = NULL;
106
107 crm_trace("Begin digest %s", do_filter?"filtered":"");
108 if (do_filter && BEST_EFFORT_STATUS) {
109 /* Exclude the status calculation from the digest
110 *
111 * This doesn't mean it won't be sync'd, we just won't be paranoid
112 * about it being an _exact_ copy
113 *
114 * We don't need it to be exact, since we throw it away and regenerate
115 * from our peers whenever a new DC is elected anyway
116 *
117 * Importantly, this reduces the amount of XML to copy+export as
118 * well as the amount of data for MD5 needs to operate on
119 */
120
121 } else {
122 crm_xml_dump(source, do_filter ? xml_log_option_filtered : 0, &buffer, &offset, &max, 0);
123 }
124
125 CRM_ASSERT(buffer != NULL);
126 digest = crm_md5sum(buffer);
127
128 if (digest_cs == NULL) {
129 digest_cs = qb_log_callsite_get(__func__, __FILE__, "cib-digest", LOG_TRACE, __LINE__,
130 crm_trace_nonlog);
131 }
132 if (digest_cs && digest_cs->targets) {
133 char *trace_file = crm_concat("/tmp/digest", digest, '-');
134
135 crm_trace("Saving %s.%s.%s to %s",
136 crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
137 crm_element_value(source, XML_ATTR_GENERATION),
138 crm_element_value(source, XML_ATTR_NUMUPDATES), trace_file);
139 save_xml_to_file(source, "digest input", trace_file);
140 free(trace_file);
141 }
142
143 free(buffer);
144 crm_trace("End digest");
145 return digest;
146 }
147
148 /*!
149 * \brief Calculate and return digest of XML tree, suitable for storing on disk
150 *
151 * \param[in] input Root of XML to digest
152 *
153 * \return Newly allocated string containing digest
154 */
155 char *
156 calculate_on_disk_digest(xmlNode * input)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
157 {
158 /* Always use the v1 format for on-disk digests
159 * a) it's a compatibility nightmare
160 * b) we only use this once at startup, all other
161 * invocations are in a separate child process
162 */
163 return calculate_xml_digest_v1(input, FALSE, FALSE);
164 }
165
166 /*!
167 * \brief Calculate and return digest of XML operation
168 *
169 * \param[in] input Root of XML to digest
170 * \param[in] version Not used
171 *
172 * \return Newly allocated string containing digest
173 */
174 char *
175 calculate_operation_digest(xmlNode *input, const char *version)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
176 {
177 /* We still need the sorting for operation digests */
178 return calculate_xml_digest_v1(input, TRUE, FALSE);
179 }
180
181 /*!
182 * \brief Calculate and return digest of XML tree
183 *
184 * \param[in] input Root of XML to digest
185 * \param[in] sort Whether to sort XML before calculating digest
186 * \param[in] do_filter Whether to filter certain XML attributes
187 * \param[in] version CRM feature set version (used to select v1/v2 digest)
188 *
189 * \return Newly allocated string containing digest
190 */
191 char *
192 calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
193 const char *version)
194 {
195 /*
196 * The sorting associated with v1 digest creation accounted for 23% of
197 * the CIB's CPU usage on the server. v2 drops this.
198 *
199 * The filtering accounts for an additional 2.5% and we may want to
200 * remove it in future.
201 *
202 * v2 also uses the xmlBuffer contents directly to avoid additional copying
203 */
204 if (version == NULL || compare_version("3.0.5", version) > 0) {
205 crm_trace("Using v1 digest algorithm for %s", crm_str(version));
206 return calculate_xml_digest_v1(input, sort, do_filter);
207 }
208 crm_trace("Using v2 digest algorithm for %s", crm_str(version));
209 return calculate_xml_digest_v2(input, do_filter);
210 }
211
212 /*!
213 * \internal
214 * \brief Return whether calculated digest of XML tree matches expected digest
215 *
216 * \param[in] input Root of XML to digest
217 * \param[in] expected Expected digest in on-disk format
218 *
219 * \return TRUE if digests match, FALSE otherwise or on error
220 */
221 gboolean
222 crm_digest_verify(xmlNode *input, const char *expected)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
223 {
224 char *calculated = NULL;
225 gboolean passed;
226
227 if (input != NULL) {
228 calculated = calculate_on_disk_digest(input);
229 if (calculated == NULL) {
230 crm_perror(LOG_ERR, "Could not calculate digest for comparison");
231 return FALSE;
232 }
233 }
234 passed = safe_str_eq(expected, calculated);
235 if (passed) {
236 crm_trace("Digest comparison passed: %s", calculated);
237 } else {
238 crm_err("Digest comparison failed: expected %s, calculated %s",
239 expected, calculated);
240 }
241 free(calculated);
242 return passed;
243 }