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
|