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-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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 606 {
 607     return -EPROTONOSUPPORT;
 608 }
 609 
 610 static int
 611 cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
     /* [previous][next][first][last][top][bottom][index][help] */
 612 {
 613     return -EPROTONOSUPPORT;
 614 }
 615 
 616 static int
 617 cib_file_set_connection_dnotify(cib_t *cib,
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 653 {
 654     cib_t *cib = NULL;
 655     cib_file_opaque_t *private = NULL;
 656     char *filename = NULL;
 657 
 658     if (cib_location == NULL) {
 659         cib_location = getenv("CIB_file");
 660         if (cib_location == NULL) {
 661             return NULL; // Shouldn't be possible if we were called internally
 662         }
 663     }
 664 
 665     cib = cib_new_variant();
 666     if (cib == NULL) {
 667         return NULL;
 668     }
 669 
 670     filename = strdup(cib_location);
 671     if (filename == NULL) {
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
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,
     /* [previous][next][first][last][top][bottom][index][help] */
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,
     /* [previous][next][first][last][top][bottom][index][help] */
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 }

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