Coverage Report

Created: 2025-11-26 06:09

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