summaryrefslogtreecommitdiffstats
path: root/daemons/based/based_io.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/based/based_io.c')
-rw-r--r--daemons/based/based_io.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c
new file mode 100644
index 0000000..fc34f39
--- /dev/null
+++ b/daemons/based/based_io.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2004-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <crm/crm.h>
+
+#include <crm/cib.h>
+#include <crm/common/util.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/cib/internal.h>
+#include <crm/cluster.h>
+
+#include <pacemaker-based.h>
+
+crm_trigger_t *cib_writer = NULL;
+
+int write_cib_contents(gpointer p);
+
+static void
+cib_rename(const char *old)
+{
+ int new_fd;
+ char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
+
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+ new_fd = mkstemp(new);
+ crm_err("Archiving unusable file %s as %s", old, new);
+ if ((new_fd < 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;
+
+ crm_info("Reading cluster configuration file %s (digest: %s)",
+ filename, sigfile);
+ 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 = crm_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 (pcmk__ends_with_ext(a->d_name, ".sig")) {
+ 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 = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name);
+ char *b_path = crm_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 (%lu) vs. %s (%lu) : %d",
+ a[0]->d_name, (unsigned long)a_age,
+ b[0]->d_name, (unsigned long)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 *sigfilepath = 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;
+
+ sigfile = crm_strdup_printf("%s.sig", file);
+ if (pcmk__daemon_can_write(dir, file) == FALSE
+ || pcmk__daemon_can_write(dir, sigfile) == FALSE) {
+ cib_status = -EACCES;
+ return NULL;
+ }
+
+ filename = crm_strdup_printf("%s/%s", dir, file);
+ sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile);
+ free(sigfile);
+
+ cib_status = pcmk_ok;
+ root = retrieveCib(filename, sigfilepath);
+ free(filename);
+ free(sigfilepath);
+
+ 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 = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
+ sigfile = crm_strdup_printf("%s.sig", filename);
+
+ crm_info("Reading cluster configuration file %s (digest: %s)",
+ filename, sigfile);
+ 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 &&
+ (crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) {
+
+ cib_writes_enabled = FALSE;
+ crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
+ }
+
+ 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 schema 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 recommended 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 (DC should set appropriate value)
+ 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",
+ pcmk__s(validation, "no schema specified"));
+ 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 schema");
+ cib_status = -pcmk_err_schema_validation;
+ }
+ }
+
+ return root;
+}
+
+gboolean
+uninitializeCib(void)
+{
+ xmlNode *tmp_cib = the_cib;
+
+ if (tmp_cib == NULL) {
+ crm_debug("The CIB has already been deallocated.");
+ return 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 free the old CIB pointer on success and the new one
+ * on failure.
+ */
+int
+activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
+{
+ if (new_cib) {
+ xmlNode *saved_cib = the_cib;
+
+ CRM_ASSERT(new_cib != saved_cib);
+ the_cib = new_cib;
+ 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;
+ }
+
+ crm_err("Ignoring invalid CIB");
+ if (the_cib) {
+ crm_warn("Reverting to last known CIB");
+ } else {
+ crm_crit("Could not write out new CIB and no saved version to revert to");
+ }
+ return -ENODATA;
+}
+
+static void
+cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
+{
+ const char *errmsg = "Could not write CIB to disk";
+
+ if ((exitcode != 0) && cib_writes_enabled) {
+ cib_writes_enabled = FALSE;
+ errmsg = "Disabling CIB disk writes after failure";
+ }
+
+ if ((signo == 0) && (exitcode == 0)) {
+ crm_trace("Disk write [%d] succeeded", (int) pid);
+
+ } else if (signo == 0) {
+ crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
+
+ } else {
+ crm_err("%s: process %d terminated with signal %d (%s)%s",
+ errmsg, (int) pid, signo, strsignal(signo),
+ (core? " and dumped core" : ""));
+ }
+
+ mainloop_trigger_complete(cib_writer);
+}
+
+int
+write_cib_contents(gpointer p)
+{
+ int exit_rc = pcmk_ok;
+ xmlNode *cib_local = 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' */
+ }
+
+ /* Asynchronous write-out after a fork() */
+
+ /* In theory, we can scribble on the_cib here and not affect the parent,
+ * but let's be safe anyway.
+ */
+ cib_local = copy_xml(the_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) {
+ crm_exit_t exit_code = CRM_EX_OK;
+
+ switch (exit_rc) {
+ case pcmk_ok:
+ exit_code = CRM_EX_OK;
+ break;
+ case pcmk_err_cib_modified:
+ exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest
+ break;
+ case pcmk_err_cib_backup: // Existing CIB couldn't be backed up
+ case pcmk_err_cib_save: // New CIB couldn't be saved
+ exit_code = CRM_EX_CANTCREAT;
+ break;
+ default:
+ exit_code = CRM_EX_ERROR;
+ break;
+ }
+
+ /* Use _exit() because exit() could affect the parent adversely */
+ _exit(exit_code);
+ }
+ return exit_rc;
+}