Coverage Report

Created: 2026-01-02 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/cib/cib_file.c
Line
Count
Source
1
/*
2
 * Original copyright 2004 International Business Machines
3
 * Later changes copyright 2008-2025 the Pacemaker project contributors
4
 *
5
 * The version control history for this file may have further details.
6
 *
7
 * This source code is licensed under the GNU Lesser General Public License
8
 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
9
 */
10
11
#include <crm_internal.h>
12
#include <unistd.h>
13
#include <limits.h>
14
#include <stdbool.h>
15
#include <stdlib.h>
16
#include <stdint.h>
17
#include <stdio.h>
18
#include <stdarg.h>
19
#include <string.h>
20
#include <pwd.h>
21
22
#include <sys/stat.h>
23
#include <sys/types.h>
24
#include <glib.h>
25
26
#include <crm/crm.h>
27
#include <crm/cib/internal.h>
28
#include <crm/common/ipc.h>
29
#include <crm/common/xml.h>
30
#include <crm/common/xml_internal.h>
31
32
0
#define CIB_SERIES "cib"
33
0
#define CIB_SERIES_MAX 100
34
0
#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
35
                                 created with hard links
36
                               */
37
38
1
#define CIB_LIVE_NAME CIB_SERIES ".xml"
39
40
// key: client ID (const char *) -> value: client (cib_t *)
41
static GHashTable *client_table = NULL;
42
43
enum cib_file_flags {
44
    cib_file_flag_dirty = (UINT32_C(1) << 0),
45
    cib_file_flag_live  = (UINT32_C(1) << 1),
46
};
47
48
typedef struct cib_file_opaque_s {
49
    char *id;
50
    char *filename;
51
    uint32_t flags; // Group of enum cib_file_flags
52
    xmlNode *cib_xml;
53
} cib_file_opaque_t;
54
55
static int cib_file_process_commit_transaction(const char *op, int options,
56
                                               const char *section,
57
                                               xmlNode *req, xmlNode *input,
58
                                               xmlNode *existing_cib,
59
                                               xmlNode **result_cib,
60
                                               xmlNode **answer);
61
62
/*!
63
 * \internal
64
 * \brief Add a CIB file client to client table
65
 *
66
 * \param[in] cib  CIB client
67
 */
68
static void
69
register_client(const cib_t *cib)
70
0
{
71
0
    cib_file_opaque_t *private = cib->variant_opaque;
72
73
0
    if (client_table == NULL) {
74
0
        client_table = pcmk__strkey_table(NULL, NULL);
75
0
    }
76
0
    g_hash_table_insert(client_table, private->id, (gpointer) cib);
77
0
}
78
79
/*!
80
 * \internal
81
 * \brief Remove a CIB file client from client table
82
 *
83
 * \param[in] cib  CIB client
84
 */
85
static void
86
unregister_client(const cib_t *cib)
87
0
{
88
0
    cib_file_opaque_t *private = cib->variant_opaque;
89
90
0
    if (client_table == NULL) {
91
0
        return;
92
0
    }
93
94
0
    g_hash_table_remove(client_table, private->id);
95
96
    /* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged,
97
     * instead of destroying the client table when there are no more clients.
98
     */
99
0
    if (g_hash_table_size(client_table) == 0) {
100
0
        g_hash_table_destroy(client_table);
101
0
        client_table = NULL;
102
0
    }
103
0
}
104
105
/*!
106
 * \internal
107
 * \brief Look up a CIB file client by its ID
108
 *
109
 * \param[in] client_id  CIB client ID
110
 *
111
 * \return CIB client with matching ID if found, or \p NULL otherwise
112
 */
113
static cib_t *
114
get_client(const char *client_id)
115
0
{
116
0
    if (client_table == NULL) {
117
0
        return NULL;
118
0
    }
119
0
    return g_hash_table_lookup(client_table, (gpointer) client_id);
120
0
}
121
122
static const cib__op_fn_t cib_op_functions[] = {
123
    [cib__op_apply_patch]      = cib_process_diff,
124
    [cib__op_bump]             = cib_process_bump,
125
    [cib__op_commit_transact]  = cib_file_process_commit_transaction,
126
    [cib__op_create]           = cib_process_create,
127
    [cib__op_delete]           = cib_process_delete,
128
    [cib__op_erase]            = cib_process_erase,
129
    [cib__op_modify]           = cib_process_modify,
130
    [cib__op_query]            = cib_process_query,
131
    [cib__op_replace]          = cib_process_replace,
132
    [cib__op_upgrade]          = cib_process_upgrade,
133
};
134
135
/* cib_file_backup() and cib_file_write_with_digest() need to chown the
136
 * written files only in limited circumstances, so these variables allow
137
 * that to be indicated without affecting external callers
138
 */
139
static uid_t cib_file_owner = 0;
140
static uid_t cib_file_group = 0;
141
static gboolean cib_do_chown = FALSE;
142
143
0
#define cib_set_file_flags(cibfile, flags_to_set) do {                  \
144
0
        (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__,       \
145
0
                                              LOG_TRACE, "CIB file",    \
146
0
                                              cibfile->filename,        \
147
0
                                              (cibfile)->flags,         \
148
0
                                              (flags_to_set),           \
149
0
                                              #flags_to_set);           \
150
0
    } while (0)
151
152
0
#define cib_clear_file_flags(cibfile, flags_to_clear) do {              \
153
0
        (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__,     \
154
0
                                                LOG_TRACE, "CIB file",  \
155
0
                                                cibfile->filename,      \
156
0
                                                (cibfile)->flags,       \
157
0
                                                (flags_to_clear),       \
158
0
                                                #flags_to_clear);       \
159
0
    } while (0)
160
161
/*!
162
 * \internal
163
 * \brief Get the function that performs a given CIB file operation
164
 *
165
 * \param[in] operation  Operation whose function to look up
166
 *
167
 * \return Function that performs \p operation for a CIB file client
168
 */
169
static cib__op_fn_t
170
file_get_op_function(const cib__operation_t *operation)
171
0
{
172
0
    enum cib__op_type type = operation->type;
173
174
0
    pcmk__assert(type >= 0);
175
176
0
    if (type >= PCMK__NELEM(cib_op_functions)) {
177
0
        return NULL;
178
0
    }
179
0
    return cib_op_functions[type];
180
0
}
181
182
/*!
183
 * \internal
184
 * \brief Check whether a file is the live CIB
185
 *
186
 * \param[in] filename Name of file to check
187
 *
188
 * \return TRUE if file exists and its real path is same as live CIB's
189
 */
190
static gboolean
191
cib_file_is_live(const char *filename)
192
23
{
193
23
    gboolean same = FALSE;
194
195
23
    if (filename != NULL) {
196
        // Canonicalize file names for true comparison
197
23
        char *real_filename = NULL;
198
199
23
        if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
200
1
            char *real_livename = NULL;
201
202
1
            if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
203
1
                                &real_livename) == pcmk_rc_ok) {
204
0
                same = !strcmp(real_filename, real_livename);
205
0
                free(real_livename);
206
0
            }
207
1
            free(real_filename);
208
1
        }
209
23
    }
210
23
    return same;
211
23
}
212
213
static int
214
cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output)
215
0
{
216
0
    int rc = pcmk_ok;
217
0
    const cib__operation_t *operation = NULL;
218
0
    cib__op_fn_t op_function = NULL;
219
220
0
    int call_id = 0;
221
0
    uint32_t call_options = cib_none;
222
0
    const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
223
0
    const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION);
224
0
    xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA,
225
0
                                            NULL, NULL);
226
0
    xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
227
228
0
    bool changed = false;
229
0
    bool read_only = false;
230
0
    xmlNode *result_cib = NULL;
231
0
    xmlNode *cib_diff = NULL;
232
233
0
    cib_file_opaque_t *private = cib->variant_opaque;
234
235
    // We error checked these in callers
236
0
    cib__get_operation(op, &operation);
237
0
    op_function = file_get_op_function(operation);
238
239
0
    pcmk__xe_get_int(request, PCMK__XA_CIB_CALLID, &call_id);
240
0
    rc = pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options,
241
0
                            cib_none);
242
0
    if (rc != pcmk_rc_ok) {
243
0
        pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc));
244
0
    }
245
246
0
    read_only = !pcmk__is_set(operation->flags, cib__op_attr_modifies);
247
248
    // Mirror the logic in prepare_input() in the CIB manager
249
0
    if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) {
250
251
0
        data = pcmk_find_cib_element(data, section);
252
0
    }
253
254
0
    rc = cib_perform_op(cib, op, call_options, op_function, read_only, section,
255
0
                        request, data, true, &changed, &private->cib_xml,
256
0
                        &result_cib, &cib_diff, output);
257
258
0
    if (pcmk__is_set(call_options, cib_transaction)) {
259
        /* The rest of the logic applies only to the transaction as a whole, not
260
         * to individual requests.
261
         */
262
0
        goto done;
263
0
    }
264
265
0
    if (rc == -pcmk_err_schema_validation) {
266
        // Show validation errors to stderr
267
0
        pcmk__validate_xml(result_cib, NULL, NULL, NULL);
268
269
0
    } else if ((rc == pcmk_ok) && !read_only) {
270
0
        pcmk__log_xml_patchset(LOG_DEBUG, cib_diff);
271
272
0
        if (result_cib != private->cib_xml) {
273
0
            pcmk__xml_free(private->cib_xml);
274
0
            private->cib_xml = result_cib;
275
0
        }
276
0
        cib_set_file_flags(private, cib_file_flag_dirty);
277
0
    }
278
279
0
done:
280
0
    if ((result_cib != private->cib_xml) && (result_cib != *output)) {
281
0
        pcmk__xml_free(result_cib);
282
0
    }
283
0
    pcmk__xml_free(cib_diff);
284
0
    return rc;
285
0
}
286
287
static int
288
cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
289
                             const char *section, xmlNode *data,
290
                             xmlNode **output_data, int call_options,
291
                             const char *user_name)
292
0
{
293
0
    int rc = pcmk_ok;
294
0
    xmlNode *request = NULL;
295
0
    xmlNode *output = NULL;
296
0
    cib_file_opaque_t *private = cib->variant_opaque;
297
298
0
    const cib__operation_t *operation = NULL;
299
300
0
    pcmk__info("Handling %s operation for %s as %s",
301
0
               pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"),
302
0
               pcmk__s(user_name, "default user"));
303
304
0
    if (output_data != NULL) {
305
0
        *output_data = NULL;
306
0
    }
307
308
0
    if (cib->state == cib_disconnected) {
309
0
        return -ENOTCONN;
310
0
    }
311
312
0
    rc = cib__get_operation(op, &operation);
313
0
    rc = pcmk_rc2legacy(rc);
314
0
    if (rc != pcmk_ok) {
315
        // @COMPAT: At compatibility break, use rc directly
316
0
        return -EPROTONOSUPPORT;
317
0
    }
318
319
0
    if (file_get_op_function(operation) == NULL) {
320
        // @COMPAT: At compatibility break, use EOPNOTSUPP
321
0
        pcmk__err("Operation %s is not supported by CIB file clients", op);
322
0
        return -EPROTONOSUPPORT;
323
0
    }
324
325
0
    cib__set_call_options(call_options, "file operation", cib_no_mtime);
326
327
0
    rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
328
0
                        NULL, &request);
329
0
    if (rc != pcmk_ok) {
330
0
        return rc;
331
0
    }
332
0
    pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user_name);
333
0
    pcmk__xe_set(request, PCMK__XA_CIB_CLIENTID, private->id);
334
335
0
    if (pcmk__is_set(call_options, cib_transaction)) {
336
0
        rc = cib__extend_transaction(cib, request);
337
0
        goto done;
338
0
    }
339
340
0
    rc = cib_file_process_request(cib, request, &output);
341
342
0
    if ((output_data != NULL) && (output != NULL)) {
343
0
        if (output->doc == private->cib_xml->doc) {
344
0
            *output_data = pcmk__xml_copy(NULL, output);
345
0
        } else {
346
0
            *output_data = output;
347
0
        }
348
0
    }
349
350
0
done:
351
0
    if ((output != NULL)
352
0
        && (output->doc != private->cib_xml->doc)
353
0
        && ((output_data == NULL) || (output != *output_data))) {
354
355
0
        pcmk__xml_free(output);
356
0
    }
357
0
    pcmk__xml_free(request);
358
0
    return rc;
359
0
}
360
361
/*!
362
 * \internal
363
 * \brief Read CIB from disk and validate it against XML schema
364
 *
365
 * \param[in]   filename  Name of file to read CIB from
366
 * \param[out]  output    Where to store the read CIB XML
367
 *
368
 * \return pcmk_ok on success,
369
 *         -ENXIO if file does not exist (or stat() otherwise fails), or
370
 *         -pcmk_err_schema_validation if XML doesn't parse or validate
371
 * \note If filename is the live CIB, this will *not* verify its digest,
372
 *       though that functionality would be trivial to add here.
373
 *       Also, this will *not* verify that the file is writable,
374
 *       because some callers might not need to write.
375
 */
376
static int
377
load_file_cib(const char *filename, xmlNode **output)
378
0
{
379
0
    struct stat buf;
380
0
    xmlNode *root = NULL;
381
382
    /* Ensure file is readable */
383
0
    if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
384
0
        return -ENXIO;
385
0
    }
386
387
    /* Parse XML from file */
388
0
    root = pcmk__xml_read(filename);
389
0
    if (root == NULL) {
390
0
        return -pcmk_err_schema_validation;
391
0
    }
392
393
    /* Add a status section if not already present */
394
0
    if (pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL) == NULL) {
395
0
        pcmk__xe_create(root, PCMK_XE_STATUS);
396
0
    }
397
398
    /* Validate XML against its specified schema */
399
0
    if (!pcmk__configured_schema_validates(root)) {
400
0
        pcmk__xml_free(root);
401
0
        return -pcmk_err_schema_validation;
402
0
    }
403
404
    /* Remember the parsed XML for later use */
405
0
    *output = root;
406
0
    return pcmk_ok;
407
0
}
408
409
static int
410
cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
411
0
{
412
0
    int rc = pcmk_ok;
413
0
    cib_file_opaque_t *private = cib->variant_opaque;
414
415
0
    if (private->filename == NULL) {
416
0
        rc = -EINVAL;
417
0
    } else {
418
0
        rc = load_file_cib(private->filename, &private->cib_xml);
419
0
    }
420
421
0
    if (rc == pcmk_ok) {
422
0
        pcmk__debug("Opened connection to local file '%s' for %s",
423
0
                    private->filename, pcmk__s(name, "client"));
424
0
        cib->state = cib_connected_command;
425
0
        cib->type = cib_command;
426
0
        register_client(cib);
427
428
0
    } else {
429
0
        pcmk__info("Connection to local file '%s' for %s (client %s) failed: "
430
0
                   "%s",
431
0
                   private->filename, pcmk__s(name, "client"), private->id,
432
0
                   pcmk_strerror(rc));
433
0
    }
434
0
    return rc;
435
0
}
436
437
/*!
438
 * \internal
439
 * \brief Write out the in-memory CIB to a live CIB file
440
 *
441
 * \param[in]     cib_root  Root of XML tree to write
442
 * \param[in,out] path      Full path to file to write
443
 *
444
 * \return Standard Pacemaker return code
445
 */
446
static int
447
cib_file_write_live(xmlNode *cib_root, char *path)
448
0
{
449
0
    uid_t euid = geteuid();
450
0
    uid_t daemon_uid = 0;
451
0
    gid_t daemon_gid = 0;
452
0
    char *sep = strrchr(path, '/');
453
0
    const char *cib_dirname, *cib_filename;
454
0
    int rc = pcmk_rc_ok;
455
456
    /* Get the desired uid/gid */
457
0
    rc = pcmk__daemon_user(&daemon_uid, &daemon_gid);
458
0
    if (rc != pcmk_rc_ok) {
459
0
        pcmk__err("Could not find user " CRM_DAEMON_USER ": %s",
460
0
                  pcmk_rc_str(rc));
461
0
        return rc;
462
0
    }
463
464
    /* If we're root, we can change the ownership;
465
     * if we're daemon, anything we create will be OK;
466
     * otherwise, block access so we don't create wrong owner
467
     */
468
0
    if ((euid != 0) && (euid != daemon_uid)) {
469
0
        pcmk__err("Must be root or " CRM_DAEMON_USER " to modify live CIB");
470
471
        // @TODO Should this return an error instead?
472
0
        return pcmk_rc_ok;
473
0
    }
474
475
    /* fancy footwork to separate dirname from filename
476
     * (we know the canonical name maps to the live CIB,
477
     * but the given name might be relative, or symlinked)
478
     */
479
0
    if (sep == NULL) { /* no directory component specified */
480
0
        cib_dirname = "./";
481
0
        cib_filename = path;
482
0
    } else if (sep == path) { /* given name is in / */
483
0
        cib_dirname = "/";
484
0
        cib_filename = path + 1;
485
0
    } else { /* typical case; split given name into parts */
486
0
        *sep = '\0';
487
0
        cib_dirname = path;
488
0
        cib_filename = sep + 1;
489
0
    }
490
491
    /* if we're root, we want to update the file ownership */
492
0
    if (euid == 0) {
493
0
        cib_file_owner = daemon_uid;
494
0
        cib_file_group = daemon_gid;
495
0
        cib_do_chown = TRUE;
496
0
    }
497
498
    /* write the file */
499
0
    rc = cib_file_write_with_digest(cib_root, cib_dirname, cib_filename);
500
0
    rc = pcmk_legacy2rc(rc);
501
502
    /* turn off file ownership changes, for other callers */
503
0
    if (euid == 0) {
504
0
        cib_do_chown = FALSE;
505
0
    }
506
507
    /* undo fancy stuff */
508
0
    if ((sep != NULL) && (*sep == '\0')) {
509
0
        *sep = '/';
510
0
    }
511
512
0
    return rc;
513
0
}
514
515
/*!
516
 * \internal
517
 * \brief Sign-off method for CIB file variants
518
 *
519
 * This will write the file to disk if needed, and free the in-memory CIB. If
520
 * the file is the live CIB, it will compute and write a signature as well.
521
 *
522
 * \param[in,out] cib  CIB object to sign off
523
 *
524
 * \return pcmk_ok on success, pcmk_err_generic on failure
525
 * \todo This method should refuse to write the live CIB if the CIB manager is
526
 *       running.
527
 */
528
static int
529
cib_file_signoff(cib_t *cib)
530
0
{
531
0
    int rc = pcmk_ok;
532
0
    cib_file_opaque_t *private = cib->variant_opaque;
533
534
0
    pcmk__debug("Disconnecting from the CIB manager");
535
0
    cib->state = cib_disconnected;
536
0
    cib->type = cib_no_connection;
537
0
    unregister_client(cib);
538
0
    cib->cmds->end_transaction(cib, false, cib_none);
539
540
    /* If the in-memory CIB has been changed, write it to disk */
541
0
    if (pcmk__is_set(private->flags, cib_file_flag_dirty)) {
542
543
        /* If this is the live CIB, write it out with a digest */
544
0
        if (pcmk__is_set(private->flags, cib_file_flag_live)) {
545
0
            rc = cib_file_write_live(private->cib_xml, private->filename);
546
0
            rc = pcmk_rc2legacy(rc);
547
548
        /* Otherwise, it's a simple write */
549
0
        } else {
550
0
            bool compress = g_str_has_suffix(private->filename, ".bz2");
551
552
0
            if (pcmk__xml_write_file(private->cib_xml, private->filename,
553
0
                                     compress) != pcmk_rc_ok) {
554
0
                rc = pcmk_err_generic;
555
0
            }
556
0
        }
557
558
0
        if (rc == pcmk_ok) {
559
0
            pcmk__info("Wrote CIB to %s", private->filename);
560
0
            cib_clear_file_flags(private, cib_file_flag_dirty);
561
0
        } else {
562
0
            pcmk__err("Could not write CIB to %s", private->filename);
563
0
        }
564
0
    }
565
566
    /* Free the in-memory CIB */
567
0
    pcmk__xml_free(private->cib_xml);
568
0
    private->cib_xml = NULL;
569
0
    return rc;
570
0
}
571
572
static int
573
cib_file_free(cib_t *cib)
574
23
{
575
23
    int rc = pcmk_ok;
576
577
23
    if (cib->state != cib_disconnected) {
578
0
        rc = cib_file_signoff(cib);
579
0
    }
580
581
23
    if (rc == pcmk_ok) {
582
23
        cib_file_opaque_t *private = cib->variant_opaque;
583
584
23
        free(private->id);
585
23
        free(private->filename);
586
23
        free(private);
587
23
        free(cib->cmds);
588
23
        free(cib->user);
589
23
        free(cib);
590
591
23
    } else {
592
0
        fprintf(stderr, "Couldn't sign off: %d\n", rc);
593
0
    }
594
595
23
    return rc;
596
23
}
597
598
static int
599
cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
600
0
{
601
0
    return -EPROTONOSUPPORT;
602
0
}
603
604
static int
605
cib_file_set_connection_dnotify(cib_t *cib,
606
                                void (*dnotify) (gpointer user_data))
607
0
{
608
0
    return -EPROTONOSUPPORT;
609
0
}
610
611
/*!
612
 * \internal
613
 * \brief Get the given CIB connection's unique client identifier
614
 *
615
 * \param[in]  cib       CIB connection
616
 * \param[out] async_id  If not \p NULL, where to store asynchronous client ID
617
 * \param[out] sync_id   If not \p NULL, where to store synchronous client ID
618
 *
619
 * \return Legacy Pacemaker return code
620
 *
621
 * \note This is the \p cib_file variant implementation of
622
 *       \p cib_api_operations_t:client_id().
623
 */
624
static int
625
cib_file_client_id(const cib_t *cib, const char **async_id,
626
                   const char **sync_id)
627
0
{
628
0
    cib_file_opaque_t *private = cib->variant_opaque;
629
630
0
    if (async_id != NULL) {
631
0
        *async_id = private->id;
632
0
    }
633
0
    if (sync_id != NULL) {
634
0
        *sync_id = private->id;
635
0
    }
636
0
    return pcmk_ok;
637
0
}
638
639
cib_t *
640
cib_file_new(const char *cib_location)
641
23
{
642
23
    cib_t *cib = NULL;
643
23
    cib_file_opaque_t *private = NULL;
644
23
    char *filename = NULL;
645
646
23
    if (cib_location == NULL) {
647
0
        cib_location = getenv("CIB_file");
648
0
        if (cib_location == NULL) {
649
0
            return NULL; // Shouldn't be possible if we were called internally
650
0
        }
651
0
    }
652
653
23
    cib = cib_new_variant();
654
23
    if (cib == NULL) {
655
0
        return NULL;
656
0
    }
657
658
23
    filename = strdup(cib_location);
659
23
    if (filename == NULL) {
660
0
        free(cib);
661
0
        return NULL;
662
0
    }
663
664
23
    private = calloc(1, sizeof(cib_file_opaque_t));
665
23
    if (private == NULL) {
666
0
        free(cib);
667
0
        free(filename);
668
0
        return NULL;
669
0
    }
670
671
23
    private->id = pcmk__generate_uuid();
672
23
    private->filename = filename;
673
674
23
    cib->variant = cib_file;
675
23
    cib->variant_opaque = private;
676
677
23
    private->flags = 0;
678
23
    if (cib_file_is_live(cib_location)) {
679
0
        cib_set_file_flags(private, cib_file_flag_live);
680
0
        pcmk__trace("File %s detected as live CIB", cib_location);
681
0
    }
682
683
    /* assign variant specific ops */
684
23
    cib->delegate_fn = cib_file_perform_op_delegate;
685
23
    cib->cmds->signon = cib_file_signon;
686
23
    cib->cmds->signoff = cib_file_signoff;
687
23
    cib->cmds->free = cib_file_free;
688
23
    cib->cmds->register_notification = cib_file_register_notification;
689
23
    cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
690
691
23
    cib->cmds->client_id = cib_file_client_id;
692
693
23
    return cib;
694
23
}
695
696
/*!
697
 * \internal
698
 * \brief Compare the calculated digest of an XML tree against a signature file
699
 *
700
 * \param[in] root     Root of XML tree to compare
701
 * \param[in] sigfile  Name of signature file containing digest to compare
702
 *
703
 * \return TRUE if digests match or signature file does not exist, else FALSE
704
 */
705
static gboolean
706
cib_file_verify_digest(xmlNode *root, const char *sigfile)
707
0
{
708
0
    gboolean passed = FALSE;
709
0
    char *expected;
710
0
    int rc = pcmk__file_contents(sigfile, &expected);
711
712
0
    switch (rc) {
713
0
        case pcmk_rc_ok:
714
0
            if (expected == NULL) {
715
0
                pcmk__err("On-disk digest at %s is empty", sigfile);
716
0
                return FALSE;
717
0
            }
718
0
            break;
719
0
        case ENOENT:
720
0
            pcmk__warn("No on-disk digest present at %s", sigfile);
721
0
            return TRUE;
722
0
        default:
723
0
            pcmk__err("Could not read on-disk digest from %s: %s", sigfile,
724
0
                      pcmk_rc_str(rc));
725
0
            return FALSE;
726
0
    }
727
0
    passed = pcmk__verify_digest(root, expected);
728
0
    free(expected);
729
0
    return passed;
730
0
}
731
732
/*!
733
 * \internal
734
 * \brief Read an XML tree from a file and verify its digest
735
 *
736
 * \param[in]  filename  Name of XML file to read
737
 * \param[in]  sigfile   Name of signature file containing digest to compare
738
 * \param[out] root      If non-NULL, will be set to pointer to parsed XML tree
739
 *
740
 * \return 0 if file was successfully read, parsed and verified, otherwise:
741
 *         -errno on stat() failure,
742
 *         -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
743
 *         -pcmk_err_cib_modified if digests do not match
744
 * \note If root is non-NULL, it is the caller's responsibility to free *root on
745
 *       successful return.
746
 */
747
int
748
cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
749
0
{
750
0
    int s_res;
751
0
    struct stat buf;
752
0
    char *local_sigfile = NULL;
753
0
    xmlNode *local_root = NULL;
754
755
0
    pcmk__assert(filename != NULL);
756
0
    if (root) {
757
0
        *root = NULL;
758
0
    }
759
760
    /* Verify that file exists and its size is nonzero */
761
0
    s_res = stat(filename, &buf);
762
0
    if (s_res < 0) {
763
0
        pcmk__warn("Could not verify cluster configuration file %s: "
764
0
                   "stat() failed: %s",
765
0
                   filename, strerror(errno));
766
0
        return -errno;
767
0
    } else if (buf.st_size == 0) {
768
0
        pcmk__warn("Cluster configuration file %s is corrupt (size is zero)",
769
0
                   filename);
770
0
        return -pcmk_err_cib_corrupt;
771
0
    }
772
773
    /* Parse XML */
774
0
    local_root = pcmk__xml_read(filename);
775
0
    if (local_root == NULL) {
776
0
        pcmk__warn("Cluster configuration file %s is corrupt (unparseable as "
777
0
                   "XML)",
778
0
                   filename);
779
0
        return -pcmk_err_cib_corrupt;
780
0
    }
781
782
    /* If sigfile is not specified, use original file name plus .sig */
783
0
    if (sigfile == NULL) {
784
0
        sigfile = local_sigfile = pcmk__assert_asprintf("%s.sig", filename);
785
0
    }
786
787
    /* Verify that digests match */
788
0
    if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
789
0
        free(local_sigfile);
790
0
        pcmk__xml_free(local_root);
791
0
        return -pcmk_err_cib_modified;
792
0
    }
793
794
0
    free(local_sigfile);
795
0
    if (root) {
796
0
        *root = local_root;
797
0
    } else {
798
0
        pcmk__xml_free(local_root);
799
0
    }
800
0
    return pcmk_ok;
801
0
}
802
803
/*!
804
 * \internal
805
 * \brief Back up a CIB
806
 *
807
 * \param[in] cib_dirname Directory containing CIB file and backups
808
 * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
809
 *
810
 * \return 0 on success, -1 on error
811
 */
812
static int
813
cib_file_backup(const char *cib_dirname, const char *cib_filename)
814
0
{
815
0
    int rc = 0;
816
0
    unsigned int seq = 0U;
817
0
    char *cib_path = pcmk__assert_asprintf("%s/%s", cib_dirname, cib_filename);
818
0
    char *cib_digest = pcmk__assert_asprintf("%s.sig", cib_path);
819
0
    char *backup_path;
820
0
    char *backup_digest;
821
822
    // Determine backup and digest file names
823
0
    if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
824
0
                                   &seq) != pcmk_rc_ok) {
825
        // @TODO maybe handle errors better ...
826
0
        seq = 0U;
827
0
    }
828
0
    backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
829
0
                                        CIB_SERIES_BZIP);
830
0
    backup_digest = pcmk__assert_asprintf("%s.sig", backup_path);
831
832
    /* Remove the old backups if they exist */
833
0
    unlink(backup_path);
834
0
    unlink(backup_digest);
835
836
    /* Back up the CIB, by hard-linking it to the backup name */
837
0
    if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
838
0
        pcmk__err("Could not archive %s by linking to %s: %s", cib_path,
839
0
                  backup_path, strerror(errno));
840
0
        rc = -1;
841
842
    /* Back up the CIB signature similarly */
843
0
    } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
844
0
        pcmk__err("Could not archive %s by linking to %s: %s", cib_digest,
845
0
                  backup_digest, strerror(errno));
846
0
        rc = -1;
847
848
    /* Update the last counter and ensure everything is sync'd to media */
849
0
    } else {
850
0
        pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
851
0
                                    CIB_SERIES_MAX);
852
0
        if (cib_do_chown) {
853
0
            int rc2;
854
855
0
            if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
856
0
                && (errno != ENOENT)) {
857
858
0
                pcmk__err("Could not set owner of %s: %s", backup_path,
859
0
                          strerror(errno));
860
0
                rc = -1;
861
0
            }
862
0
            if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
863
0
                && (errno != ENOENT)) {
864
865
0
                pcmk__err("Could not set owner of %s: %s", backup_digest,
866
0
                          strerror(errno));
867
0
                rc = -1;
868
0
            }
869
0
            rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
870
0
                                              cib_file_owner, cib_file_group);
871
0
            if (rc2 != pcmk_rc_ok) {
872
0
                pcmk__err("Could not set owner of sequence file in %s: %s",
873
0
                          cib_dirname, pcmk_rc_str(rc2));
874
0
                rc = -1;
875
0
            }
876
0
        }
877
0
        pcmk__sync_directory(cib_dirname);
878
0
        pcmk__info("Archived previous version as %s", backup_path);
879
0
    }
880
881
0
    free(cib_path);
882
0
    free(cib_digest);
883
0
    free(backup_path);
884
0
    free(backup_digest);
885
0
    return rc;
886
0
}
887
888
/*!
889
 * \internal
890
 * \brief Prepare CIB XML to be written to disk
891
 *
892
 * Set \c PCMK_XA_NUM_UPDATES to 0, set \c PCMK_XA_CIB_LAST_WRITTEN to the
893
 * current timestamp, and strip out the status section.
894
 *
895
 * \param[in,out] root  Root of CIB XML tree
896
 *
897
 * \return void
898
 */
899
static void
900
cib_file_prepare_xml(xmlNode *root)
901
0
{
902
0
    xmlNode *cib_status_root = NULL;
903
904
    /* Always write out with num_updates=0 and current last-written timestamp */
905
0
    pcmk__xe_set(root, PCMK_XA_NUM_UPDATES, "0");
906
0
    pcmk__xe_add_last_written(root);
907
908
    /* Delete status section before writing to file, because
909
     * we discard it on startup anyway, and users get confused by it */
910
0
    cib_status_root = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL);
911
0
    CRM_CHECK(cib_status_root != NULL, return);
912
0
    pcmk__xml_free(cib_status_root);
913
0
}
914
915
/*!
916
 * \internal
917
 * \brief Write CIB to disk, along with a signature file containing its digest
918
 *
919
 * \param[in,out] cib_root      Root of XML tree to write
920
 * \param[in]     cib_dirname   Directory containing CIB and signature files
921
 * \param[in]     cib_filename  Name (relative to cib_dirname) of file to write
922
 *
923
 * \return pcmk_ok on success,
924
 *         pcmk_err_cib_modified if existing cib_filename doesn't match digest,
925
 *         pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
926
 *         or pcmk_err_cib_save if new cib_filename couldn't be saved
927
 */
928
int
929
cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
930
                           const char *cib_filename)
931
0
{
932
0
    int exit_rc = pcmk_ok;
933
0
    int rc, fd;
934
0
    char *digest = NULL;
935
936
    /* Detect CIB version for diagnostic purposes */
937
0
    const char *epoch = pcmk__xe_get(cib_root, PCMK_XA_EPOCH);
938
0
    const char *admin_epoch = pcmk__xe_get(cib_root, PCMK_XA_ADMIN_EPOCH);
939
940
    /* Determine full CIB and signature pathnames */
941
0
    char *cib_path = pcmk__assert_asprintf("%s/%s", cib_dirname, cib_filename);
942
0
    char *digest_path = pcmk__assert_asprintf("%s.sig", cib_path);
943
944
    /* Create temporary file name patterns for writing out CIB and signature */
945
0
    char *tmp_cib = pcmk__assert_asprintf("%s/cib.XXXXXX", cib_dirname);
946
0
    char *tmp_digest = pcmk__assert_asprintf("%s/cib.XXXXXX", cib_dirname);
947
948
    /* Ensure the admin didn't modify the existing CIB underneath us */
949
0
    pcmk__trace("Reading cluster configuration file %s", cib_path);
950
0
    rc = cib_file_read_and_verify(cib_path, NULL, NULL);
951
0
    if ((rc != pcmk_ok) && (rc != -ENOENT)) {
952
0
        pcmk__err("%s was manually modified while the cluster was active!",
953
0
                  cib_path);
954
0
        exit_rc = pcmk_err_cib_modified;
955
0
        goto cleanup;
956
0
    }
957
958
    /* Back up the existing CIB */
959
0
    if (cib_file_backup(cib_dirname, cib_filename) < 0) {
960
0
        exit_rc = pcmk_err_cib_backup;
961
0
        goto cleanup;
962
0
    }
963
964
0
    pcmk__debug("Writing CIB to disk");
965
0
    umask(S_IWGRP | S_IWOTH | S_IROTH);
966
0
    cib_file_prepare_xml(cib_root);
967
968
    /* Write the CIB to a temporary file, so we can deploy (near) atomically */
969
0
    fd = mkstemp(tmp_cib);
970
0
    if (fd < 0) {
971
0
        pcmk__err("Couldn't open temporary file %s for writing CIB: %s",
972
0
                  tmp_cib, strerror(errno));
973
0
        exit_rc = pcmk_err_cib_save;
974
0
        goto cleanup;
975
0
    }
976
977
    /* Protect the temporary file */
978
0
    if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
979
0
        pcmk__err("Couldn't protect temporary file %s for writing CIB: %s",
980
0
                  tmp_cib, strerror(errno));
981
0
        exit_rc = pcmk_err_cib_save;
982
0
        goto cleanup;
983
0
    }
984
0
    if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
985
0
        pcmk__err("Couldn't protect temporary file %s for writing CIB: %s",
986
0
                  tmp_cib, strerror(errno));
987
0
        exit_rc = pcmk_err_cib_save;
988
0
        goto cleanup;
989
0
    }
990
991
    /* Write out the CIB */
992
0
    if (pcmk__xml_write_fd(cib_root, tmp_cib, fd) != pcmk_rc_ok) {
993
0
        pcmk__err("Changes couldn't be written to %s", tmp_cib);
994
0
        exit_rc = pcmk_err_cib_save;
995
0
        goto cleanup;
996
0
    }
997
998
    /* Calculate CIB digest */
999
0
    digest = pcmk__digest_on_disk_cib(cib_root);
1000
0
    pcmk__assert(digest != NULL);
1001
0
    pcmk__info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
1002
0
               pcmk__s(admin_epoch, "0"), pcmk__s(epoch, "0"), digest);
1003
1004
    /* Write the CIB digest to a temporary file */
1005
0
    fd = mkstemp(tmp_digest);
1006
0
    if (fd < 0) {
1007
0
        pcmk__err("Could not create temporary file %s for CIB digest: %s",
1008
0
                  tmp_digest, strerror(errno));
1009
0
        exit_rc = pcmk_err_cib_save;
1010
0
        goto cleanup;
1011
0
    }
1012
0
    if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
1013
0
        pcmk__err("Couldn't protect temporary file %s for writing CIB: %s",
1014
0
                  tmp_cib, strerror(errno));
1015
0
        exit_rc = pcmk_err_cib_save;
1016
0
        close(fd);
1017
0
        goto cleanup;
1018
0
    }
1019
0
    rc = pcmk__write_sync(fd, digest);
1020
0
    if (rc != pcmk_rc_ok) {
1021
0
        pcmk__err("Could not write digest to %s: %s", tmp_digest,
1022
0
                  pcmk_rc_str(rc));
1023
0
        exit_rc = pcmk_err_cib_save;
1024
0
        close(fd);
1025
0
        goto cleanup;
1026
0
    }
1027
0
    close(fd);
1028
0
    pcmk__debug("Wrote digest %s to disk", digest);
1029
1030
    /* Verify that what we wrote is sane */
1031
0
    pcmk__info("Reading cluster configuration file %s (digest: %s)", tmp_cib,
1032
0
               tmp_digest);
1033
0
    rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
1034
0
    pcmk__assert(rc == 0);
1035
1036
    /* Rename temporary files to live, and sync directory changes to media */
1037
0
    pcmk__debug("Activating %s", tmp_cib);
1038
0
    if (rename(tmp_cib, cib_path) < 0) {
1039
0
        pcmk__err("Couldn't rename %s as %s: %s", tmp_cib, cib_path,
1040
0
                  strerror(errno));
1041
0
        exit_rc = pcmk_err_cib_save;
1042
0
    }
1043
0
    if (rename(tmp_digest, digest_path) < 0) {
1044
0
        pcmk__err("Couldn't rename %s as %s: %s", tmp_digest, digest_path,
1045
0
                  strerror(errno));
1046
0
        exit_rc = pcmk_err_cib_save;
1047
0
    }
1048
0
    pcmk__sync_directory(cib_dirname);
1049
1050
0
  cleanup:
1051
0
    free(cib_path);
1052
0
    free(digest_path);
1053
0
    free(digest);
1054
0
    free(tmp_digest);
1055
0
    free(tmp_cib);
1056
0
    return exit_rc;
1057
0
}
1058
1059
/*!
1060
 * \internal
1061
 * \brief Process requests in a CIB transaction
1062
 *
1063
 * Stop when a request fails or when all requests have been processed.
1064
 *
1065
 * \param[in,out] cib          CIB client
1066
 * \param[in,out] transaction  CIB transaction
1067
 *
1068
 * \return Standard Pacemaker return code
1069
 */
1070
static int
1071
cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction)
1072
0
{
1073
0
    cib_file_opaque_t *private = cib->variant_opaque;
1074
1075
0
    for (xmlNode *request = pcmk__xe_first_child(transaction,
1076
0
                                                 PCMK__XE_CIB_COMMAND, NULL,
1077
0
                                                 NULL);
1078
0
         request != NULL;
1079
0
         request = pcmk__xe_next(request, PCMK__XE_CIB_COMMAND)) {
1080
1081
0
        xmlNode *output = NULL;
1082
0
        const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
1083
1084
0
        int rc = cib_file_process_request(cib, request, &output);
1085
1086
0
        rc = pcmk_legacy2rc(rc);
1087
0
        if (rc != pcmk_rc_ok) {
1088
0
            pcmk__err("Aborting transaction for CIB file client (%s) on file "
1089
0
                      "'%s' due to failed %s request: %s",
1090
0
                      private->id, private->filename, op, pcmk_rc_str(rc));
1091
0
            pcmk__log_xml_info(request, "Failed request");
1092
0
            return rc;
1093
0
        }
1094
1095
0
        pcmk__trace("Applied %s request to transaction working CIB for CIB "
1096
0
                    "file client (%s) on file '%s'",
1097
0
                    op, private->id, private->filename);
1098
0
        pcmk__log_xml_trace(request, "Successful request");
1099
0
    }
1100
1101
0
    return pcmk_rc_ok;
1102
0
}
1103
1104
/*!
1105
 * \internal
1106
 * \brief Commit a given CIB file client's transaction to a working CIB copy
1107
 *
1108
 * \param[in,out] cib          CIB file client
1109
 * \param[in]     transaction  CIB transaction
1110
 * \param[in,out] result_cib   Where to store result CIB
1111
 *
1112
 * \return Standard Pacemaker return code
1113
 *
1114
 * \note The caller is responsible for replacing the \p cib argument's
1115
 *       \p private->cib_xml with \p result_cib on success, and for freeing
1116
 *       \p result_cib using \p pcmk__xml_free() on failure.
1117
 */
1118
static int
1119
cib_file_commit_transaction(cib_t *cib, xmlNode *transaction,
1120
                            xmlNode **result_cib)
1121
0
{
1122
0
    int rc = pcmk_rc_ok;
1123
0
    cib_file_opaque_t *private = cib->variant_opaque;
1124
0
    xmlNode *saved_cib = private->cib_xml;
1125
1126
0
    CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION),
1127
0
              return pcmk_rc_no_transaction);
1128
1129
    /* *result_cib should be a copy of private->cib_xml (created by
1130
     * cib_perform_op()). If not, make a copy now. Change tracking isn't
1131
     * strictly required here because:
1132
     * * Each request in the transaction will have changes tracked and ACLs
1133
     *   checked if appropriate.
1134
     * * cib_perform_op() will infer changes for the commit request at the end.
1135
     */
1136
0
    CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml),
1137
0
              *result_cib = pcmk__xml_copy(NULL, private->cib_xml));
1138
1139
0
    pcmk__trace("Committing transaction for CIB file client (%s) on file '%s' "
1140
0
                "to working CIB",
1141
0
                private->id, private->filename);
1142
1143
    // Apply all changes to a working copy of the CIB
1144
0
    private->cib_xml = *result_cib;
1145
1146
0
    rc = cib_file_process_transaction_requests(cib, transaction);
1147
1148
0
    pcmk__trace("Transaction commit %s for CIB file client (%s) on file '%s'",
1149
0
                ((rc == pcmk_rc_ok)? "succeeded" : "failed"),
1150
0
                private->id, private->filename);
1151
1152
    /* Some request types (for example, erase) may have freed private->cib_xml
1153
     * (the working copy) and pointed it at a new XML object. In that case, it
1154
     * follows that *result_cib (the working copy) was freed.
1155
     *
1156
     * Point *result_cib at the updated working copy stored in private->cib_xml.
1157
     */
1158
0
    *result_cib = private->cib_xml;
1159
1160
    // Point private->cib_xml back to the unchanged original copy
1161
0
    private->cib_xml = saved_cib;
1162
1163
0
    return rc;
1164
0
}
1165
1166
static int
1167
cib_file_process_commit_transaction(const char *op, int options,
1168
                                    const char *section, xmlNode *req,
1169
                                    xmlNode *input, xmlNode *existing_cib,
1170
                                    xmlNode **result_cib, xmlNode **answer)
1171
0
{
1172
0
    int rc = pcmk_rc_ok;
1173
0
    const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID);
1174
0
    cib_t *cib = NULL;
1175
1176
0
    CRM_CHECK(client_id != NULL, return -EINVAL);
1177
1178
0
    cib = get_client(client_id);
1179
0
    CRM_CHECK(cib != NULL, return -EINVAL);
1180
1181
0
    rc = cib_file_commit_transaction(cib, input, result_cib);
1182
0
    if (rc != pcmk_rc_ok) {
1183
0
        cib_file_opaque_t *private = cib->variant_opaque;
1184
1185
        pcmk__err("Could not commit transaction for CIB file client (%s) on "
1186
0
                  "file '%s': %s",
1187
0
                  private->id, private->filename, pcmk_rc_str(rc));
1188
0
    }
1189
0
    return pcmk_rc2legacy(rc);
1190
0
}