diff --git a/cib/io.c b/cib/io.c index 5918d61ea4..b36e46df9e 100644 --- a/cib/io.c +++ b/cib/io.c @@ -1,633 +1,488 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#define CIB_SERIES "cib" -#define CIB_SERIES_MAX 100 -#define CIB_SERIES_BZIP FALSE /* Must be false due to the way archived - * copies are created - ie. with calls to - * link() - */ - extern const char *cib_root; crm_trigger_t *cib_writer = NULL; gboolean initialized = FALSE; extern int cib_status; int write_cib_contents(gpointer p); static void cib_rename(const char *old) { int new_fd; char *new = g_strdup_printf("%s/cib.auto.XXXXXX", cib_root); crm_err("Archiving unusable file %s as %s", old, new); umask(S_IWGRP | S_IWOTH | S_IROTH); if ((new_fd = mkstemp(new) < 0) || (rename(old, new) < 0)) { crm_perror(LOG_ERR, "Couldn't rename %s as %s", old, new); crm_err("Disabling disk writes and continuing"); cib_writes_enabled = FALSE; } if (new_fd > 0) { close(new_fd); } free(new); } /* * It is the callers responsibility to free the output of this function */ static xmlNode * retrieveCib(const char *filename, const char *sigfile) { xmlNode *root = NULL; switch (cib_file_read_and_verify(filename, sigfile, &root)) { case -pcmk_err_cib_corrupt: crm_warn("Continuing but %s will NOT be used.", filename); break; case -pcmk_err_cib_modified: /* Archive the original files so the contents are not lost */ crm_warn("Continuing but %s will NOT be used.", filename); cib_rename(filename); cib_rename(sigfile); break; } return root; } /* * for OSs without support for direntry->d_type, like Solaris */ #ifndef DT_UNKNOWN # define DT_UNKNOWN 0 # define DT_FIFO 1 # define DT_CHR 2 # define DT_DIR 4 # define DT_BLK 6 # define DT_REG 8 # define DT_LNK 10 # define DT_SOCK 12 # define DT_WHT 14 #endif /*DT_UNKNOWN*/ static int cib_archive_filter(const struct dirent * a) { int rc = 0; /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */ struct stat s; char *a_path = g_strdup_printf("%s/%s", cib_root, a->d_name); if(stat(a_path, &s) != 0) { rc = errno; crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_strerror(rc), rc); rc = 0; } else if ((s.st_mode & S_IFREG) != S_IFREG) { unsigned char dtype; #ifdef HAVE_STRUCT_DIRENT_D_TYPE dtype = a->d_type; #else switch (s.st_mode & S_IFMT) { case S_IFREG: dtype = DT_REG; break; case S_IFDIR: dtype = DT_DIR; break; case S_IFCHR: dtype = DT_CHR; break; case S_IFBLK: dtype = DT_BLK; break; case S_IFLNK: dtype = DT_LNK; break; case S_IFIFO: dtype = DT_FIFO; break; case S_IFSOCK: dtype = DT_SOCK; break; default: dtype = DT_UNKNOWN; break; } #endif crm_trace("%s - wrong type (%d)", a->d_name, dtype); } else if(strstr(a->d_name, "cib-") != a->d_name) { crm_trace("%s - wrong prefix", a->d_name); } else if(strstr(a->d_name, ".sig") != NULL) { crm_trace("%s - wrong suffix", a->d_name); } else { crm_debug("%s - candidate", a->d_name); rc = 1; } free(a_path); return rc; } static int cib_archive_sort(const struct dirent ** a, const struct dirent **b) { /* Order by creation date - most recently created file first */ int rc = 0; struct stat buf; time_t a_age = 0; time_t b_age = 0; char *a_path = g_strdup_printf("%s/%s", cib_root, a[0]->d_name); char *b_path = g_strdup_printf("%s/%s", cib_root, b[0]->d_name); if(stat(a_path, &buf) == 0) { a_age = buf.st_ctime; } if(stat(b_path, &buf) == 0) { b_age = buf.st_ctime; } free(a_path); free(b_path); if(a_age > b_age) { rc = 1; } else if(a_age < b_age) { rc = -1; } crm_trace("%s (%u) vs. %s (%u) : %d", a[0]->d_name, a_age, b[0]->d_name, b_age, rc); return rc; } xmlNode * readCibXmlFile(const char *dir, const char *file, gboolean discard_status) { struct dirent **namelist = NULL; int lpc = 0; char *sigfile = NULL; char *filename = NULL; const char *name = NULL; const char *value = NULL; const char *validation = NULL; const char *use_valgrind = getenv("PCMK_valgrind_enabled"); xmlNode *root = NULL; xmlNode *status = NULL; if (!crm_is_writable(dir, file, CRM_DAEMON_USER, NULL, FALSE)) { cib_status = -EACCES; return NULL; } filename = crm_concat(dir, file, '/'); sigfile = crm_concat(filename, "sig", '.'); cib_status = pcmk_ok; root = retrieveCib(filename, sigfile); free(filename); free(sigfile); if (root == NULL) { crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root); lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort); if (lpc < 0) { crm_perror(LOG_NOTICE, "scandir(%s) failed", cib_root); } } while (root == NULL && lpc > 1) { crm_debug("Testing %d candidates", lpc); lpc--; filename = g_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name); sigfile = crm_concat(filename, "sig", '.'); if (cib_file_read_and_verify(filename, sigfile, &root) < 0) { crm_warn("Continuing but %s will NOT be used.", filename); } else { crm_notice("Continuing with last valid configuration archive: %s", filename); } free(namelist[lpc]); free(filename); free(sigfile); } free(namelist); if (root == NULL) { root = createEmptyCib(0); crm_warn("Continuing with an empty configuration."); } if (cib_writes_enabled && use_valgrind) { if (crm_is_true(use_valgrind) || strstr(use_valgrind, "cib")) { cib_writes_enabled = FALSE; crm_err("*********************************************************"); crm_err("*** Disabling disk writes to avoid confusing Valgrind ***"); crm_err("*********************************************************"); } } status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE); if (discard_status && status != NULL) { /* strip out the status section if there is one */ free_xml(status); status = NULL; } if (status == NULL) { create_xml_node(root, XML_CIB_TAG_STATUS); } /* Do this before DTD validation happens */ /* fill in some defaults */ name = XML_ATTR_GENERATION_ADMIN; value = crm_element_value(root, name); if (value == NULL) { crm_warn("No value for %s was specified in the configuration.", name); crm_warn("The reccomended course of action is to shutdown," " run crm_verify and fix any errors it reports."); crm_warn("We will default to zero and continue but may get" " confused about which configuration to use if" " multiple nodes are powered up at the same time."); crm_xml_add_int(root, name, 0); } name = XML_ATTR_GENERATION; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } name = XML_ATTR_NUMUPDATES; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } /* unset these and require the DC/CCM to update as needed */ xml_remove_prop(root, XML_ATTR_DC_UUID); if (discard_status) { crm_log_xml_trace(root, "[on-disk]"); } validation = crm_element_value(root, XML_ATTR_VALIDATION); if (validate_xml(root, NULL, TRUE) == FALSE) { crm_err("CIB does not validate with %s", crm_str(validation)); cib_status = -pcmk_err_schema_validation; } else if (validation == NULL) { int version = 0; update_validation(&root, &version, 0, FALSE, FALSE); if (version > 0) { crm_notice("Enabling %s validation on" " the existing (sane) configuration", get_schema_name(version)); } else { crm_err("CIB does not validate with any known DTD or schema"); cib_status = -pcmk_err_schema_validation; } } return root; } /* * The caller should never free the return value */ xmlNode * get_the_CIB(void) { return the_cib; } gboolean uninitializeCib(void) { xmlNode *tmp_cib = the_cib; if (tmp_cib == NULL) { crm_debug("The CIB has already been deallocated."); return FALSE; } initialized = FALSE; the_cib = NULL; crm_debug("Deallocating the CIB."); free_xml(tmp_cib); crm_debug("The CIB has been deallocated."); return TRUE; } /* * This method will not free the old CIB pointer or the new one. * We rely on the caller to have saved a pointer to the old CIB * and to free the old/bad one depending on what is appropriate. */ gboolean initializeCib(xmlNode * new_cib) { if (new_cib == NULL) { return FALSE; } the_cib = new_cib; initialized = TRUE; return TRUE; } /* * This method will free the old CIB pointer on success and the new one * on failure. */ int activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op) { xmlNode *saved_cib = the_cib; CRM_ASSERT(new_cib != saved_cib); if (initializeCib(new_cib) == FALSE) { free_xml(new_cib); crm_err("Ignoring invalid or NULL CIB"); if (saved_cib != NULL) { crm_warn("Reverting to last known CIB"); if (initializeCib(saved_cib) == FALSE) { /* oh we are so dead */ crm_crit("Couldn't re-initialize the old CIB!"); exit(1); } } else { crm_crit("Could not write out new CIB and no saved" " version to revert to"); } return -ENODATA; } free_xml(saved_cib); if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) { crm_debug("Triggering CIB write for %s op", op); mainloop_set_trigger(cib_writer); } return pcmk_ok; } static void cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) { if (signo) { crm_notice("Disk write process terminated with signal %d (pid=%d, core=%d)", signo, pid, core); } else { do_crm_log(exitcode == 0 ? LOG_TRACE : LOG_ERR, "Disk write process exited (pid=%d, rc=%d)", pid, exitcode); } if (exitcode != 0 && cib_writes_enabled) { crm_err("Disabling disk writes after write failure"); cib_writes_enabled = FALSE; } mainloop_trigger_complete(cib_writer); } int write_cib_contents(gpointer p) { int exit_rc = pcmk_ok; - int rc; - int seq; - char *digest = NULL; - xmlNode *cib_status_root = NULL; - xmlNode *cib_local = NULL; - int tmp_cib_fd = 0; - int tmp_digest_fd = 0; - char *tmp_cib = NULL; - char *tmp_digest = NULL; - - char *digest_file = NULL; - char *primary_file = NULL; - - char *backup_file = NULL; - char *backup_digest = NULL; - - const char *epoch = NULL; - const char *admin_epoch = NULL; - /* Make a copy of the CIB to write (possibly in a forked child) */ if (p) { /* Synchronous write out */ cib_local = copy_xml(p); } else { int pid = 0; int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); /* Turn it off before the fork() to avoid: * - 2 processes writing to the same shared mem * - the child needing to disable it * (which would close it from underneath the parent) * This way, the shared mem files are already closed */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); pid = fork(); if (pid < 0) { crm_perror(LOG_ERR, "Disabling disk writes after fork failure"); cib_writes_enabled = FALSE; return FALSE; } if (pid) { /* Parent */ mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete); if (bb_state == QB_LOG_STATE_ENABLED) { /* Re-enable now that it it safe */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); } return -1; /* -1 means 'still work to do' */ } /* A-synchronous write out after a fork() */ /* In theory we can scribble on "the_cib" here and not affect the parent * But lets be safe anyway */ cib_local = copy_xml(the_cib); } - primary_file = crm_concat(cib_root, "cib.xml", '/'); - digest_file = crm_concat(primary_file, "sig", '.'); - - /* Ensure the admin didn't modify the existing CIB underneath us */ - rc = cib_file_read_and_verify(primary_file, NULL, NULL); - if ((rc != pcmk_ok) && (rc != -ENOENT)) { - crm_err("%s was manually modified while the cluster was active!", - primary_file); - exit_rc = pcmk_err_cib_modified; - goto cleanup; - } - - /* Figure out what backup file sequence number to use */ - seq = get_last_sequence(cib_root, CIB_SERIES); - backup_file = generate_series_filename(cib_root, CIB_SERIES, seq, - CIB_SERIES_BZIP); - backup_digest = crm_concat(backup_file, "sig", '.'); - - /* Remove the old backups at that sequence number */ - unlink(backup_file); - unlink(backup_digest); - - /* Back up the CIB and its signature */ - if (link(primary_file, backup_file) < 0) { - switch (errno) { - case ENOENT: /* No file to back up */ - goto writeout; - break; - default: - exit_rc = pcmk_err_cib_backup; - crm_err("Cannot link %s to %s: %s (%d)", primary_file, - backup_file, pcmk_strerror(errno), errno); - } - goto cleanup; - } - if ((link(digest_file, backup_digest) < 0) && (errno != ENOENT)) { - exit_rc = pcmk_err_cib_backup; - crm_perror(LOG_ERR, "Cannot link %s to %s", digest_file, backup_digest); - goto cleanup; - } - write_last_sequence(cib_root, CIB_SERIES, seq + 1, CIB_SERIES_MAX); - crm_sync_directory(cib_root); - crm_info("Archived previous version as %s", backup_file); - - writeout: - crm_debug("Writing CIB to disk"); - - /* Always write out with num_updates=0 */ - crm_xml_add(cib_local, XML_ATTR_NUMUPDATES, "0"); - - /* Delete status section before writing, because - * we discard it on startup anyway, and users get confused by it */ - if (p == NULL) { - cib_status_root = find_xml_node(cib_local, XML_CIB_TAG_STATUS, TRUE); - CRM_LOG_ASSERT(cib_status_root != NULL); - if (cib_status_root != NULL) { - free_xml(cib_status_root); - } - } - - tmp_cib = g_strdup_printf("%s/cib.XXXXXX", cib_root); - tmp_digest = g_strdup_printf("%s/cib.XXXXXX", cib_root); - - umask(S_IWGRP | S_IWOTH | S_IROTH); - - tmp_cib_fd = mkstemp(tmp_cib); - if (tmp_cib_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; - } - - fchmod(tmp_cib_fd, S_IRUSR | S_IWUSR); /* establish the correct permissions */ - crm_xml_add_last_written(cib_local); - if (write_xml_fd(cib_local, tmp_cib, 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 the digest after writing, because we updated the last-written field */ - digest = calculate_on_disk_digest(cib_local); - CRM_ASSERT(digest != NULL); - epoch = crm_element_value(cib_local, XML_ATTR_GENERATION); - admin_epoch = crm_element_value(cib_local, XML_ATTR_GENERATION_ADMIN); - crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)", - (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest); - - tmp_digest_fd = mkstemp(tmp_digest); - if ((tmp_digest_fd < 0) || (crm_write_sync(tmp_digest_fd, digest) < 0)) { - crm_perror(LOG_ERR, "Could not write digest to file %s", tmp_digest); - exit_rc = pcmk_err_cib_save; - goto cleanup; - } - crm_debug("Wrote digest %s to disk", digest); - - /* Verify that what we wrote is sane */ - CRM_ASSERT(cib_file_read_and_verify(tmp_cib, tmp_digest, NULL) == 0); - - /* Rename temporary files to live, and ensure they are sync'd to media */ - crm_debug("Activating %s", tmp_cib); - if (rename(tmp_cib, primary_file) < 0) { - crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, primary_file); - exit_rc = pcmk_err_cib_save; - } - if (rename(tmp_digest, digest_file) < 0) { - crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest, - digest_file); - exit_rc = pcmk_err_cib_save; - } - crm_sync_directory(cib_root); - - cleanup: - free(backup_digest); - free(primary_file); - free(backup_file); - free(digest_file); - free(digest); - free(tmp_digest); - free(tmp_cib); + /* Write the CIB */ + exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml"); + /* A nonzero exit code will cause further writes to be disabled */ free_xml(cib_local); - if (p == NULL) { /* Use _exit() because exit() could affect the parent adversely */ _exit(exit_rc); } return exit_rc; } diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 19c3a88d33..431a2bd4d2 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,192 +1,195 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # define CIB_OP_SLAVE "cib_slave" # define CIB_OP_SLAVEALL "cib_slave_all" # define CIB_OP_MASTER "cib_master" # define CIB_OP_SYNC "cib_sync" # define CIB_OP_SYNC_ONE "cib_sync_one" # define CIB_OP_ISMASTER "cib_ismaster" # define CIB_OP_BUMP "cib_bump" # define CIB_OP_QUERY "cib_query" # define CIB_OP_CREATE "cib_create" # define CIB_OP_UPDATE "cib_update" # define CIB_OP_MODIFY "cib_modify" # define CIB_OP_DELETE "cib_delete" # define CIB_OP_ERASE "cib_erase" # define CIB_OP_REPLACE "cib_replace" # define CIB_OP_APPLY_DIFF "cib_apply_diff" # define CIB_OP_UPGRADE "cib_upgrade" # define CIB_OP_UPGRADE_OK "cib_upgrade_ok" # define CIB_OP_DELETE_ALT "cib_delete_alt" # define CIB_OP_NOTIFY "cib_notify" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_CALLBACK_TOKEN "cib_async_id" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define F_CIB_SCHEMA_MAX "cib_schema_max" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" # define cib_channel_ro "cib_ro" # define cib_channel_rw "cib_rw" # define cib_channel_shm "cib_shm" void cib_add_digest(xmlNode * source, xmlNode * target); void fix_cib_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed); gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean startCib(const char *filename); int cib_compare_generation(xmlNode * left, xmlNode * right); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); void verify_cib_options(GHashTable * options); gboolean cib_internal_config_changed(xmlNode * diff); extern GHashTable *cib_op_callback_table; typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output); xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_native_register_notification(cib_t * cib, const char *callback, int enabled); gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)); int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff); gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code); int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); int cib_internal_op(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 cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root); +int cib_file_read_and_verify(const char *filename, const char *sigfile, + xmlNode **root); +int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, + const char *cib_filename); #endif diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c index e571b55fe8..2932b43a11 100644 --- a/lib/cib/cib_file.c +++ b/lib/cib/cib_file.c @@ -1,459 +1,626 @@ /* * Copyright (c) 2004 International Business Machines * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define cib_flag_dirty 0x00001 typedef struct cib_file_opaque_s { int flags; char *filename; } cib_file_opaque_t; int cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options); 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 cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type); int cib_file_signoff(cib_t * cib); int cib_file_free(cib_t * cib); static int cib_file_inputfd(cib_t * cib) { return -EPROTONOSUPPORT; } static int cib_file_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)) { return -EPROTONOSUPPORT; } static int cib_file_register_notification(cib_t * cib, const char *callback, int enabled) { return -EPROTONOSUPPORT; } /*! * \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 = crm_read_contents(sigfile); if (expected == NULL) { switch (errno) { case 0: crm_err("On-disk digest is empty"); return FALSE; case ENOENT: crm_warn("No on-disk digest present"); return TRUE; default: crm_perror(LOG_ERR, "Could not read on-disk digest from %s", sigfile); return FALSE; } } passed = crm_digest_verify(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[in] 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 */ crm_info("Reading cluster configuration file %s", filename); 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_concat(filename, "sig", '.'); } /* Verify that digests match */ crm_info("Verifying cluster configuration signature from %s", sigfile); 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; } +#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 + */ + +static int +cib_file_backup(const char *cib_dirname, const char *cib_filename) +{ + int rc = 0; + char *cib_path = crm_concat(cib_dirname, cib_filename, '/'); + char *cib_digest = crm_concat(cib_path, "sig", '.'); + + /* Figure out what backup file sequence number to use */ + int seq = get_last_sequence(cib_dirname, CIB_SERIES); + char *backup_path = generate_series_filename(cib_dirname, CIB_SERIES, seq, + CIB_SERIES_BZIP); + char *backup_digest = crm_concat(backup_path, "sig", '.'); + + CRM_ASSERT((cib_path != NULL) && (cib_digest != NULL) + && (backup_path != NULL) && (backup_digest != NULL)); + + /* 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 { + write_last_sequence(cib_dirname, CIB_SERIES, seq + 1, CIB_SERIES_MAX); + crm_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; +} + +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"); + crm_xml_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); + } +} + +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_concat(cib_dirname, cib_filename, '/'); + char *digest_path = crm_concat(cib_path, "sig", '.'); + + /* Create temporary file name patterns for writing out CIB and signature */ + char *tmp_cib = g_strdup_printf("%s/cib.XXXXXX", cib_dirname); + char *tmp_digest = g_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 */ + 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; + } + fchmod(fd, S_IRUSR | S_IWUSR); /* establish the correct permissions */ + 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_write_sync(fd, digest) < 0)) { + crm_perror(LOG_ERR, "Could not write digest to file %s", tmp_digest); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } + crm_debug("Wrote digest %s to disk", digest); + + /* Verify that what we wrote is sane */ + 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; + } + crm_sync_directory(cib_dirname); + + cleanup: + free(cib_path); + free(digest_path); + free(digest); + free(tmp_digest); + free(tmp_cib); + return exit_rc; +} + cib_t * cib_file_new(const char *cib_location) { cib_file_opaque_t *private = NULL; cib_t *cib = cib_new_variant(); private = calloc(1, sizeof(cib_file_opaque_t)); cib->variant = cib_file; cib->variant_opaque = private; if (cib_location == NULL) { cib_location = getenv("CIB_file"); } private->flags = 0; 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; return cib; } static xmlNode *in_mem_cib = NULL; static int load_file_cib(const char *filename) { int rc = pcmk_ok; struct stat buf; xmlNode *root = NULL; gboolean dtd_ok = TRUE; const char *ignore_dtd = NULL; xmlNode *status = NULL; rc = stat(filename, &buf); if (rc == 0) { root = filename2xml(filename); if (root == NULL) { return -pcmk_err_schema_validation; } } else { return -ENXIO; } rc = 0; status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE); if (status == NULL) { create_xml_node(root, XML_CIB_TAG_STATUS); } ignore_dtd = crm_element_value(root, XML_ATTR_VALIDATION); dtd_ok = validate_xml(root, NULL, TRUE); if (dtd_ok == FALSE) { crm_err("CIB does not validate against %s", ignore_dtd); rc = -pcmk_err_schema_validation; goto bail; } in_mem_cib = root; return rc; bail: free_xml(root); root = NULL; return rc; } 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; private->flags = 0; if (private->filename == FALSE) { rc = -EINVAL; } else { rc = load_file_cib(private->filename); } if (rc == pcmk_ok) { crm_debug("%s: Opened connection to local file '%s'", name, private->filename); cib->state = cib_connected_command; cib->type = cib_command; } else { fprintf(stderr, "%s: Connection to local file '%s' failed: %s\n", name, private->filename, pcmk_strerror(rc)); } return rc; } int cib_file_signoff(cib_t * cib) { int rc = pcmk_ok; cib_file_opaque_t *private = cib->variant_opaque; crm_debug("Signing out of the CIB Service"); cib->state = cib_disconnected; cib->type = cib_no_connection; if(is_not_set(private->flags, cib_flag_dirty)) { /* No changes to write out */ free_xml(in_mem_cib); return pcmk_ok; } else if (strstr(private->filename, ".bz2") != NULL) { rc = write_xml_file(in_mem_cib, private->filename, TRUE); } else { rc = write_xml_file(in_mem_cib, private->filename, FALSE); } if (rc > 0) { crm_info("Wrote CIB to %s", private->filename); rc = pcmk_ok; } else { crm_err("Could not write CIB to %s: %s (%d)", private->filename, pcmk_strerror(rc), rc); } free_xml(in_mem_cib); return rc; } 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; } struct cib_func_entry { const char *op; gboolean read_only; cib_op_t fn; }; /* *INDENT-OFF* */ static struct cib_func_entry cib_file_ops[] = { {CIB_OP_QUERY, TRUE, cib_process_query}, {CIB_OP_MODIFY, FALSE, cib_process_modify}, {CIB_OP_APPLY_DIFF, FALSE, cib_process_diff}, {CIB_OP_BUMP, FALSE, cib_process_bump}, {CIB_OP_REPLACE, FALSE, cib_process_replace}, {CIB_OP_CREATE, FALSE, cib_process_create}, {CIB_OP_DELETE, FALSE, cib_process_delete}, {CIB_OP_ERASE, FALSE, cib_process_erase}, {CIB_OP_UPGRADE, FALSE, cib_process_upgrade}, }; /* *INDENT-ON* */ int cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options) { return cib_file_perform_op_delegate(cib, op, host, section, data, output_data, call_options, NULL); } 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 = DIMOF(cib_file_ops); cib_file_opaque_t *private = cib->variant_opaque; crm_info("%s on %s", op, section); call_options |= (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 (safe_str_eq(op, cib_file_ops[lpc].op)) { 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, "dummy-token", op, host, section, data, call_options, user_name); #if ENABLE_ACL if(user_name) { crm_xml_add(request, XML_ACL_TAG_USER, user_name); } crm_trace("Performing %s operation as %s", op, user_name); #endif /* Mirror the logic in cib_prepare_common() */ if (section != NULL && data != NULL && crm_str_eq(crm_element_name(data), XML_TAG_CIB, TRUE)) { data = get_object_root(section, data); } 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) { xml_log_patchset(LOG_DEBUG, "cib:diff", cib_diff); free_xml(in_mem_cib); in_mem_cib = result_cib; private->flags |= cib_flag_dirty; } free_xml(cib_diff); if (cib->op_callback != NULL) { cib->op_callback(NULL, cib->call_id, rc, output); } if (output_data && output) { if(output == in_mem_cib) { *output_data = copy_xml(output); } else { *output_data = output; } } else if(output != in_mem_cib) { free_xml(output); } free(effective_user); return rc; }