root/lib/cib/cib_file.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. register_client
  2. unregister_client
  3. get_client
  4. file_get_op_function
  5. cib_file_is_live
  6. cib_file_process_request
  7. cib_file_perform_op_delegate
  8. load_file_cib
  9. cib_file_signon
  10. cib_file_write_live
  11. cib_file_signoff
  12. cib_file_free
  13. cib_file_inputfd
  14. cib_file_register_notification
  15. cib_file_set_connection_dnotify
  16. cib_file_client_id
  17. cib_file_new
  18. cib_file_verify_digest
  19. cib_file_read_and_verify
  20. cib_file_backup
  21. cib_file_prepare_xml
  22. cib_file_write_with_digest
  23. cib_file_process_transaction_requests
  24. cib_file_commit_transaction
  25. cib_file_process_commit_transaction

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

/* [previous][next][first][last][top][bottom][index][help] */