Coverage Report

Created: 2026-06-25 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/cib/cib_ops.c
Line
Count
Source
1
/*
2
 * Copyright 2004-2026 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 <stdint.h>                     // uint32_t
14
#include <stdio.h>
15
#include <unistd.h>
16
#include <stdlib.h>
17
#include <errno.h>
18
#include <fcntl.h>
19
#include <time.h>
20
21
#include <sys/param.h>
22
#include <sys/types.h>
23
24
#include <glib.h>
25
#include <libxml/tree.h>
26
#include <libxml/xpath.h>               // xmlXPathObject, etc.
27
28
#include <crm/crm.h>
29
#include <crm/cib/internal.h>
30
31
#include <crm/common/xml.h>
32
33
// @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon
34
static GHashTable *operation_table = NULL;
35
36
static const cib__operation_t cib_ops[] = {
37
    {
38
        PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete,
39
        cib__op_attr_modifies|cib__op_attr_privileged
40
    },
41
    {
42
        PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch,
43
        cib__op_attr_modifies
44
        |cib__op_attr_privileged
45
        |cib__op_attr_transaction
46
    },
47
    {
48
        PCMK__CIB_REQUEST_BUMP, cib__op_bump,
49
        cib__op_attr_modifies
50
        |cib__op_attr_privileged
51
        |cib__op_attr_transaction
52
    },
53
    {
54
        PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
55
        cib__op_attr_modifies
56
        |cib__op_attr_privileged
57
        |cib__op_attr_replaces
58
        |cib__op_attr_writes_through
59
    },
60
    {
61
        PCMK__CIB_REQUEST_CREATE, cib__op_create,
62
        cib__op_attr_modifies
63
        |cib__op_attr_privileged
64
        |cib__op_attr_transaction
65
    },
66
    {
67
        PCMK__CIB_REQUEST_DELETE, cib__op_delete,
68
        cib__op_attr_modifies
69
        |cib__op_attr_privileged
70
        |cib__op_attr_transaction
71
    },
72
    {
73
        PCMK__CIB_REQUEST_ERASE, cib__op_erase,
74
        cib__op_attr_modifies
75
        |cib__op_attr_privileged
76
        |cib__op_attr_replaces
77
        |cib__op_attr_transaction
78
    },
79
    {
80
        PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary,
81
        cib__op_attr_privileged
82
    },
83
    {
84
        PCMK__CIB_REQUEST_MODIFY, cib__op_modify,
85
        cib__op_attr_modifies
86
        |cib__op_attr_privileged
87
        |cib__op_attr_transaction
88
    },
89
    {
90
        PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none
91
    },
92
    {
93
        CRM_OP_PING, cib__op_ping, cib__op_attr_none
94
    },
95
    {
96
        // @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support
97
        PCMK__CIB_REQUEST_PRIMARY, cib__op_primary,
98
        cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local
99
    },
100
    {
101
        PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none
102
    },
103
    {
104
        PCMK__CIB_REQUEST_REPLACE, cib__op_replace,
105
        cib__op_attr_modifies
106
        |cib__op_attr_privileged
107
        |cib__op_attr_replaces
108
        |cib__op_attr_writes_through
109
        |cib__op_attr_transaction
110
    },
111
    {
112
        PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local
113
    },
114
    {
115
        PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary,
116
        cib__op_attr_privileged|cib__op_attr_local
117
    },
118
    {
119
        PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged
120
    },
121
    {
122
        PCMK__CIB_REQUEST_SYNC, cib__op_sync, cib__op_attr_privileged
123
    },
124
    {
125
        PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade,
126
        cib__op_attr_modifies
127
        |cib__op_attr_privileged
128
        |cib__op_attr_writes_through
129
        |cib__op_attr_transaction
130
    },
131
};
132
133
/*!
134
 * \internal
135
 * \brief Get the \c cib__operation_t object for a given CIB operation name
136
 *
137
 * \param[in]  op         CIB operation name
138
 * \param[out] operation  Where to store CIB operation object
139
 *
140
 * \return Standard Pacemaker return code
141
 */
142
int
143
cib__get_operation(const char *op, const cib__operation_t **operation)
144
0
{
145
0
    pcmk__assert((op != NULL) && (operation != NULL));
146
147
0
    if (operation_table == NULL) {
148
0
        operation_table = pcmk__strkey_table(NULL, NULL);
149
150
0
        for (int lpc = 0; lpc < PCMK__NELEM(cib_ops); lpc++) {
151
0
            const cib__operation_t *oper = &(cib_ops[lpc]);
152
153
0
            g_hash_table_insert(operation_table, (void *) oper->name,
154
0
                                (void *) oper);
155
0
        }
156
0
    }
157
158
0
    *operation = g_hash_table_lookup(operation_table, op);
159
0
    if (*operation == NULL) {
160
0
        pcmk__err("Operation %s is invalid", op);
161
0
        return EINVAL;
162
0
    }
163
0
    return pcmk_rc_ok;
164
0
}
165
166
int
167
cib__process_apply_patch(xmlNode *req, xmlNode **cib, xmlNode **answer)
168
0
{
169
0
    const xmlNode *input = cib__get_calldata(req);
170
0
    int rc = xml_apply_patchset(*cib, input, true);
171
172
0
    return pcmk_legacy2rc(rc);
173
0
}
174
175
static void
176
update_counter(xmlNode *xml, const char *field, bool reset)
177
0
{
178
0
    int old_value = 0;
179
0
    bool was_set = (pcmk__xe_get_int(xml, field, &old_value) == pcmk_rc_ok);
180
0
    int new_value = (reset? 1 : (old_value + 1));
181
182
0
    if (was_set) {
183
0
        pcmk__trace("Updating %s from %d to %d", field, old_value, new_value);
184
185
0
    } else {
186
0
        pcmk__trace("Updating %s from unset to %d", field, new_value);
187
0
    }
188
189
0
    pcmk__xe_set_int(xml, field, new_value);
190
0
}
191
192
int
193
cib__process_bump(xmlNode *req, xmlNode **cib, xmlNode **answer)
194
0
{
195
0
    update_counter(*cib, PCMK_XA_EPOCH, false);
196
0
    return pcmk_rc_ok;
197
0
}
198
199
static int
200
add_cib_object(xmlNode *parent, xmlNode *new_obj)
201
0
{
202
0
    const char *object_name = NULL;
203
0
    const char *object_id = NULL;
204
205
0
    if ((parent == NULL) || (new_obj == NULL)) {
206
0
        return EINVAL;
207
0
    }
208
209
0
    object_name = (const char *) new_obj->name;
210
0
    if (object_name == NULL) {
211
0
        return EINVAL;
212
0
    }
213
214
0
    object_id = pcmk__xe_id(new_obj);
215
0
    if (pcmk__xe_first_child(parent, object_name,
216
0
                             ((object_id != NULL)? PCMK_XA_ID : NULL),
217
0
                             object_id)) {
218
0
        return EEXIST;
219
0
    }
220
221
0
    if (object_id != NULL) {
222
0
        pcmk__trace("Processing creation of <%s " PCMK_XA_ID "='%s'>",
223
0
                    object_name, object_id);
224
0
    } else {
225
0
        pcmk__trace("Processing creation of <%s>", object_name);
226
0
    }
227
228
    /* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use
229
     * case, PCMK__XA_REPLACE has special meaning and should not be included in
230
     * the newly created object until we can break behavioral backward
231
     * compatibility.
232
     *
233
     * At a compatibility break, drop this and drop the definition of
234
     * PCMK__XA_REPLACE. Treat it like any other attribute.
235
     */
236
0
    pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb,
237
0
                           (void *) PCMK__XA_REPLACE);
238
239
0
    pcmk__xml_copy(parent, new_obj);
240
0
    return pcmk_rc_ok;
241
0
}
242
243
static void
244
update_results(xmlNode *failed, xmlNode *target, const char *operation, int rc)
245
0
{
246
0
    xmlNode *failed_update = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE);
247
248
0
    pcmk__xml_copy(failed_update, target);
249
250
0
    pcmk__xe_set(failed_update, PCMK_XA_ID, pcmk__xe_id(target));
251
0
    pcmk__xe_set(failed_update, PCMK_XA_OBJECT_TYPE,
252
0
                 (const char *) target->name);
253
0
    pcmk__xe_set(failed_update, PCMK_XA_OPERATION, operation);
254
0
    pcmk__xe_set(failed_update, PCMK_XA_REASON, pcmk_rc_str(rc));
255
256
0
    pcmk__warn("Action %s failed: %s", operation, pcmk_rc_str(rc));
257
0
}
258
259
static int
260
process_create_xpath(const char *op, const char *xpath, xmlNode *input,
261
                     xmlNode *cib)
262
0
{
263
0
    int num_results = 0;
264
0
    int rc = pcmk_rc_ok;
265
0
    xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath);
266
0
    xmlNode *match = NULL;
267
0
    xmlChar *path = NULL;
268
269
0
    num_results = pcmk__xpath_num_results(xpath_obj);
270
0
    if (num_results == 0) {
271
0
        pcmk__debug("%s: %s does not exist", op, xpath);
272
0
        rc = ENXIO;
273
0
        goto done;
274
0
    }
275
276
0
    match = pcmk__xpath_result(xpath_obj, 0);
277
0
    if (match == NULL) {
278
0
        goto done;
279
0
    }
280
281
0
    path = xmlGetNodePath(match);
282
0
    pcmk__debug("Processing %s op for %s with %s", op, xpath, path);
283
0
    free(path);
284
285
0
    pcmk__xml_copy(match, input);
286
287
0
done:
288
0
    xmlXPathFreeObject(xpath_obj);
289
0
    return rc;
290
0
}
291
292
int
293
cib__process_create(xmlNode *req, xmlNode **cib, xmlNode **answer)
294
0
{
295
0
    const char *op = pcmk__xe_get(req, PCMK__XA_CIB_OP);
296
0
    const char *section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION);
297
0
    xmlNode *input = cib__get_calldata(req);
298
0
    xmlNode *failed = NULL;
299
0
    int rc = pcmk_rc_ok;
300
0
    xmlNode *update_section = NULL;
301
302
0
    if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) {
303
0
        input = pcmk_find_cib_element(input, section);
304
0
    }
305
306
0
    if (input == NULL) {
307
0
        pcmk__err("Cannot perform modification with no data");
308
0
        return EINVAL;
309
0
    }
310
311
0
    if (pcmk__strcase_any_of(section, PCMK__XE_ALL, PCMK_XE_CIB, NULL)
312
0
        || pcmk__xe_is(input, PCMK_XE_CIB)) {
313
314
0
        return cib__process_modify(req, cib, answer);
315
0
    }
316
317
    // @COMPAT Deprecated since 2.1.8
318
0
    failed = pcmk__xe_create(NULL, PCMK__XE_FAILED);
319
320
0
    update_section = pcmk_find_cib_element(*cib, section);
321
0
    if (pcmk__xe_is(input, section)) {
322
0
        xmlNode *a_child = NULL;
323
324
0
        for (a_child = pcmk__xml_first_child(input); a_child != NULL;
325
0
             a_child = pcmk__xml_next(a_child)) {
326
327
0
            rc = add_cib_object(update_section, a_child);
328
0
            if (rc != pcmk_rc_ok) {
329
0
                update_results(failed, a_child, op, rc);
330
0
                break;
331
0
            }
332
0
        }
333
334
0
    } else {
335
0
        rc = add_cib_object(update_section, input);
336
0
        if (rc != pcmk_rc_ok) {
337
0
            update_results(failed, input, op, rc);
338
0
        }
339
0
    }
340
341
0
    if ((rc == pcmk_rc_ok) && (failed->children != NULL)) {
342
0
        rc = EINVAL;
343
0
    }
344
345
0
    if (rc != pcmk_rc_ok) {
346
0
        pcmk__log_xml_err(failed, "CIB Update failures");
347
0
        *answer = failed;
348
349
0
    } else {
350
0
        pcmk__xml_free(failed);
351
0
    }
352
353
0
    return rc;
354
0
}
355
356
static int
357
process_delete_xpath(const xmlNode *request, xmlNode *cib)
358
0
{
359
0
    const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
360
0
    const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
361
0
    uint32_t options = cib_none;
362
363
0
    int num_results = 0;
364
0
    int rc = pcmk_rc_ok;
365
366
0
    xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath);
367
368
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
369
370
0
    num_results = pcmk__xpath_num_results(xpath_obj);
371
0
    if (num_results == 0) {
372
0
        pcmk__debug("%s was already removed", xpath);
373
0
        goto done;
374
0
    }
375
376
0
    for (int i = 0; i < num_results; i++) {
377
0
        xmlNode *match = NULL;
378
0
        xmlChar *path = NULL;
379
380
        /* If we're deleting multiple nodes, go in reverse document order.
381
         * If we go in forward order and the node set contains both a parent and
382
         * its descendant, then deleting the parent frees the descendant before
383
         * the loop reaches the descendant. This is a use-after-free error.
384
         *
385
         * @COMPAT cib_multiple is only ever used with delete operations. The
386
         * correct order to process multiple nodes for operations other than
387
         * query (forward) and delete (reverse) is less clear but likely should
388
         * be reverse. If we ever replace the CIB public API with libpacemaker
389
         * functions, revisit this. For now, we keep forward order for other
390
         * operations to preserve backward compatibility, even though external
391
         * callers of other ops with cib_multiple might segfault.
392
         *
393
         * For more info, see comment in xpath2.c:update_xpath_nodes() in
394
         * libxml2.
395
         */
396
0
        if (pcmk__is_set(options, cib_multiple)) {
397
0
            match = pcmk__xpath_result(xpath_obj, num_results - 1 - i);
398
0
        } else {
399
0
            match = pcmk__xpath_result(xpath_obj, i);
400
0
        }
401
402
0
        if (match == NULL) {
403
0
            continue;
404
0
        }
405
406
0
        path = xmlGetNodePath(match);
407
0
        pcmk__debug("Processing %s op for %s with %s", op, xpath, path);
408
0
        free(path);
409
410
0
        if (match == cib) {
411
0
            pcmk__warn("Cannot perform %s for %s: the XPath is addressing the "
412
0
                       "whole /cib", op, xpath);
413
0
            rc = EINVAL;
414
0
            break;
415
0
        }
416
417
0
        pcmk__xml_free(match);
418
0
        if (!pcmk__is_set(options, cib_multiple)) {
419
0
            break;
420
0
        }
421
0
    }
422
423
0
done:
424
0
    xmlXPathFreeObject(xpath_obj);
425
0
    return rc;
426
0
}
427
428
static int
429
delete_child(xmlNode *child, void *userdata)
430
0
{
431
0
    xmlNode *obj_root = userdata;
432
433
0
    if (pcmk__xe_delete_match(obj_root, child) != pcmk_rc_ok) {
434
0
        pcmk__trace("No matching object to delete: %s=%s", child->name,
435
0
                    pcmk__xe_id(child));
436
0
    }
437
438
0
    return pcmk_rc_ok;
439
0
}
440
441
static int
442
process_delete_section(const xmlNode *request, xmlNode *cib)
443
0
{
444
0
    const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
445
0
    xmlNode *input = cib__get_calldata(request);
446
0
    xmlNode *obj_root = NULL;
447
448
0
    if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) {
449
0
        input = pcmk_find_cib_element(input, section);
450
0
    }
451
452
0
    if (input == NULL) {
453
0
        pcmk__err("Cannot find matching section to delete with no input data");
454
0
        return EINVAL;
455
0
    }
456
457
0
    obj_root = pcmk_find_cib_element(cib, section);
458
459
0
    if (pcmk__xe_is(input, section)) {
460
0
        pcmk__xe_foreach_child(input, NULL, delete_child, obj_root);
461
462
0
    } else {
463
0
        delete_child(input, obj_root);
464
0
    }
465
466
0
    return pcmk_rc_ok;
467
0
}
468
469
int
470
cib__process_delete(xmlNode *req, xmlNode **cib, xmlNode **answer)
471
0
{
472
0
    uint32_t options = cib_none;
473
474
0
    pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none);
475
476
0
    if (pcmk__is_set(options, cib_xpath)) {
477
0
        return process_delete_xpath(req, *cib);
478
0
    }
479
480
0
    return process_delete_section(req, *cib);
481
0
}
482
483
int
484
cib__process_erase(xmlNode *req, xmlNode **cib, xmlNode **answer)
485
0
{
486
0
    xmlNode *empty = createEmptyCib(0);
487
0
    xmlNode *empty_config = pcmk__xe_first_child(empty, PCMK_XE_CONFIGURATION,
488
0
                                                 NULL, NULL);
489
0
    xmlNode *empty_status = pcmk__xe_first_child(empty, PCMK_XE_STATUS, NULL,
490
0
                                                 NULL);
491
492
    // Free all existing children, regardless of node type
493
0
    while ((*cib)->children != NULL) {
494
0
        pcmk__xml_free((*cib)->children);
495
0
    }
496
497
    /* Copying is wasteful here, but calling pcmk__xml_copy() adds the copy as a
498
     * child of the existing *cib within the same document. This reduces the
499
     * number of opportunities to make mistakes related to XML documents, change
500
     * tracking, etc., compared to calling xmlUnlinkChild(), xmlAddChild(), etc.
501
     */
502
0
    pcmk__xml_copy(*cib, empty_config);
503
0
    pcmk__xml_copy(*cib, empty_status);
504
505
0
    update_counter(*cib, PCMK_XA_ADMIN_EPOCH, false);
506
507
0
    pcmk__xml_free(empty);
508
0
    return pcmk_rc_ok;
509
0
}
510
511
static int
512
process_modify_xpath(const xmlNode *request, xmlNode *cib)
513
0
{
514
0
    const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
515
0
    const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
516
0
    xmlNode *input = cib__get_calldata(request);
517
0
    uint32_t options = cib_none;
518
519
0
    int num_results = 0;
520
0
    int rc = pcmk_rc_ok;
521
0
    xmlXPathObject *xpath_obj = NULL;
522
0
    uint32_t flags = pcmk__xaf_none;
523
524
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
525
0
    if (pcmk__is_set(options, cib_score_update)) {
526
0
        flags = pcmk__xaf_score_update;
527
0
    }
528
529
0
    if (xpath == NULL) {
530
0
        xpath = pcmk__cib_abs_xpath_for(PCMK_XE_CIB);
531
0
    }
532
533
0
    xpath_obj = pcmk__xpath_search(cib->doc, xpath);
534
535
0
    num_results = pcmk__xpath_num_results(xpath_obj);
536
0
    if (num_results == 0) {
537
0
        pcmk__debug("%s: %s does not exist", op, xpath);
538
0
        rc = ENXIO;
539
0
        goto done;
540
0
    }
541
542
0
    for (int i = 0; i < num_results; i++) {
543
0
        xmlNode *match = NULL;
544
0
        xmlChar *path = NULL;
545
546
0
        match = pcmk__xpath_result(xpath_obj, i);
547
0
        if (match == NULL) {
548
0
            continue;
549
0
        }
550
551
0
        path = xmlGetNodePath(match);
552
0
        pcmk__debug("Processing %s op for %s with %s", op, xpath, path);
553
0
        free(path);
554
555
0
        if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) {
556
0
            rc = ENXIO;
557
558
0
        } else if (!pcmk__is_set(options, cib_multiple)) {
559
0
            break;
560
0
        }
561
0
    }
562
563
0
done:
564
0
    xmlXPathFreeObject(xpath_obj);
565
0
    return rc;
566
0
}
567
568
static int
569
process_modify_section(const xmlNode *request, xmlNode *cib)
570
0
{
571
0
    const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
572
0
    xmlNode *input = cib__get_calldata(request);
573
0
    uint32_t options = cib_none;
574
575
0
    uint32_t flags = pcmk__xaf_none;
576
0
    xmlNode *obj_root = NULL;
577
578
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
579
0
    if (pcmk__is_set(options, cib_score_update)) {
580
0
        flags = pcmk__xaf_score_update;
581
0
    }
582
583
0
    if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) {
584
0
        input = pcmk_find_cib_element(input, section);
585
0
    }
586
587
0
    if (input == NULL) {
588
0
        pcmk__err("Cannot complete CIB modify request with no input data");
589
0
        return EINVAL;
590
0
    }
591
592
0
    obj_root = pcmk_find_cib_element(cib, section);
593
0
    if (obj_root == NULL) {
594
0
        xmlNode *tmp_section = NULL;
595
0
        const char *path = pcmk_cib_parent_name_for(section);
596
597
0
        if (path == NULL) {
598
0
            return EINVAL;
599
0
        }
600
601
0
        tmp_section = pcmk__xe_create(NULL, section);
602
603
        // @TODO This feels hacky and is the only call to process_create_xpath()
604
0
        process_create_xpath(PCMK__CIB_REQUEST_CREATE, path, tmp_section, cib);
605
0
        pcmk__xml_free(tmp_section);
606
607
0
        obj_root = pcmk_find_cib_element(cib, section);
608
0
    }
609
610
    // Should be impossible, as we just created this section if it didn't exist
611
0
    CRM_CHECK(obj_root != NULL, return EINVAL);
612
613
0
    if (pcmk__xe_update_match(obj_root, input, flags) == pcmk_rc_ok) {
614
0
        return pcmk_rc_ok;
615
0
    }
616
617
0
    if (!pcmk__is_set(options, cib_can_create)) {
618
0
        return ENXIO;
619
0
    }
620
621
0
    pcmk__xml_copy(obj_root, input);
622
0
    return pcmk_rc_ok;
623
0
}
624
625
int
626
cib__process_modify(xmlNode *req, xmlNode **cib, xmlNode **answer)
627
0
{
628
0
    uint32_t options = cib_none;
629
630
0
    pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none);
631
632
0
    if (pcmk__is_set(options, cib_xpath)) {
633
0
        return process_modify_xpath(req, *cib);
634
0
    }
635
636
0
    return process_modify_section(req, *cib);
637
0
}
638
639
static int
640
process_query_xpath(const xmlNode *request, xmlNode *cib, xmlNode **answer)
641
0
{
642
0
    const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
643
0
    const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
644
0
    uint32_t options = cib_none;
645
646
0
    int num_results = 0;
647
0
    int rc = pcmk_rc_ok;
648
0
    xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath);
649
650
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
651
652
0
    num_results = pcmk__xpath_num_results(xpath_obj);
653
0
    if (num_results == 0) {
654
0
        pcmk__debug("%s: %s does not exist", op, xpath);
655
0
        rc = ENXIO;
656
0
        goto done;
657
0
    }
658
659
0
    if (num_results > 1) {
660
0
        *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY);
661
0
    }
662
663
0
    for (int i = 0; i < num_results; i++) {
664
0
        xmlChar *path = NULL;
665
0
        xmlNode *match = pcmk__xpath_result(xpath_obj, i);
666
667
0
        if (match == NULL) {
668
0
            continue;
669
0
        }
670
671
0
        path = xmlGetNodePath(match);
672
0
        pcmk__debug("Processing %s op for %s with %s", op, xpath, path);
673
0
        free(path);
674
675
0
        if (match->type != XML_ELEMENT_NODE
676
0
            && pcmk__is_set(options, cib_xpath_address)) {
677
            /* @COMPAT cib_xpath_address is deprecated since 3.0.2
678
             * For a non-element, handle cib_xpath_address with its
679
             * corresponding element.
680
             */
681
0
            match = pcmk__xpath_match_element(match);
682
0
            if (match == NULL) {
683
0
                continue;
684
0
            }
685
686
0
        } else if (match->type != XML_ELEMENT_NODE) {
687
            // Create an element for a single match of a non-element
688
0
            if (*answer == NULL) {
689
0
                *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY);
690
0
            }
691
692
0
            pcmk__xml_copy(*answer, match);
693
0
            continue;
694
0
        }
695
696
0
        if (pcmk__is_set(options, cib_no_children)) {
697
0
            xmlNode *shallow = pcmk__xe_create(*answer,
698
0
                                               (const char *) match->name);
699
700
0
            pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none);
701
702
0
            if (*answer == NULL) {
703
0
                *answer = shallow;
704
0
            }
705
706
0
            continue;
707
0
        }
708
709
0
        if (pcmk__is_set(options, cib_xpath_address)) {
710
            // @COMPAT cib_xpath_address is deprecated since 3.0.2
711
0
            char *path = NULL;
712
0
            xmlNode *parent = match;
713
714
0
            while ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) {
715
0
                const char *id = pcmk__xe_get(parent, PCMK_XA_ID);
716
0
                char *new_path = NULL;
717
718
0
                if (id != NULL) {
719
0
                    new_path = pcmk__assert_asprintf("/%s[@" PCMK_XA_ID "='%s']"
720
0
                                                     "%s", parent->name, id,
721
0
                                                     pcmk__s(path, ""));
722
0
                } else {
723
0
                    new_path = pcmk__assert_asprintf("/%s%s", parent->name,
724
0
                                                     pcmk__s(path, ""));
725
0
                }
726
727
0
                free(path);
728
0
                path = new_path;
729
0
                parent = parent->parent;
730
0
            }
731
732
0
            pcmk__trace("Got: %s", path);
733
734
0
            if (*answer == NULL) {
735
0
                *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY);
736
0
            }
737
738
0
            parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH);
739
0
            pcmk__xe_set(parent, PCMK_XA_ID, path);
740
0
            free(path);
741
0
            continue;
742
0
        }
743
744
0
        if (*answer != NULL) {
745
0
            pcmk__xml_copy(*answer, match);
746
0
            continue;
747
0
        }
748
749
0
        *answer = match;
750
0
    }
751
752
0
done:
753
0
    xmlXPathFreeObject(xpath_obj);
754
0
    return rc;
755
0
}
756
757
static int
758
process_query_section(const xmlNode *request, xmlNode *cib, xmlNode **answer)
759
0
{
760
0
    const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
761
0
    xmlNode *obj_root = NULL;
762
0
    uint32_t options = cib_none;
763
764
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
765
766
0
    if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
767
0
        section = NULL;
768
0
    }
769
770
0
    obj_root = pcmk_find_cib_element(cib, section);
771
0
    if (obj_root == NULL) {
772
0
        return ENXIO;
773
0
    }
774
775
    /* We make a copy in the cib_no_children case but not in the other. We may
776
     * be able to simplify the callers if we're able to do the same thing (copy
777
     * or don't copy) for both.
778
     */
779
0
    if (pcmk__is_set(options, cib_no_children)) {
780
0
        *answer = pcmk__xe_create(NULL, (const char *) obj_root->name);
781
0
        pcmk__xe_copy_attrs(*answer, obj_root, pcmk__xaf_none);
782
783
0
    } else {
784
0
        *answer = obj_root;
785
0
    }
786
787
0
    return pcmk_rc_ok;
788
0
}
789
790
int
791
cib__process_query(xmlNode *req, xmlNode **cib, xmlNode **answer)
792
0
{
793
0
    uint32_t options = cib_none;
794
795
0
    pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none);
796
797
0
    if (pcmk__is_set(options, cib_xpath)) {
798
0
        return process_query_xpath(req, *cib, answer);
799
0
    }
800
801
0
    return process_query_section(req, *cib, answer);
802
0
}
803
804
static bool
805
replace_cib_digest_matches(const xmlNode *request)
806
0
{
807
0
    const char *peer = pcmk__xe_get(request, PCMK__XA_SRC);
808
0
    const char *expected = pcmk__xe_get(request, PCMK_XA_DIGEST);
809
0
    const xmlNode *input = cib__get_calldata(request);
810
0
    char *calculated = NULL;
811
0
    bool matches = false;
812
813
0
    if (expected == NULL) {
814
        // Nothing to verify
815
0
        return true;
816
0
    }
817
818
0
    calculated = pcmk__digest_xml(input, true);
819
0
    matches = pcmk__str_eq(calculated, expected, pcmk__str_casei);
820
821
0
    if (matches) {
822
0
        pcmk__info("Digest matched on replace from %s: %s", peer, expected);
823
824
0
    } else {
825
0
        pcmk__err("Digest mismatch on replace from %s: %s vs. %s (expected)",
826
0
                  peer, calculated, expected);
827
0
    }
828
829
0
    free(calculated);
830
0
    return matches;
831
0
}
832
833
static int
834
replace_cib(xmlNode *request, xmlNode **cib)
835
0
{
836
0
    int updates = 0;
837
0
    int epoch = 0;
838
0
    int admin_epoch = 0;
839
840
0
    int replace_updates = 0;
841
0
    int replace_epoch = 0;
842
0
    int replace_admin_epoch = 0;
843
844
0
    const char *reason = NULL;
845
0
    const char *peer = pcmk__xe_get(request, PCMK__XA_SRC);
846
0
    xmlNode *input = cib__get_calldata(request);
847
848
0
    cib_version_details(*cib, &admin_epoch, &epoch, &updates);
849
0
    cib_version_details(input, &replace_admin_epoch, &replace_epoch,
850
0
                        &replace_updates);
851
852
0
    if (!replace_cib_digest_matches(request)) {
853
0
        pcmk__info("Replacement %d.%d.%d from %s not applied to %d.%d.%d: "
854
0
                   "digest mismatch", replace_admin_epoch, replace_epoch,
855
0
                   replace_updates, peer, admin_epoch, epoch, updates);
856
0
        return pcmk_rc_digest_mismatch;
857
0
    }
858
859
0
    if (replace_admin_epoch < admin_epoch) {
860
0
        reason = PCMK_XA_ADMIN_EPOCH;
861
862
0
    } else if (replace_admin_epoch > admin_epoch) {
863
        /* no more checks */
864
865
0
    } else if (replace_epoch < epoch) {
866
0
        reason = PCMK_XA_EPOCH;
867
868
0
    } else if (replace_epoch > epoch) {
869
        /* no more checks */
870
871
0
    } else if (replace_updates < updates) {
872
0
        reason = PCMK_XA_NUM_UPDATES;
873
0
    }
874
875
0
    if (reason != NULL) {
876
0
        pcmk__info("Replacement %d.%d.%d from %s not applied to %d.%d.%d: "
877
0
                   "current %s is greater than the replacement",
878
0
                   replace_admin_epoch, replace_epoch, replace_updates, peer,
879
0
                   admin_epoch, epoch, updates, reason);
880
0
        return pcmk_rc_old_data;
881
0
    }
882
883
0
    *cib = pcmk__xml_replace_with_copy(*cib, input);
884
885
0
    pcmk__info("Replaced %d.%d.%d with %d.%d.%d from %s", admin_epoch, epoch,
886
0
               updates, replace_admin_epoch, replace_epoch, replace_updates,
887
0
               peer);
888
0
    return pcmk_rc_ok;
889
0
}
890
891
static int
892
process_replace_xpath(xmlNode *request, xmlNode **cib)
893
0
{
894
0
    const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
895
0
    const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
896
0
    xmlNode *input = cib__get_calldata(request);
897
0
    uint32_t options = cib_none;
898
899
0
    int num_results = 0;
900
0
    int rc = pcmk_rc_ok;
901
0
    xmlXPathObject *xpath_obj = pcmk__xpath_search((*cib)->doc, xpath);
902
903
0
    pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none);
904
905
0
    num_results = pcmk__xpath_num_results(xpath_obj);
906
0
    if (num_results == 0) {
907
0
        pcmk__debug("%s: %s does not exist", op, xpath);
908
0
        rc = ENXIO;
909
0
        goto done;
910
0
    }
911
912
0
    for (int i = 0; i < num_results; i++) {
913
0
        xmlNode *match = NULL;
914
0
        xmlNode *parent = NULL;
915
0
        xmlChar *path = NULL;
916
917
0
        match = pcmk__xpath_result(xpath_obj, i);
918
0
        if (match == NULL) {
919
0
            continue;
920
0
        }
921
922
0
        path = xmlGetNodePath(match);
923
0
        pcmk__debug("Processing %s op for %s with %s", op, xpath, path);
924
0
        free(path);
925
926
0
        if (match == *cib) {
927
0
            rc = replace_cib(request, cib);
928
0
            break;
929
0
        }
930
931
0
        parent = match->parent;
932
933
0
        pcmk__xml_free(match);
934
0
        pcmk__xml_copy(parent, input);
935
936
0
        if (!pcmk__is_set(options, cib_multiple)) {
937
0
            break;
938
0
        }
939
0
    }
940
941
0
done:
942
0
    xmlXPathFreeObject(xpath_obj);
943
0
    return rc;
944
0
}
945
946
static int
947
process_replace_section(xmlNode *request, xmlNode **cib)
948
0
{
949
0
    const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
950
0
    xmlNode *input = cib__get_calldata(request);
951
952
0
    int rc = pcmk_rc_ok;
953
0
    xmlNode *obj_root = NULL;
954
955
0
    if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) {
956
0
        input = pcmk_find_cib_element(input, section);
957
0
    }
958
959
0
    if (input == NULL) {
960
0
        pcmk__err("Cannot find matching section to replace with no input data");
961
0
        return EINVAL;
962
0
    }
963
964
0
    if (pcmk__xe_is(input, PCMK_XE_CIB)) {
965
0
        return replace_cib(request, cib);
966
0
    }
967
968
0
    if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)
969
0
        || pcmk__xe_is(input, section)) {
970
971
0
        section = NULL;
972
0
    }
973
974
0
    obj_root = pcmk_find_cib_element(*cib, section);
975
976
0
    rc = pcmk__xe_replace_match(obj_root, input);
977
0
    if (rc != pcmk_rc_ok) {
978
0
        pcmk__trace("No matching object to replace");
979
0
    }
980
981
0
    return rc;
982
0
}
983
984
int
985
cib__process_replace(xmlNode *req, xmlNode **cib, xmlNode **answer)
986
0
{
987
0
    uint32_t options = cib_none;
988
989
0
    pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none);
990
991
0
    if (pcmk__is_set(options, cib_xpath)) {
992
0
        return process_replace_xpath(req, cib);
993
0
    }
994
995
0
    return process_replace_section(req, cib);
996
0
}
997
998
int
999
cib__process_upgrade(xmlNode *req, xmlNode **cib, xmlNode **answer)
1000
0
{
1001
0
    int rc = pcmk_rc_ok;
1002
0
    uint32_t options = cib_none;
1003
0
    const char *max_schema = pcmk__xe_get(req, PCMK__XA_CIB_SCHEMA_MAX);
1004
0
    char *original_schema = NULL;
1005
0
    const char *new_schema = NULL;
1006
0
    xmlNode *updated = pcmk__xml_copy(NULL, *cib);
1007
1008
0
    pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none);
1009
1010
    // pcmk__update_schema() may free the original validate-with string
1011
0
    original_schema = pcmk__xe_get_copy(*cib, PCMK_XA_VALIDATE_WITH);
1012
1013
0
    rc = pcmk__update_schema(&updated, max_schema, true,
1014
0
                             !pcmk__is_set(options, cib_verbose));
1015
0
    *cib = pcmk__xml_replace_with_copy(*cib, updated);
1016
0
    pcmk__xml_free(updated);
1017
1018
0
    new_schema = pcmk__xe_get(*cib, PCMK_XA_VALIDATE_WITH);
1019
1020
0
    if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) {
1021
0
        free(original_schema);
1022
0
        update_counter(*cib, PCMK_XA_ADMIN_EPOCH, false);
1023
0
        update_counter(*cib, PCMK_XA_EPOCH, true);
1024
0
        update_counter(*cib, PCMK_XA_NUM_UPDATES, true);
1025
0
        return pcmk_rc_ok;
1026
0
    }
1027
1028
0
    free(original_schema);
1029
0
    return rc;
1030
0
}