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