diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
commit | e5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch) | |
tree | a6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/cib/cib_file.c | |
parent | Initial commit. (diff) | |
download | pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip |
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/cib/cib_file.c')
-rw-r--r-- | lib/cib/cib_file.c | 919 |
1 files changed, 919 insertions, 0 deletions
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c new file mode 100644 index 0000000..7d05965 --- /dev/null +++ b/lib/cib/cib_file.c @@ -0,0 +1,919 @@ +/* + * Original copyright 2004 International Business Machines + * Later changes copyright 2008-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <pwd.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <glib.h> + +#include <crm/crm.h> +#include <crm/cib/internal.h> +#include <crm/msg_xml.h> +#include <crm/common/ipc.h> +#include <crm/common/xml.h> +#include <crm/common/xml_internal.h> + +#define CIB_SERIES "cib" +#define CIB_SERIES_MAX 100 +#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are + created with hard links + */ + +#define CIB_LIVE_NAME CIB_SERIES ".xml" + +enum cib_file_flags { + cib_file_flag_dirty = (1 << 0), + cib_file_flag_live = (1 << 1), +}; + +typedef struct cib_file_opaque_s { + uint32_t flags; // Group of enum cib_file_flags + char *filename; +} cib_file_opaque_t; + +struct cib_func_entry { + const char *op; + gboolean read_only; + cib_op_t fn; +}; + +static struct cib_func_entry cib_file_ops[] = { + { PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query }, + { PCMK__CIB_REQUEST_MODIFY, FALSE, cib_process_modify }, + { PCMK__CIB_REQUEST_APPLY_PATCH, FALSE, cib_process_diff }, + { PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump }, + { PCMK__CIB_REQUEST_REPLACE, FALSE, cib_process_replace }, + { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create }, + { PCMK__CIB_REQUEST_DELETE, FALSE, cib_process_delete }, + { PCMK__CIB_REQUEST_ERASE, FALSE, cib_process_erase }, + { PCMK__CIB_REQUEST_UPGRADE, FALSE, cib_process_upgrade }, +}; + +static xmlNode *in_mem_cib = NULL; + +/* cib_file_backup() and cib_file_write_with_digest() need to chown the + * written files only in limited circumstances, so these variables allow + * that to be indicated without affecting external callers + */ +static uid_t cib_file_owner = 0; +static uid_t cib_file_group = 0; +static gboolean cib_do_chown = FALSE; + +#define cib_set_file_flags(cibfile, flags_to_set) do { \ + (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \ + LOG_TRACE, "CIB file", \ + cibfile->filename, \ + (cibfile)->flags, \ + (flags_to_set), \ + #flags_to_set); \ + } while (0) + +#define cib_clear_file_flags(cibfile, flags_to_clear) do { \ + (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ + LOG_TRACE, "CIB file", \ + cibfile->filename, \ + (cibfile)->flags, \ + (flags_to_clear), \ + #flags_to_clear); \ + } while (0) + +/*! + * \internal + * \brief Check whether a file is the live CIB + * + * \param[in] filename Name of file to check + * + * \return TRUE if file exists and its real path is same as live CIB's + */ +static gboolean +cib_file_is_live(const char *filename) +{ + gboolean same = FALSE; + + if (filename != NULL) { + // Canonicalize file names for true comparison + char *real_filename = NULL; + + if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) { + char *real_livename = NULL; + + if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME, + &real_livename) == pcmk_rc_ok) { + same = !strcmp(real_filename, real_livename); + free(real_livename); + } + free(real_filename); + } + } + return same; +} + +static int +cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, + const char *user_name) +{ + int rc = pcmk_ok; + char *effective_user = NULL; + gboolean query = FALSE; + gboolean changed = FALSE; + xmlNode *request = NULL; + xmlNode *output = NULL; + xmlNode *cib_diff = NULL; + xmlNode *result_cib = NULL; + cib_op_t *fn = NULL; + int lpc = 0; + static int max_msg_types = PCMK__NELEM(cib_file_ops); + cib_file_opaque_t *private = cib->variant_opaque; + + crm_info("Handling %s operation for %s as %s", + (op? op : "invalid"), (section? section : "entire CIB"), + (user_name? user_name : "default user")); + + cib__set_call_options(call_options, "file operation", + cib_no_mtime|cib_inhibit_bcast|cib_scope_local); + + if (cib->state == cib_disconnected) { + return -ENOTCONN; + } + + if (output_data != NULL) { + *output_data = NULL; + } + + if (op == NULL) { + return -EINVAL; + } + + for (lpc = 0; lpc < max_msg_types; lpc++) { + if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) { + fn = &(cib_file_ops[lpc].fn); + query = cib_file_ops[lpc].read_only; + break; + } + } + + if (fn == NULL) { + return -EPROTONOSUPPORT; + } + + cib->call_id++; + request = cib_create_op(cib->call_id, op, host, section, data, call_options, + user_name); + if(user_name) { + crm_xml_add(request, XML_ACL_TAG_USER, user_name); + } + + /* Mirror the logic in cib_prepare_common() */ + if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { + data = pcmk_find_cib_element(data, section); + } + + rc = cib_perform_op(op, call_options, fn, query, + section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff, + &output); + + free_xml(request); + if (rc == -pcmk_err_schema_validation) { + validate_xml_verbose(result_cib); + } + + if (rc != pcmk_ok) { + free_xml(result_cib); + + } else if (query == FALSE) { + pcmk__output_t *out = NULL; + + rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); + CRM_CHECK(rc == pcmk_ok, goto done); + + pcmk__output_set_log_level(out, LOG_DEBUG); + rc = out->message(out, "xml-patchset", cib_diff); + out->finish(out, pcmk_rc2exitc(rc), true, NULL); + pcmk__output_free(out); + rc = pcmk_ok; + + free_xml(in_mem_cib); + in_mem_cib = result_cib; + cib_set_file_flags(private, cib_file_flag_dirty); + } + + if (cib->op_callback != NULL) { + cib->op_callback(NULL, cib->call_id, rc, output); + } + + if ((output_data != NULL) && (output != NULL)) { + *output_data = (output == in_mem_cib)? copy_xml(output) : output; + } + +done: + free_xml(cib_diff); + + if ((output_data == NULL) && (output != in_mem_cib)) { + /* Don't free output if we're still using it. (output_data != NULL) + * means we may have assigned *output_data = output above. + */ + free_xml(output); + } + free(effective_user); + return rc; +} + +/*! + * \internal + * \brief Read CIB from disk and validate it against XML schema + * + * \param[in] filename Name of file to read CIB from + * + * \return pcmk_ok on success, + * -ENXIO if file does not exist (or stat() otherwise fails), or + * -pcmk_err_schema_validation if XML doesn't parse or validate + * \note If filename is the live CIB, this will *not* verify its digest, + * though that functionality would be trivial to add here. + * Also, this will *not* verify that the file is writable, + * because some callers might not need to write. + */ +static int +load_file_cib(const char *filename) +{ + struct stat buf; + xmlNode *root = NULL; + + /* Ensure file is readable */ + if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) { + return -ENXIO; + } + + /* Parse XML from file */ + root = filename2xml(filename); + if (root == NULL) { + return -pcmk_err_schema_validation; + } + + /* Add a status section if not already present */ + if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) { + create_xml_node(root, XML_CIB_TAG_STATUS); + } + + /* Validate XML against its specified schema */ + if (validate_xml(root, NULL, TRUE) == FALSE) { + const char *schema = crm_element_value(root, XML_ATTR_VALIDATION); + + crm_err("CIB does not validate against %s", schema); + free_xml(root); + return -pcmk_err_schema_validation; + } + + /* Remember the parsed XML for later use */ + in_mem_cib = root; + return pcmk_ok; +} + +static int +cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) +{ + int rc = pcmk_ok; + cib_file_opaque_t *private = cib->variant_opaque; + + if (private->filename == NULL) { + rc = -EINVAL; + } else { + rc = load_file_cib(private->filename); + } + + if (rc == pcmk_ok) { + crm_debug("Opened connection to local file '%s' for %s", + private->filename, name); + cib->state = cib_connected_command; + cib->type = cib_command; + + } else { + crm_info("Connection to local file '%s' for %s failed: %s\n", + private->filename, name, pcmk_strerror(rc)); + } + return rc; +} + +/*! + * \internal + * \brief Write out the in-memory CIB to a live CIB file + * + * param[in,out] path Full path to file to write + * + * \return 0 on success, -1 on failure + */ +static int +cib_file_write_live(char *path) +{ + uid_t uid = geteuid(); + struct passwd *daemon_pwent; + char *sep = strrchr(path, '/'); + const char *cib_dirname, *cib_filename; + int rc = 0; + + /* Get the desired uid/gid */ + errno = 0; + daemon_pwent = getpwnam(CRM_DAEMON_USER); + if (daemon_pwent == NULL) { + crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER); + return -1; + } + + /* If we're root, we can change the ownership; + * if we're daemon, anything we create will be OK; + * otherwise, block access so we don't create wrong owner + */ + if ((uid != 0) && (uid != daemon_pwent->pw_uid)) { + crm_perror(LOG_ERR, "Must be root or %s to modify live CIB", + CRM_DAEMON_USER); + return 0; + } + + /* fancy footwork to separate dirname from filename + * (we know the canonical name maps to the live CIB, + * but the given name might be relative, or symlinked) + */ + if (sep == NULL) { /* no directory component specified */ + cib_dirname = "./"; + cib_filename = path; + } else if (sep == path) { /* given name is in / */ + cib_dirname = "/"; + cib_filename = path + 1; + } else { /* typical case; split given name into parts */ + *sep = '\0'; + cib_dirname = path; + cib_filename = sep + 1; + } + + /* if we're root, we want to update the file ownership */ + if (uid == 0) { + cib_file_owner = daemon_pwent->pw_uid; + cib_file_group = daemon_pwent->pw_gid; + cib_do_chown = TRUE; + } + + /* write the file */ + if (cib_file_write_with_digest(in_mem_cib, cib_dirname, + cib_filename) != pcmk_ok) { + rc = -1; + } + + /* turn off file ownership changes, for other callers */ + if (uid == 0) { + cib_do_chown = FALSE; + } + + /* undo fancy stuff */ + if ((sep != NULL) && (*sep == '\0')) { + *sep = '/'; + } + + return rc; +} + +/*! + * \internal + * \brief Sign-off method for CIB file variants + * + * This will write the file to disk if needed, and free the in-memory CIB. If + * the file is the live CIB, it will compute and write a signature as well. + * + * \param[in,out] cib CIB object to sign off + * + * \return pcmk_ok on success, pcmk_err_generic on failure + * \todo This method should refuse to write the live CIB if the CIB manager is + * running. + */ +static int +cib_file_signoff(cib_t *cib) +{ + int rc = pcmk_ok; + cib_file_opaque_t *private = cib->variant_opaque; + + crm_debug("Disconnecting from the CIB manager"); + cib->state = cib_disconnected; + cib->type = cib_no_connection; + + /* If the in-memory CIB has been changed, write it to disk */ + if (pcmk_is_set(private->flags, cib_file_flag_dirty)) { + + /* If this is the live CIB, write it out with a digest */ + if (pcmk_is_set(private->flags, cib_file_flag_live)) { + if (cib_file_write_live(private->filename) < 0) { + rc = pcmk_err_generic; + } + + /* Otherwise, it's a simple write */ + } else { + gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2"); + + if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) { + rc = pcmk_err_generic; + } + } + + if (rc == pcmk_ok) { + crm_info("Wrote CIB to %s", private->filename); + cib_clear_file_flags(private, cib_file_flag_dirty); + } else { + crm_err("Could not write CIB to %s", private->filename); + } + } + + /* Free the in-memory CIB */ + free_xml(in_mem_cib); + in_mem_cib = NULL; + return rc; +} + +static int +cib_file_free(cib_t *cib) +{ + int rc = pcmk_ok; + + if (cib->state != cib_disconnected) { + rc = cib_file_signoff(cib); + } + + if (rc == pcmk_ok) { + cib_file_opaque_t *private = cib->variant_opaque; + + free(private->filename); + free(cib->cmds); + free(private); + free(cib); + + } else { + fprintf(stderr, "Couldn't sign off: %d\n", rc); + } + + return rc; +} + +static int +cib_file_inputfd(cib_t *cib) +{ + return -EPROTONOSUPPORT; +} + +static int +cib_file_register_notification(cib_t *cib, const char *callback, int enabled) +{ + return -EPROTONOSUPPORT; +} + +static int +cib_file_set_connection_dnotify(cib_t *cib, + void (*dnotify) (gpointer user_data)) +{ + return -EPROTONOSUPPORT; +} + +/*! + * \internal + * \brief Get the given CIB connection's unique client identifier + * + * \param[in] cib CIB connection + * \param[out] async_id If not \p NULL, where to store asynchronous client ID + * \param[out] sync_id If not \p NULL, where to store synchronous client ID + * + * \return Legacy Pacemaker return code (specifically, \p -EPROTONOSUPPORT) + * + * \note This is the \p cib_file variant implementation of + * \p cib_api_operations_t:client_id(). + * \note A \p cib_file object doesn't connect to the CIB and is never assigned a + * client ID. + */ +static int +cib_file_client_id(const cib_t *cib, const char **async_id, + const char **sync_id) +{ + if (async_id != NULL) { + *async_id = NULL; + } + if (sync_id != NULL) { + *sync_id = NULL; + } + return -EPROTONOSUPPORT; +} + +cib_t * +cib_file_new(const char *cib_location) +{ + cib_file_opaque_t *private = NULL; + cib_t *cib = cib_new_variant(); + + if (cib == NULL) { + return NULL; + } + + private = calloc(1, sizeof(cib_file_opaque_t)); + + if (private == NULL) { + free(cib); + return NULL; + } + + cib->variant = cib_file; + cib->variant_opaque = private; + + if (cib_location == NULL) { + cib_location = getenv("CIB_file"); + CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible + } + private->flags = 0; + if (cib_file_is_live(cib_location)) { + cib_set_file_flags(private, cib_file_flag_live); + crm_trace("File %s detected as live CIB", cib_location); + } + private->filename = strdup(cib_location); + + /* assign variant specific ops */ + cib->delegate_fn = cib_file_perform_op_delegate; + cib->cmds->signon = cib_file_signon; + cib->cmds->signoff = cib_file_signoff; + cib->cmds->free = cib_file_free; + cib->cmds->inputfd = cib_file_inputfd; + + cib->cmds->register_notification = cib_file_register_notification; + cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify; + + cib->cmds->client_id = cib_file_client_id; + + return cib; +} + +/*! + * \internal + * \brief Compare the calculated digest of an XML tree against a signature file + * + * \param[in] root Root of XML tree to compare + * \param[in] sigfile Name of signature file containing digest to compare + * + * \return TRUE if digests match or signature file does not exist, else FALSE + */ +static gboolean +cib_file_verify_digest(xmlNode *root, const char *sigfile) +{ + gboolean passed = FALSE; + char *expected; + int rc = pcmk__file_contents(sigfile, &expected); + + switch (rc) { + case pcmk_rc_ok: + if (expected == NULL) { + crm_err("On-disk digest at %s is empty", sigfile); + return FALSE; + } + break; + case ENOENT: + crm_warn("No on-disk digest present at %s", sigfile); + return TRUE; + default: + crm_err("Could not read on-disk digest from %s: %s", + sigfile, pcmk_rc_str(rc)); + return FALSE; + } + passed = pcmk__verify_digest(root, expected); + free(expected); + return passed; +} + +/*! + * \internal + * \brief Read an XML tree from a file and verify its digest + * + * \param[in] filename Name of XML file to read + * \param[in] sigfile Name of signature file containing digest to compare + * \param[out] root If non-NULL, will be set to pointer to parsed XML tree + * + * \return 0 if file was successfully read, parsed and verified, otherwise: + * -errno on stat() failure, + * -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or + * -pcmk_err_cib_modified if digests do not match + * \note If root is non-NULL, it is the caller's responsibility to free *root on + * successful return. + */ +int +cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root) +{ + int s_res; + struct stat buf; + char *local_sigfile = NULL; + xmlNode *local_root = NULL; + + CRM_ASSERT(filename != NULL); + if (root) { + *root = NULL; + } + + /* Verify that file exists and its size is nonzero */ + s_res = stat(filename, &buf); + if (s_res < 0) { + crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename); + return -errno; + } else if (buf.st_size == 0) { + crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename); + return -pcmk_err_cib_corrupt; + } + + /* Parse XML */ + local_root = filename2xml(filename); + if (local_root == NULL) { + crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename); + return -pcmk_err_cib_corrupt; + } + + /* If sigfile is not specified, use original file name plus .sig */ + if (sigfile == NULL) { + sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename); + } + + /* Verify that digests match */ + if (cib_file_verify_digest(local_root, sigfile) == FALSE) { + free(local_sigfile); + free_xml(local_root); + return -pcmk_err_cib_modified; + } + + free(local_sigfile); + if (root) { + *root = local_root; + } else { + free_xml(local_root); + } + return pcmk_ok; +} + +/*! + * \internal + * \brief Back up a CIB + * + * \param[in] cib_dirname Directory containing CIB file and backups + * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up + * + * \return 0 on success, -1 on error + */ +static int +cib_file_backup(const char *cib_dirname, const char *cib_filename) +{ + int rc = 0; + unsigned int seq; + char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); + char *cib_digest = crm_strdup_printf("%s.sig", cib_path); + char *backup_path; + char *backup_digest; + + // Determine backup and digest file names + if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES, + &seq) != pcmk_rc_ok) { + // @TODO maybe handle errors better ... + seq = 0; + } + backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, + CIB_SERIES_BZIP); + backup_digest = crm_strdup_printf("%s.sig", backup_path); + + /* Remove the old backups if they exist */ + unlink(backup_path); + unlink(backup_digest); + + /* Back up the CIB, by hard-linking it to the backup name */ + if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not archive %s by linking to %s", + cib_path, backup_path); + rc = -1; + + /* Back up the CIB signature similarly */ + } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not archive %s by linking to %s", + cib_digest, backup_digest); + rc = -1; + + /* Update the last counter and ensure everything is sync'd to media */ + } else { + pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq, + CIB_SERIES_MAX); + if (cib_do_chown) { + int rc2; + + if ((chown(backup_path, cib_file_owner, cib_file_group) < 0) + && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not set owner of %s", backup_path); + rc = -1; + } + if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0) + && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest); + rc = -1; + } + rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES, + cib_file_owner, cib_file_group); + if (rc2 != pcmk_rc_ok) { + crm_err("Could not set owner of sequence file in %s: %s", + cib_dirname, pcmk_rc_str(rc2)); + rc = -1; + } + } + pcmk__sync_directory(cib_dirname); + crm_info("Archived previous version as %s", backup_path); + } + + free(cib_path); + free(cib_digest); + free(backup_path); + free(backup_digest); + return rc; +} + +/*! + * \internal + * \brief Prepare CIB XML to be written to disk + * + * Set num_updates to 0, set cib-last-written to the current timestamp, + * and strip out the status section. + * + * \param[in,out] root Root of CIB XML tree + * + * \return void + */ +static void +cib_file_prepare_xml(xmlNode *root) +{ + xmlNode *cib_status_root = NULL; + + /* Always write out with num_updates=0 and current last-written timestamp */ + crm_xml_add(root, XML_ATTR_NUMUPDATES, "0"); + pcmk__xe_add_last_written(root); + + /* Delete status section before writing to file, because + * we discard it on startup anyway, and users get confused by it */ + cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE); + CRM_LOG_ASSERT(cib_status_root != NULL); + if (cib_status_root != NULL) { + free_xml(cib_status_root); + } +} + +/*! + * \internal + * \brief Write CIB to disk, along with a signature file containing its digest + * + * \param[in,out] cib_root Root of XML tree to write + * \param[in] cib_dirname Directory containing CIB and signature files + * \param[in] cib_filename Name (relative to cib_dirname) of file to write + * + * \return pcmk_ok on success, + * pcmk_err_cib_modified if existing cib_filename doesn't match digest, + * pcmk_err_cib_backup if existing cib_filename couldn't be backed up, + * or pcmk_err_cib_save if new cib_filename couldn't be saved + */ +int +cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, + const char *cib_filename) +{ + int exit_rc = pcmk_ok; + int rc, fd; + char *digest = NULL; + + /* Detect CIB version for diagnostic purposes */ + const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION); + const char *admin_epoch = crm_element_value(cib_root, + XML_ATTR_GENERATION_ADMIN); + + /* Determine full CIB and signature pathnames */ + char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); + char *digest_path = crm_strdup_printf("%s.sig", cib_path); + + /* Create temporary file name patterns for writing out CIB and signature */ + char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); + char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); + + CRM_ASSERT((cib_path != NULL) && (digest_path != NULL) + && (tmp_cib != NULL) && (tmp_digest != NULL)); + + /* Ensure the admin didn't modify the existing CIB underneath us */ + crm_trace("Reading cluster configuration file %s", cib_path); + rc = cib_file_read_and_verify(cib_path, NULL, NULL); + if ((rc != pcmk_ok) && (rc != -ENOENT)) { + crm_err("%s was manually modified while the cluster was active!", + cib_path); + exit_rc = pcmk_err_cib_modified; + goto cleanup; + } + + /* Back up the existing CIB */ + if (cib_file_backup(cib_dirname, cib_filename) < 0) { + exit_rc = pcmk_err_cib_backup; + goto cleanup; + } + + crm_debug("Writing CIB to disk"); + umask(S_IWGRP | S_IWOTH | S_IROTH); + cib_file_prepare_xml(cib_root); + + /* Write the CIB to a temporary file, so we can deploy (near) atomically */ + fd = mkstemp(tmp_cib); + if (fd < 0) { + crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + + /* Protect the temporary file */ + if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + + /* Write out the CIB */ + if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) { + crm_err("Changes couldn't be written to %s", tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + + /* Calculate CIB digest */ + digest = calculate_on_disk_digest(cib_root); + CRM_ASSERT(digest != NULL); + crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)", + (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest); + + /* Write the CIB digest to a temporary file */ + fd = mkstemp(tmp_digest); + if (fd < 0) { + crm_perror(LOG_ERR, "Could not create temporary file for CIB digest"); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + close(fd); + goto cleanup; + } + rc = pcmk__write_sync(fd, digest); + if (rc != pcmk_rc_ok) { + crm_err("Could not write digest to %s: %s", + tmp_digest, pcmk_rc_str(rc)); + exit_rc = pcmk_err_cib_save; + close(fd); + goto cleanup; + } + close(fd); + crm_debug("Wrote digest %s to disk", digest); + + /* Verify that what we wrote is sane */ + crm_info("Reading cluster configuration file %s (digest: %s)", + tmp_cib, tmp_digest); + rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL); + CRM_ASSERT(rc == 0); + + /* Rename temporary files to live, and sync directory changes to media */ + crm_debug("Activating %s", tmp_cib); + if (rename(tmp_cib, cib_path) < 0) { + crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path); + exit_rc = pcmk_err_cib_save; + } + if (rename(tmp_digest, digest_path) < 0) { + crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest, + digest_path); + exit_rc = pcmk_err_cib_save; + } + pcmk__sync_directory(cib_dirname); + + cleanup: + free(cib_path); + free(digest_path); + free(digest); + free(tmp_digest); + free(tmp_cib); + return exit_rc; +} |