Coverage Report

Created: 2026-01-03 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/patchset.c
Line
Count
Source
1
/*
2
 * Copyright 2004-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 <sys/types.h>
15
#include <unistd.h>
16
#include <time.h>
17
#include <string.h>
18
#include <stdlib.h>
19
#include <stdarg.h>
20
#include <bzlib.h>
21
22
#include <libxml/tree.h>                // xmlNode
23
24
#include <crm/crm.h>
25
#include <crm/common/cib_internal.h>
26
#include <crm/common/xml.h>
27
#include <crm/common/xml_internal.h>  // CRM_XML_LOG_BASE, etc.
28
#include "crmcommon_private.h"
29
30
static const char *const vfields[] = {
31
    PCMK_XA_ADMIN_EPOCH,
32
    PCMK_XA_EPOCH,
33
    PCMK_XA_NUM_UPDATES,
34
};
35
36
/* Add changes for specified XML to patchset.
37
 * For patchset format, refer to diff schema.
38
 */
39
static void
40
add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
41
0
{
42
0
    xmlNode *cIter = NULL;
43
0
    xmlAttr *pIter = NULL;
44
0
    xmlNode *change = NULL;
45
0
    xml_node_private_t *nodepriv = xml->_private;
46
0
    const char *value = NULL;
47
48
0
    if (nodepriv == NULL) {
49
        /* Elements that shouldn't occur in a CIB don't have _private set. They
50
         * should be stripped out, ignored, or have an error thrown by any code
51
         * that processes their parent, so we ignore any changes to them.
52
         */
53
0
        return;
54
0
    }
55
56
    // If this XML node is new, just report that
57
0
    if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
58
0
        GString *xpath = pcmk__element_xpath(xml->parent);
59
60
0
        if (xpath != NULL) {
61
0
            int position = pcmk__xml_position(xml, pcmk__xf_deleted);
62
63
0
            change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
64
65
0
            pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE);
66
0
            pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str);
67
0
            pcmk__xe_set_int(change, PCMK_XE_POSITION, position);
68
0
            pcmk__xml_copy(change, xml);
69
0
            g_string_free(xpath, TRUE);
70
0
        }
71
72
0
        return;
73
0
    }
74
75
    // Check each of the XML node's attributes for changes
76
0
    for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
77
0
         pIter = pIter->next) {
78
0
        xmlNode *attr = NULL;
79
80
0
        nodepriv = pIter->_private;
81
0
        if (!pcmk__any_flags_set(nodepriv->flags,
82
0
                                 pcmk__xf_deleted|pcmk__xf_dirty)) {
83
0
            continue;
84
0
        }
85
86
0
        if (change == NULL) {
87
0
            GString *xpath = pcmk__element_xpath(xml);
88
89
0
            if (xpath != NULL) {
90
0
                change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
91
92
0
                pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY);
93
0
                pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str);
94
95
0
                change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
96
0
                g_string_free(xpath, TRUE);
97
0
            }
98
0
        }
99
100
0
        attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR);
101
102
0
        pcmk__xe_set(attr, PCMK_XA_NAME, (const char *) pIter->name);
103
0
        if (nodepriv->flags & pcmk__xf_deleted) {
104
0
            pcmk__xe_set(attr, PCMK_XA_OPERATION, "unset");
105
106
0
        } else {
107
0
            pcmk__xe_set(attr, PCMK_XA_OPERATION, "set");
108
109
0
            value = pcmk__xml_attr_value(pIter);
110
0
            pcmk__xe_set(attr, PCMK_XA_VALUE, value);
111
0
        }
112
0
    }
113
114
0
    if (change) {
115
0
        xmlNode *result = NULL;
116
117
0
        change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
118
0
        result = pcmk__xe_create(change, (const char *)xml->name);
119
120
0
        for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
121
0
             pIter = pIter->next) {
122
0
            nodepriv = pIter->_private;
123
0
            if (!pcmk__is_set(nodepriv->flags, pcmk__xf_deleted)) {
124
0
                value = pcmk__xe_get(xml, (const char *) pIter->name);
125
0
                pcmk__xe_set(result, (const char *)pIter->name, value);
126
0
            }
127
0
        }
128
0
    }
129
130
    // Now recursively do the same for each child node of this node
131
0
    for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
132
0
         cIter = pcmk__xml_next(cIter)) {
133
0
        add_xml_changes_to_patchset(cIter, patchset);
134
0
    }
135
136
0
    nodepriv = xml->_private;
137
0
    if ((patchset != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_moved)) {
138
0
        GString *xpath = pcmk__element_xpath(xml);
139
140
0
        pcmk__trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml),
141
0
                    pcmk__xml_position(xml, pcmk__xf_skip));
142
143
0
        if (xpath != NULL) {
144
0
            change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
145
146
0
            pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE);
147
0
            pcmk__xe_set(change, PCMK_XA_PATH, (const char *) xpath->str);
148
0
            pcmk__xe_set_int(change, PCMK_XE_POSITION,
149
0
                             pcmk__xml_position(xml, pcmk__xf_deleted));
150
0
            g_string_free(xpath, TRUE);
151
0
        }
152
0
    }
153
0
}
154
155
static bool
156
is_config_change(xmlNode *xml)
157
0
{
158
0
    GList *gIter = NULL;
159
0
    xml_node_private_t *nodepriv = NULL;
160
0
    xml_doc_private_t *docpriv;
161
0
    xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
162
0
                                           NULL);
163
164
0
    if (config) {
165
0
        nodepriv = config->_private;
166
0
    }
167
0
    if ((nodepriv != NULL) && pcmk__is_set(nodepriv->flags, pcmk__xf_dirty)) {
168
0
        return TRUE;
169
0
    }
170
171
0
    if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
172
0
        docpriv = xml->doc->_private;
173
0
        for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
174
0
            pcmk__deleted_xml_t *deleted_obj = gIter->data;
175
176
0
            if (strstr(deleted_obj->path,
177
0
                       "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
178
0
                return TRUE;
179
0
            }
180
0
        }
181
0
    }
182
0
    return FALSE;
183
0
}
184
185
static xmlNode *
186
xml_create_patchset_v2(xmlNode *source, xmlNode *target)
187
0
{
188
0
    int lpc = 0;
189
0
    GList *gIter = NULL;
190
0
    xml_doc_private_t *docpriv;
191
192
0
    xmlNode *v = NULL;
193
0
    xmlNode *version = NULL;
194
0
    xmlNode *patchset = NULL;
195
196
0
    pcmk__assert(target != NULL);
197
198
0
    if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
199
0
        return NULL;
200
0
    }
201
202
0
    pcmk__assert(target->doc != NULL);
203
0
    docpriv = target->doc->_private;
204
205
0
    patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
206
0
    pcmk__xe_set_int(patchset, PCMK_XA_FORMAT, 2);
207
208
0
    version = pcmk__xe_create(patchset, PCMK_XE_VERSION);
209
210
0
    v = pcmk__xe_create(version, PCMK_XE_SOURCE);
211
0
    for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
212
0
        const char *value = pcmk__xe_get(source, vfields[lpc]);
213
214
0
        if (value == NULL) {
215
0
            value = "1";
216
0
        }
217
0
        pcmk__xe_set(v, vfields[lpc], value);
218
0
    }
219
220
0
    v = pcmk__xe_create(version, PCMK_XE_TARGET);
221
0
    for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
222
0
        const char *value = pcmk__xe_get(target, vfields[lpc]);
223
224
0
        if (value == NULL) {
225
0
            value = "1";
226
0
        }
227
0
        pcmk__xe_set(v, vfields[lpc], value);
228
0
    }
229
230
0
    for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
231
0
        pcmk__deleted_xml_t *deleted_obj = gIter->data;
232
0
        xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
233
234
0
        pcmk__xe_set(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE);
235
0
        pcmk__xe_set(change, PCMK_XA_PATH, deleted_obj->path);
236
0
        if (deleted_obj->position >= 0) {
237
0
            pcmk__xe_set_int(change, PCMK_XE_POSITION, deleted_obj->position);
238
0
        }
239
0
    }
240
241
0
    add_xml_changes_to_patchset(target, patchset);
242
0
    return patchset;
243
0
}
244
245
xmlNode *
246
xml_create_patchset(int format, xmlNode *source, xmlNode *target,
247
                    bool *config_changed, bool manage_version)
248
0
{
249
0
    bool local_config_changed = false;
250
251
0
    if (format == 0) {
252
0
        format = 2;
253
0
    }
254
0
    if (format != 2) {
255
0
        pcmk__err("Unknown patch format: %d", format);
256
0
        return NULL;
257
0
    }
258
259
0
    xml_acl_disable(target);
260
0
    if ((target == NULL)
261
0
        || !pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
262
263
0
        pcmk__trace("No change %d", format);
264
0
        return NULL;
265
0
    }
266
267
0
    if (config_changed == NULL) {
268
0
        config_changed = &local_config_changed;
269
0
    }
270
0
    *config_changed = is_config_change(target);
271
272
0
    if (manage_version) {
273
0
        int counter = 0;
274
275
0
        if (*config_changed) {
276
0
            pcmk__xe_set(target, PCMK_XA_NUM_UPDATES, "0");
277
278
0
            pcmk__xe_get_int(target, PCMK_XA_EPOCH, &counter);
279
0
            pcmk__xe_set_int(target, PCMK_XA_EPOCH, counter + 1);
280
281
0
        } else {
282
0
            pcmk__xe_get_int(target, PCMK_XA_NUM_UPDATES, &counter);
283
0
            pcmk__xe_set_int(target, PCMK_XA_NUM_UPDATES, counter + 1);
284
0
        }
285
0
    }
286
287
0
    return xml_create_patchset_v2(source, target);
288
0
}
289
290
/*!
291
 * \internal
292
 * \brief Add a digest of a patchset's target XML to the patchset
293
 *
294
 * \param[in,out] patchset  XML patchset
295
 * \param[in]     target    Target XML
296
 */
297
void
298
pcmk__xml_patchset_add_digest(xmlNode *patchset, const xmlNode *target)
299
0
{
300
0
    char *digest = NULL;
301
302
0
    CRM_CHECK((patchset != NULL) && (target != NULL), return);
303
304
    /* If tracking is enabled and the document is dirty, we could get an
305
     * incorrect digest. Call pcmk__xml_commit_changes() before calling this.
306
     */
307
0
    CRM_CHECK(!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty),
308
0
              return);
309
310
0
    digest = pcmk__digest_xml(target, true);
311
312
0
    pcmk__xe_set(patchset, PCMK_XA_DIGEST, digest);
313
0
    free(digest);
314
0
}
315
316
/*!
317
 * \internal
318
 * \brief Get the source and target CIB versions from an XML patchset
319
 *
320
 * Each output object will contain, in order, the following version fields from
321
 * the source and target, respectively:
322
 * * \c PCMK_XA_ADMIN_EPOCH
323
 * * \c PCMK_XA_EPOCH
324
 * * \c PCMK_XA_NUM_UPDATES
325
 *
326
 * If source versions or target versions are absent from the patchset, then
327
 * \p source and \p target (respectively) are left unmodified. This is not
328
 * treated as an error. An unparsable version is an error, however.
329
 *
330
 * \param[in]  patchset  XML patchset
331
 * \param[out] source    Where to store versions from source CIB
332
 * \param[out] target    Where to store versions from target CIB
333
 *
334
 * \return Standard Pacemaker return code
335
 */
336
int
337
pcmk__xml_patchset_versions(const xmlNode *patchset, int source[3],
338
                            int target[3])
339
0
{
340
0
    int format = 0;
341
0
    const xmlNode *version = NULL;
342
0
    const xmlNode *source_xml = NULL;
343
0
    const xmlNode *target_xml = NULL;
344
345
0
    CRM_CHECK((patchset != NULL) && (source != NULL) && (target != NULL),
346
0
              return EINVAL);
347
348
0
    pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
349
0
    if (format != 2) {
350
0
        pcmk__err("Unknown patch format: %d", format);
351
0
        return EINVAL;
352
0
    }
353
354
0
    version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL, NULL);
355
0
    source_xml = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL, NULL);
356
0
    target_xml = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL, NULL);
357
358
    /* @COMPAT Consider requiring source_xml and target_xml to be non-NULL. As
359
     * of pcs version 0.10.8, pcs creates a patchset using crm_diff
360
     * --no-version. The behavior and documentation of the crm_diff options
361
     * --cib and --no-version are questionable and should be re-examined. Even
362
     * without --no-version, crm_diff does not update the target version in the
363
     * generated patchset. So a diff based on a manual CIB XML edit is likely to
364
     * have unchanged version numbers. (Pacemaker tools bump the CIB versions
365
     * automatically when editing the CIB.)
366
     *
367
     * Until then, we may be applying a patchset that has no version info. We
368
     * will allow either source version or target version to be missing (even
369
     * though both should be present or both should be missing). However, return
370
     * an error if any of the three vfields is missing from a source or target
371
     * version element that is present. That level of sanity check should be
372
     * okay.
373
     *
374
     * We leave the destination arrays unmodified in case of absent versions,
375
     * instead of setting them to some default value like { 0, 0, 0 }.
376
     * xml_patch_version_check() sets its own defaults in case of absent
377
     * versions.
378
     */
379
0
    for (int i = 0; i < PCMK__NELEM(vfields); i++) {
380
0
        if (source_xml != NULL) {
381
0
            if (pcmk__xe_get_int(source_xml, vfields[i],
382
0
                                 &(source[i])) != pcmk_rc_ok) {
383
0
                return EINVAL;
384
0
            }
385
0
            pcmk__trace("Got source[%s]=%d", vfields[i], source[i]);
386
387
0
        } else {
388
0
            pcmk__trace("No source versions found; keeping source[%s]=%d",
389
0
                        vfields[i], source[i]);
390
0
        }
391
392
0
        if (target_xml != NULL) {
393
0
            if (pcmk__xe_get_int(target_xml, vfields[i],
394
0
                                 &(target[i])) != pcmk_rc_ok) {
395
0
                return EINVAL;
396
0
            }
397
0
            pcmk__trace("Got target[%s]=%d", vfields[i], target[i]);
398
399
0
        } else {
400
0
            pcmk__trace("No target versions found; keeping target[%s]=%d",
401
0
                        vfields[i], target[i]);
402
0
        }
403
0
    }
404
405
0
    return pcmk_rc_ok;
406
0
}
407
408
/*!
409
 * \internal
410
 * \brief Check whether patchset can be applied to current CIB
411
 *
412
 * \param[in] cib_root  Root of current CIB
413
 * \param[in] patchset  Patchset to check
414
 *
415
 * \return Standard Pacemaker return code
416
 */
417
static int
418
check_patchset_versions(const xmlNode *cib_root, const xmlNode *patchset)
419
0
{
420
0
    int current[] = { 0, 0, 0 };
421
0
    int source[] = { 0, 0, 0 };
422
0
    int target[] = { 0, 0, 0 };
423
0
    int rc = pcmk_rc_ok;
424
425
0
    for (int i = 0; i < PCMK__NELEM(vfields); i++) {
426
        /* @COMPAT We should probably fail with EINVAL for negative or invalid.
427
         *
428
         * Preserve behavior for xml_apply_patchset(). Use new behavior in a
429
         * future replacement.
430
         */
431
0
        if (pcmk__xe_get_int(cib_root, vfields[i],
432
0
                             &(current[i])) == pcmk_rc_ok) {
433
0
            pcmk__trace("Got %d for current[%s]%s", current[i], vfields[i],
434
0
                        ((current[i] < 0)? ", using 0" : ""));
435
0
        } else {
436
0
            pcmk__debug("Failed to get value for current[%s], using 0",
437
0
                        vfields[i]);
438
0
        }
439
0
        if (current[i] < 0) {
440
0
            current[i] = 0;
441
0
        }
442
0
    }
443
444
    /* Set some defaults in case nothing is present.
445
     *
446
     * @COMPAT We should probably skip this step, and fail immediately below if
447
     * target[i] < source[i].
448
     *
449
     * Preserve behavior for xml_apply_patchset(). Use new behavior in a future
450
     * replacement.
451
     */
452
0
    target[0] = current[0];
453
0
    target[1] = current[1];
454
0
    target[2] = current[2] + 1;
455
0
    for (int i = 0; i < PCMK__NELEM(vfields); i++) {
456
0
        source[i] = current[i];
457
0
    }
458
459
0
    rc = pcmk__xml_patchset_versions(patchset, source, target);
460
0
    if (rc != pcmk_rc_ok) {
461
0
        return rc;
462
0
    }
463
464
    // Ensure current version matches patchset source version
465
0
    for (int i = 0; i < PCMK__NELEM(vfields); i++) {
466
0
        if (current[i] < source[i]) {
467
0
            pcmk__debug("Current %s is too low "
468
0
                        "(%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
469
0
                        vfields[i], current[0], current[1], current[2],
470
0
                        source[0], source[1], source[2],
471
0
                        target[0], target[1], target[2]);
472
0
            return pcmk_rc_diff_resync;
473
0
        }
474
0
        if (current[i] > source[i]) {
475
0
            pcmk__info("Current %s is too high "
476
0
                       "(%d.%d.%d > %d.%d.%d --> %d.%d.%d)",
477
0
                       vfields[i], current[0], current[1], current[2],
478
0
                       source[0], source[1], source[2],
479
0
                       target[0], target[1], target[2]);
480
0
            pcmk__log_xml_info(patchset, "OldPatch");
481
0
            return pcmk_rc_old_data;
482
0
        }
483
0
    }
484
485
    // Ensure target version is newer than source version
486
0
    for (int i = 0; i < PCMK__NELEM(vfields); i++) {
487
0
        if (target[i] > source[i]) {
488
0
            pcmk__debug("Can apply patch %d.%d.%d to %d.%d.%d",
489
0
                        target[0], target[1], target[2],
490
0
                        current[0], current[1], current[2]);
491
0
            return pcmk_rc_ok;
492
0
        }
493
0
    }
494
495
0
    pcmk__notice("Versions did not change in patch %d.%d.%d",
496
0
                 target[0], target[1], target[2]);
497
0
    return pcmk_rc_old_data;
498
0
}
499
500
// Return first child matching element name and optionally id or position
501
static xmlNode *
502
first_matching_xml_child(const xmlNode *parent, const char *name,
503
                         const char *id, int position)
504
0
{
505
0
    xmlNode *cIter = NULL;
506
507
0
    for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
508
0
         cIter = pcmk__xml_next(cIter)) {
509
0
        if (strcmp((const char *) cIter->name, name) != 0) {
510
0
            continue;
511
0
        } else if (id) {
512
0
            const char *cid = pcmk__xe_id(cIter);
513
514
0
            if ((cid == NULL) || (strcmp(cid, id) != 0)) {
515
0
                continue;
516
0
            }
517
0
        }
518
519
        // "position" makes sense only for XML comments for now
520
0
        if ((cIter->type == XML_COMMENT_NODE)
521
0
            && (position >= 0)
522
0
            && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
523
0
            continue;
524
0
        }
525
526
0
        return cIter;
527
0
    }
528
0
    return NULL;
529
0
}
530
531
/*!
532
 * \internal
533
 * \brief Simplified, more efficient alternative to pcmk__xpath_find_one()
534
 *
535
 * \param[in] top              Root of XML to search
536
 * \param[in] key              Search xpath
537
 * \param[in] target_position  If deleting, where to delete
538
 *
539
 * \return XML child matching xpath if found, NULL otherwise
540
 *
541
 * \note This only works on simplified xpaths found in v2 patchset diffs,
542
 *       i.e. the only allowed search predicate is [@id='XXX'].
543
 */
544
static xmlNode *
545
search_v2_xpath(const xmlNode *top, const char *key, int target_position)
546
0
{
547
0
    xmlNode *target = (xmlNode *) top->doc;
548
0
    const char *current = key;
549
0
    char *section;
550
0
    char *remainder;
551
0
    char *id;
552
0
    char *tag;
553
0
    int rc;
554
0
    size_t key_len;
555
556
0
    CRM_CHECK(key != NULL, return NULL);
557
0
    key_len = strlen(key);
558
559
    /* These are scanned from key after a slash, so they can't be bigger
560
     * than key_len - 1 characters plus a null terminator.
561
     */
562
563
0
    remainder = pcmk__assert_alloc(key_len, sizeof(char));
564
0
    section = pcmk__assert_alloc(key_len, sizeof(char));
565
0
    id = pcmk__assert_alloc(key_len, sizeof(char));
566
0
    tag = pcmk__assert_alloc(key_len, sizeof(char));
567
568
0
    do {
569
        // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
570
0
        rc = sscanf(current, "/%[^/]%s", section, remainder);
571
0
        if (rc > 0) {
572
            // Separate FIRST_COMPONENT into TAG[@id='ID']
573
0
            int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
574
0
            int current_position = -1;
575
576
            /* The target position is for the final component tag, so only use
577
             * it if there is nothing left to search after this component.
578
             */
579
0
            if ((rc == 1) && (target_position >= 0)) {
580
0
                current_position = target_position;
581
0
            }
582
583
0
            switch (f) {
584
0
                case 1:
585
0
                    target = first_matching_xml_child(target, tag, NULL,
586
0
                                                      current_position);
587
0
                    break;
588
0
                case 2:
589
0
                    target = first_matching_xml_child(target, tag, id,
590
0
                                                      current_position);
591
0
                    break;
592
0
                default:
593
                    // This should not be possible
594
0
                    target = NULL;
595
0
                    break;
596
0
            }
597
0
            current = remainder;
598
0
        }
599
600
    // Continue if something remains to search, and we've matched so far
601
0
    } while ((rc == 2) && target);
602
603
0
    if (target) {
604
0
        pcmk__if_tracing(
605
0
            {
606
0
                char *path = (char *) xmlGetNodePath(target);
607
608
0
                pcmk__trace("Found %s for %s", path, key);
609
0
                free(path);
610
0
            },
611
0
            {}
612
0
        );
613
0
    } else {
614
0
        pcmk__debug("No match for %s", key);
615
0
    }
616
617
0
    free(remainder);
618
0
    free(section);
619
0
    free(tag);
620
0
    free(id);
621
0
    return target;
622
0
}
623
624
typedef struct xml_change_obj_s {
625
    const xmlNode *change;
626
    xmlNode *match;
627
} xml_change_obj_t;
628
629
static gint
630
sort_change_obj_by_position(gconstpointer a, gconstpointer b)
631
0
{
632
0
    const xml_change_obj_t *change_obj_a = a;
633
0
    const xml_change_obj_t *change_obj_b = b;
634
0
    int position_a = -1;
635
0
    int position_b = -1;
636
637
0
    pcmk__xe_get_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
638
0
    pcmk__xe_get_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
639
640
0
    if (position_a < position_b) {
641
0
        return -1;
642
643
0
    } else if (position_a > position_b) {
644
0
        return 1;
645
0
    }
646
647
0
    return 0;
648
0
}
649
650
/*!
651
 * \internal
652
 * \brief Apply a version 2 patchset to an XML node
653
 *
654
 * \param[in,out] xml       XML to apply patchset to
655
 * \param[in]     patchset  Patchset to apply
656
 *
657
 * \return Standard Pacemaker return code
658
 */
659
static int
660
apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
661
0
{
662
0
    int rc = pcmk_rc_ok;
663
0
    const xmlNode *change = NULL;
664
0
    GList *change_objs = NULL;
665
0
    GList *gIter = NULL;
666
667
0
    for (change = pcmk__xml_first_child(patchset); change != NULL;
668
0
         change = pcmk__xml_next(change)) {
669
0
        xmlNode *match = NULL;
670
0
        const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION);
671
0
        const char *xpath = pcmk__xe_get(change, PCMK_XA_PATH);
672
0
        int position = -1;
673
674
0
        if (op == NULL) {
675
0
            continue;
676
0
        }
677
678
0
        pcmk__trace("Processing %s %s", change->name, op);
679
680
        /* PCMK_VALUE_DELETE changes for XML comments are generated with
681
         * PCMK_XE_POSITION
682
         */
683
0
        if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
684
0
            pcmk__xe_get_int(change, PCMK_XE_POSITION, &position);
685
0
        }
686
0
        match = search_v2_xpath(xml, xpath, position);
687
0
        pcmk__trace("Performing %s on %s with %p", op, xpath, match);
688
689
0
        if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
690
0
            pcmk__debug("No %s match for %s in %p", op, xpath, xml->doc);
691
0
            continue;
692
693
0
        } else if (match == NULL) {
694
0
            pcmk__err("No %s match for %s in %p", op, xpath, xml->doc);
695
0
            rc = pcmk_rc_diff_failed;
696
0
            continue;
697
698
0
        } else if (pcmk__str_any_of(op,
699
0
                                    PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) {
700
            // Delay the adding of a PCMK_VALUE_CREATE object
701
0
            xml_change_obj_t *change_obj =
702
0
                pcmk__assert_alloc(1, sizeof(xml_change_obj_t));
703
704
0
            change_obj->change = change;
705
0
            change_obj->match = match;
706
707
0
            change_objs = g_list_append(change_objs, change_obj);
708
709
0
            if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
710
                // Temporarily put the PCMK_VALUE_MOVE object after the last sibling
711
0
                if ((match->parent != NULL) && (match->parent->last != NULL)) {
712
0
                    xmlAddNextSibling(match->parent->last, match);
713
0
                }
714
0
            }
715
716
0
        } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
717
0
            pcmk__xml_free(match);
718
719
0
        } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
720
0
            const xmlNode *child = pcmk__xe_first_child(change,
721
0
                                                        PCMK_XE_CHANGE_RESULT,
722
0
                                                        NULL, NULL);
723
0
            const xmlNode *attrs = pcmk__xml_first_child(child);
724
725
0
            if (attrs == NULL) {
726
0
                rc = ENOMSG;
727
0
                continue;
728
0
            }
729
730
            // Remove all attributes
731
0
            pcmk__xe_remove_matching_attrs(match, false, NULL, NULL);
732
733
0
            for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
734
0
                 pIter = pIter->next) {
735
0
                const char *name = (const char *) pIter->name;
736
0
                const char *value = pcmk__xml_attr_value(pIter);
737
738
0
                pcmk__xe_set(match, name, value);
739
0
            }
740
741
0
        } else {
742
0
            pcmk__err("Unknown operation: %s", op);
743
0
            rc = pcmk_rc_diff_failed;
744
0
        }
745
0
    }
746
747
    // Changes should be generated in the right order. Double checking.
748
0
    change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
749
750
0
    for (gIter = change_objs; gIter; gIter = gIter->next) {
751
0
        xml_change_obj_t *change_obj = gIter->data;
752
0
        xmlNode *match = change_obj->match;
753
0
        const char *op = NULL;
754
0
        const char *xpath = NULL;
755
756
0
        change = change_obj->change;
757
758
0
        op = pcmk__xe_get(change, PCMK_XA_OPERATION);
759
0
        xpath = pcmk__xe_get(change, PCMK_XA_PATH);
760
761
0
        pcmk__trace("Continue performing %s on %s with %p", op, xpath, match);
762
763
0
        if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
764
0
            int position = 0;
765
0
            xmlNode *child = NULL;
766
0
            xmlNode *match_child = NULL;
767
768
0
            match_child = match->children;
769
0
            pcmk__xe_get_int(change, PCMK_XE_POSITION, &position);
770
771
0
            while ((match_child != NULL)
772
0
                   && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
773
0
                match_child = match_child->next;
774
0
            }
775
776
0
            child = pcmk__xml_copy(match, change->children);
777
778
0
            if (match_child != NULL) {
779
0
                pcmk__trace("Adding %s at position %d", child->name, position);
780
0
                xmlAddPrevSibling(match_child, child);
781
782
0
            } else {
783
0
                pcmk__trace("Adding %s at position %d (end)", child->name,
784
0
                            position);
785
0
            }
786
787
0
        } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
788
0
            int position = 0;
789
790
0
            pcmk__xe_get_int(change, PCMK_XE_POSITION, &position);
791
0
            if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
792
0
                xmlNode *match_child = NULL;
793
0
                int p = position;
794
795
0
                if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
796
0
                    p++; // Skip ourselves
797
0
                }
798
799
0
                pcmk__assert(match->parent != NULL);
800
0
                match_child = match->parent->children;
801
802
0
                while ((match_child != NULL)
803
0
                       && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
804
0
                    match_child = match_child->next;
805
0
                }
806
807
0
                pcmk__trace("Moving %s to position %d (was %d, prev %p, %s %p)",
808
0
                            match->name, position,
809
0
                            pcmk__xml_position(match, pcmk__xf_skip),
810
0
                            match->prev,
811
0
                            ((match_child != NULL)? "next" : "last"),
812
0
                            ((match_child != NULL)? match_child
813
0
                                                  : match->parent->last));
814
815
0
                if (match_child) {
816
0
                    xmlAddPrevSibling(match_child, match);
817
818
0
                } else {
819
0
                    pcmk__assert(match->parent->last != NULL);
820
0
                    xmlAddNextSibling(match->parent->last, match);
821
0
                }
822
823
0
            } else {
824
0
                pcmk__trace("%s is already in position %d", match->name,
825
0
                            position);
826
0
            }
827
828
0
            if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
829
0
                pcmk__err("Moved %s.%s to position %d instead of %d (%p)",
830
0
                          match->name, pcmk__xe_id(match),
831
0
                          pcmk__xml_position(match, pcmk__xf_skip),
832
0
                          position, match->prev);
833
0
                rc = pcmk_rc_diff_failed;
834
0
            }
835
0
        }
836
0
    }
837
838
0
    g_list_free_full(change_objs, free);
839
0
    return rc;
840
0
}
841
842
int
843
xml_apply_patchset(xmlNode *xml, const xmlNode *patchset, bool check_version)
844
0
{
845
0
    int format = 1;
846
0
    int rc = pcmk_ok;
847
0
    xmlNode *old = NULL;
848
0
    const char *digest = NULL;
849
850
0
    if (patchset == NULL) {
851
0
        return rc;
852
0
    }
853
854
0
    pcmk__log_xml_patchset(LOG_TRACE, patchset);
855
856
0
    if (check_version) {
857
0
        rc = pcmk_rc2legacy(check_patchset_versions(xml, patchset));
858
0
        if (rc != pcmk_ok) {
859
0
            return rc;
860
0
        }
861
0
    }
862
863
0
    digest = pcmk__xe_get(patchset, PCMK_XA_DIGEST);
864
0
    if (digest != NULL) {
865
        /* Make original XML available for logging in case result doesn't have
866
         * expected digest
867
         */
868
0
        pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
869
0
    }
870
871
0
    if (rc == pcmk_ok) {
872
0
        pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
873
874
0
        if (format != 2) {
875
0
            pcmk__err("Unknown patch format: %d", format);
876
0
            rc = -EINVAL;
877
878
0
        } else {
879
0
            rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
880
0
        }
881
0
    }
882
883
0
    if ((rc == pcmk_ok) && (digest != NULL)) {
884
0
        char *new_digest = NULL;
885
886
0
        new_digest = pcmk__digest_xml(xml, true);
887
0
        if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
888
0
            pcmk__info("v%d digest mis-match: expected %s, calculated %s",
889
0
                       format, digest, new_digest);
890
0
            rc = -pcmk_err_diff_failed;
891
0
            pcmk__if_tracing(
892
0
                {
893
0
                    pcmk__xml_write_temp_file(old, "PatchDigest:input", NULL);
894
0
                    pcmk__xml_write_temp_file(xml, "PatchDigest:result", NULL);
895
0
                    pcmk__xml_write_temp_file(patchset, "PatchDigest:diff",
896
0
                                              NULL);
897
0
                },
898
0
                {}
899
0
            );
900
901
0
        } else {
902
0
            pcmk__trace("v%d digest matched: expected %s, calculated %s",
903
0
                        format, digest, new_digest);
904
0
        }
905
0
        free(new_digest);
906
0
    }
907
0
    pcmk__xml_free(old);
908
0
    return rc;
909
0
}
910
911
/*!
912
 * \internal
913
 * \brief Check whether a given CIB element was modified in a CIB patchset
914
 *
915
 * \param[in] patchset  CIB XML patchset
916
 * \param[in] element   XML tag of CIB element to check (\c NULL is equivalent
917
 *                      to \c PCMK_XE_CIB). Supported values include any CIB
918
 *                      element supported by \c pcmk__cib_abs_xpath_for().
919
 *
920
 * \retval \c true if \p element was modified
921
 * \retval \c false otherwise
922
 */
923
bool
924
pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
925
0
{
926
0
    const char *element_xpath = pcmk__cib_abs_xpath_for(element);
927
0
    const char *parent_xpath = pcmk_cib_parent_name_for(element);
928
0
    char *element_regex = NULL;
929
0
    bool rc = false;
930
0
    int format = 1;
931
932
0
    pcmk__assert(patchset != NULL);
933
934
0
    pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
935
0
    if (format != 2) {
936
0
        pcmk__warn("Unknown patch format: %d", format);
937
0
        return false;
938
0
    }
939
940
0
    CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
941
942
    /* Matches if and only if element_xpath is part of a changed path
943
     * (supported values for element never contain XML IDs with schema
944
     * validation enabled)
945
     *
946
     * @TODO Use POSIX word boundary instead of (/|$), if it works:
947
     * https://www.regular-expressions.info/wordboundaries.html.
948
     */
949
0
    element_regex = pcmk__assert_asprintf("^%s(/|$)", element_xpath);
950
951
0
    for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
952
0
                                                      NULL, NULL);
953
0
         change != NULL; change = pcmk__xe_next(change, PCMK_XE_CHANGE)) {
954
955
0
        const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION);
956
0
        const char *diff_xpath = pcmk__xe_get(change, PCMK_XA_PATH);
957
958
0
        if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
959
            // Change to an existing element
960
0
            rc = true;
961
0
            break;
962
0
        }
963
964
0
        if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
965
0
            && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
966
0
            && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
967
0
                                                element)) {
968
            // Newly added element
969
0
            rc = true;
970
0
            break;
971
0
        }
972
0
    }
973
974
0
    free(element_regex);
975
0
    return rc;
976
0
}
977
978
// Deprecated functions kept only for backward API compatibility
979
// LCOV_EXCL_START
980
981
#include <crm/common/xml_compat.h>
982
983
// Return value of true means failure; false means success
984
bool
985
xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
986
0
{
987
0
    const xmlNode *version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION,
988
0
                                                  NULL, NULL);
989
0
    const xmlNode *source = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL,
990
0
                                                 NULL);
991
0
    const xmlNode *target = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL,
992
0
                                                 NULL);
993
0
    int format = 1;
994
995
0
    pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format);
996
0
    if (format != 2) {
997
0
        pcmk__err("Unknown patch format: %d", format);
998
0
        return true;
999
0
    }
1000
1001
0
    if (source != NULL) {
1002
0
        for (int i = 0; i < PCMK__NELEM(vfields); i++) {
1003
0
            pcmk__xe_get_int(source, vfields[i], &(del[i]));
1004
0
            pcmk__trace("Got %d for del[%s]", del[i], vfields[i]);
1005
0
        }
1006
0
    }
1007
1008
0
    if (target != NULL) {
1009
0
        for (int i = 0; i < PCMK__NELEM(vfields); i++) {
1010
0
            pcmk__xe_get_int(target, vfields[i], &(add[i]));
1011
0
            pcmk__trace("Got %d for add[%s]", add[i], vfields[i]);
1012
0
        }
1013
0
    }
1014
0
    return false;
1015
0
}
1016
1017
void
1018
patchset_process_digest(xmlNode *patch, const xmlNode *source,
1019
                        const xmlNode *target, bool with_digest)
1020
0
{
1021
0
    char *digest = NULL;
1022
1023
0
    if ((patch == NULL) || (source == NULL) || (target == NULL)
1024
0
        || !with_digest) {
1025
0
        return;
1026
0
    }
1027
1028
0
    CRM_LOG_ASSERT(!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty));
1029
1030
0
    digest = pcmk__digest_xml(target, true);
1031
1032
0
    pcmk__xe_set(patch, PCMK_XA_DIGEST, digest);
1033
0
    free(digest);
1034
1035
0
    return;
1036
0
}
1037
1038
// LCOV_EXCL_STOP
1039
// End deprecated API