diff --git a/daemons/based/Makefile.am b/daemons/based/Makefile.am index 44e5ce4a5aa..187a8a59cbc 100644 --- a/daemons/based/Makefile.am +++ b/daemons/based/Makefile.am @@ -10,13 +10,14 @@ include $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/man.mk -EXTRA_DIST = cib.pam - halibdir = $(CRM_DAEMON_DIR) halib_PROGRAMS = pacemaker-based -noinst_HEADERS = based_transaction.h +noinst_HEADERS = based_io.h +noinst_HEADERS += based_notify.h +noinst_HEADERS += based_operation.h +noinst_HEADERS += based_transaction.h noinst_HEADERS += pacemaker-based.h pacemaker_based_CFLAGS = $(CFLAGS_HARDENED_EXE) diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index f02bfe74042..6017682c065 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,29 +9,39 @@ #include -#include -#include -#include -#include - +#include // EACCES, ECONNREFUSED +#include // PRIu64 #include -#include -#include // uint32_t, uint64_t, UINT64_C() -#include -#include -#include // PRIu64 - -#include -#include -#include // xmlXPathObject, etc. - -#include -#include -#include - -#include - -#include +#include // NULL, size_t +#include // u?int*_t, UINT64_C +#include // snprintf +#include // free +#include // gid_t, uid_t +#include // LOG_INFO, LOG_DEBUG +#include // time_t +#include // close + +#include // gboolean, gpointer, g_*, etc. +#include // xmlNode +#include // xmlXPath* +#include // QB_FALSE +#include // qb_ipcs_connection_t +#include // LOG_TRACE + +#include // cib_call_options values +#include // cib__* +#include // pcmk_cluster_disconnect +#include // pcmk__cluster_send_message +#include // pcmk_find_cib_element +#include // pcmk__s, pcmk__str_eq +#include // crm_ipc_*, pcmk_ipc_* +#include // CRM_LOG_ASSERT, CRM_CHECK +#include // mainloop_* +#include // pcmk_rc_* +#include // PCMK_XA_*, PCMK_XE_* +#include // CRM_OP_* + +#include "pacemaker-based.h" #define EXIT_ESCALATION_MS 10000 @@ -1127,8 +1137,7 @@ cib_process_command(xmlNode *request, const cib__operation_t *operation, pcmk__xe_get(result_cib, PCMK_XA_NUM_UPDATES), (config_changed? " changed" : "")); - rc = activateCibXml(result_cib, config_changed, op); - rc = pcmk_legacy2rc(rc); + rc = based_activate_cib(result_cib, config_changed, op); if (rc != pcmk_rc_ok) { pcmk__err("Failed to activate new CIB: %s", pcmk_rc_str(rc)); } @@ -1183,8 +1192,8 @@ cib_process_command(xmlNode *request, const cib__operation_t *operation, cib_dryrun|cib_inhibit_notify|cib_transaction)) { pcmk__trace("Sending notifications %d", pcmk__is_set(call_options, cib_dryrun)); - cib_diff_notify(op, pcmk_rc2legacy(rc), call_id, client_id, client_name, - originator, input, cib_diff); + based_diff_notify(op, pcmk_rc2legacy(rc), call_id, client_id, + client_name, originator, input, cib_diff); } pcmk__log_xml_patchset(LOG_TRACE, cib_diff); @@ -1363,7 +1372,7 @@ terminate_cib(int exit_status) remote_tls_fd = 0; } - uninitializeCib(); + g_clear_pointer(&the_cib, pcmk__xml_free); // Exit immediately on error if (exit_status > CRM_EX_OK) { diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 981db4ffd7b..5524358b7d8 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,420 +9,617 @@ #include +#include // dirent, scandir +#include // errno, EACCES, ENODATA +#include // SIGPIPE #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include +#include // NULL +#include // rename +#include // free, mkdtemp +#include // strerror, strrchr, etc. +#include // stat, umask, etc. +#include // pid_t +#include // time_t +#include // _exit, fork + +#include // g_*, G_* +#include // xmlNode +#include // QB_FALSE, QB_TRUE +#include // qb_log_* + +#include // cib_file_* +#include // createEmptyCib +#include // pcmk__assert_asprintf, PCMK__XE_*, etc. +#include // CRM_CHECK +#include // mainloop_add_signal +#include // pcmk_legacy2rc, pcmk_rc_* +#include // pcmk_common_cleanup +#include // PCMK_XA_*, PCMK_XE_* + +#include "pacemaker-based.h" + +static bool writes_enabled = true; +static crm_trigger_t *write_trigger = NULL; + +/*! + * \internal + * \brief Process the exit status of a child forked from \c write_cib_async() + * + * \param[in] child Mainloop child data + * \param[in] core If set to 1, the child process dumped core + * \param[in] signo Signal that the child process exited with + * \param[in] exit_code Child process's exit code + */ +static void +write_cib_cb(mainloop_child_t *child, int core, int signo, int exit_code) +{ + const char *error = "Could not write CIB to disk"; -#include + if ((exit_code != 0) && writes_enabled) { + writes_enabled = false; + error = "Disabling CIB disk writes after failure"; + } -#include -#include -#include -#include -#include + if ((signo == 0) && (exit_code == 0)) { + pcmk__trace("Disk write [%lld] succeeded", (long long) child->pid); -#include + } else if (signo == 0) { + pcmk__err("%s: process %lld exited with code %d", error, + (long long) child->pid, exit_code); -crm_trigger_t *cib_writer = NULL; + } else { + pcmk__err("%s: process %lld terminated with signal %d (%s)%s", + error, (long long) child->pid, signo, strsignal(signo), + ((core != 0)? " and dumped core" : "")); + } -int write_cib_contents(gpointer p); + mainloop_trigger_complete(write_trigger); +} -static void -cib_rename(const char *old) +/*! + * \internal + * \brief Write the CIB to disk in a forked child + * + * This avoids blocking in the parent. The child writes synchronously. The + * parent tracks the child via the mainloop and runs a callback when the child + * exits. + * + * \param[in] user_data Ignored + */ +static int +write_cib_async(gpointer user_data) { - int new_fd; - char *new = pcmk__assert_asprintf("%s/cib.auto.XXXXXX", cib_root); + int rc = pcmk_rc_ok; + pid_t pid = 0; + int blackbox_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); + + /* Disable blackbox logging before the fork to avoid two processes writing + * to the same shared memory. The disable should not be done in the child, + * because this would close shared memory files in the parent. + * + * @TODO How? What is meant by this last sentence? + */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + + pid = fork(); + if (pid < 0) { + pcmk__err("Disabling disk writes after fork failure: %s", + strerror(errno)); + writes_enabled = false; + return G_SOURCE_REMOVE; + } - umask(S_IWGRP | S_IWOTH | S_IROTH); - new_fd = mkstemp(new); + if (pid > 0) { + // Parent + mainloop_child_add(pid, 0, "disk-writer", NULL, write_cib_cb); - if ((new_fd < 0) || (rename(old, new) < 0)) { - pcmk__err("Couldn't archive unusable file %s (disabling disk writes " - "and continuing)", - old); - cib_writes_enabled = FALSE; - } else { - pcmk__err("Archived unusable file %s as %s", old, new); + if (blackbox_state == QB_LOG_STATE_ENABLED) { + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); + } + + return G_SOURCE_CONTINUE; } - if (new_fd > 0) { - close(new_fd); + /* Write the CIB. Note that this modifies the_cib, but this child is about + * to exit. The parent's copy of the_cib won't be affected. + */ + rc = cib_file_write_with_digest(the_cib, cib_root, "cib.xml"); + rc = pcmk_legacy2rc(rc); + + pcmk_common_cleanup(); + + /* A nonzero exit code will cause further writes to be disabled. Use _exit() + * because exit() could affect the parent adversely. + * + * @TODO Investigate whether _exit() instead of exit() is really necessary. + * This goes back to commit 58cb43dc, which states that exit() may close + * things it shoudn't close. There is no explanation of what these things + * might be. The exit(2) man page states that exit() calls atexit/on_exit + * handlers and flushes open stdio streams. The exit(3) man page states that + * file created with tmpfile() are removed. But neither Pacemaker nor libqb + * uses atexit or on_exit, and it's not clear why we'd be worried about + * stdio streams. + */ + switch (rc) { + case pcmk_rc_ok: + _exit(CRM_EX_OK); + + case pcmk_rc_cib_modified: + _exit(CRM_EX_DIGEST); + + case pcmk_rc_cib_backup: + case pcmk_rc_cib_save: + _exit(CRM_EX_CANTCREAT); + + default: + _exit(CRM_EX_ERROR); } - free(new); } -/* - * It is the callers responsibility to free the output of this function +/*! + * \internal + * \brief Enable CIB writes to disk (signal handler) + * + * \param[in] nsig Ignored */ - -static xmlNode * -retrieveCib(const char *filename, const char *sigfile) +void +based_enable_writes(int nsig) { - xmlNode *root = NULL; - int rc = cib_file_read_and_verify(filename, sigfile, &root); - - if (rc == pcmk_ok) { - pcmk__info("Loaded CIB from %s (with digest %s)", filename, sigfile); - } else { - pcmk__warn("Continuing but NOT using CIB from %s (with digest %s): %s", - filename, sigfile, pcmk_strerror(rc)); - if (rc == -pcmk_err_cib_modified) { - // Archive the original files so the contents are not lost - cib_rename(filename); - cib_rename(sigfile); - } - } - return root; + pcmk__info("(Re)enabling disk writes"); + writes_enabled = true; } -static int cib_archive_filter(const struct dirent * a) +/*! + * \internal + * \brief Initialize data structures for \c pacemaker-based I/O + */ +void +based_io_init(void) { - int rc = 0; - // Looking for regular files starting with "cib-" and not ending in .sig - struct stat s; - char *a_path = pcmk__assert_asprintf("%s/%s", cib_root, a->d_name); + writes_enabled = !stand_alone; + if (writes_enabled + && pcmk__env_option_enabled(PCMK__SERVER_BASED, + PCMK__ENV_VALGRIND_ENABLED)) { - if(stat(a_path, &s) != 0) { - rc = errno; - pcmk__trace("%s - stat failed: %s (%d)", a->d_name, pcmk_rc_str(rc), - rc); - rc = 0; + writes_enabled = false; + pcmk__err("*** Disabling disk writes to avoid confusing Valgrind ***"); + } - } else if (!S_ISREG(s.st_mode)) { - pcmk__trace("%s - wrong type (%#o)", a->d_name, - (unsigned int) (s.st_mode & S_IFMT)); + /* @TODO Should we be setting this up if we've explicitly disabled writes + * already? + */ + mainloop_add_signal(SIGPIPE, based_enable_writes); - } else if (!g_str_has_prefix(a->d_name, "cib-")) { - pcmk__trace("%s - wrong prefix", a->d_name); + write_trigger = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_async, NULL); +} - } else if (g_str_has_suffix(a->d_name, ".sig")) { - pcmk__trace("%s - wrong suffix", a->d_name); +/*! + * \internal + * \brief Rename a CIB or digest file after digest mismatch + * + * This is just a wrapper for logging an error. The caller should disable writes + * on error. + * + * \param[in] old_path Original file path + * \param[in] new_path New file path + * + * \return Standard Pacemaker return code + */ +static int +rename_one(const char *old_path, const char *new_path) +{ + int rc = rename(old_path, new_path); - } else { - pcmk__debug("%s - candidate", a->d_name); - rc = 1; + if (rc == 0) { + return pcmk_rc_ok; } - free(a_path); + rc = errno; + pcmk__err("Failed to rename %s to %s after digest mismatch: %s. Disabling " + "disk writes.", old_path, new_path, strerror(rc)); 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; +#define CIBFILE "cib.xml" - time_t a_age = 0; - time_t b_age = 0; +/*! + * \internal + * \brief Archive the current CIB file in \c cib_root with its saved digest file + * + * When a CIB file's calculated digest doesn't match its saved one, we archive + * both the CIB file and its digest (".sig") file. This way the contents can be + * inspected for troubleshooting purposes. + * + * A subdirectory with a unique name is created in \c cib_root, using the + * \c mkdtemp() template \c "cib.auto.XXXXXX". Then \c CIB_FILE and + * CIB_FILE ".sig" are moved to that directory. + * + * \param[in] old_cibfile_path Original path of CIB file + * \param[in] old_sigfile_path Original path of digest file + */ +static void +archive_on_digest_mismatch(const char *old_cibfile_path, + const char *old_sigfile_path) +{ + char *new_dir = pcmk__assert_asprintf("%s/cib.auto.XXXXXX", cib_root); + char *new_cibfile_path = NULL; + char *new_sigfile_path = NULL; - char *a_path = pcmk__assert_asprintf("%s/%s", cib_root, a[0]->d_name); - char *b_path = pcmk__assert_asprintf("%s/%s", cib_root, b[0]->d_name); + umask(S_IWGRP | S_IWOTH | S_IROTH); - if(stat(a_path, &buf) == 0) { - a_age = buf.st_ctime; - } - if(stat(b_path, &buf) == 0) { - b_age = buf.st_ctime; + if (mkdtemp(new_dir) == NULL) { + pcmk__err("Failed to create directory to archive %s and %s after " + "digest mismatch: %s. Disabling disk writes.", + old_cibfile_path, old_sigfile_path, strerror(errno)); + writes_enabled = false; + goto done; } - free(a_path); - free(b_path); + new_cibfile_path = pcmk__assert_asprintf("%s/%s", new_dir, CIBFILE); + new_sigfile_path = pcmk__assert_asprintf("%s.sig", new_cibfile_path); + + if ((rename_one(old_cibfile_path, new_cibfile_path) != pcmk_rc_ok) + || (rename_one(old_sigfile_path, new_sigfile_path) != pcmk_rc_ok)) { - if(a_age > b_age) { - rc = 1; - } else if(a_age < b_age) { - rc = -1; + writes_enabled = false; + goto done; } - pcmk__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; + pcmk__err("Archived %s and %s in %s after digest mismatch", + old_cibfile_path, old_sigfile_path, new_dir); + +done: + free(new_dir); + free(new_cibfile_path); + free(new_sigfile_path); } -xmlNode * -readCibXmlFile(const char *dir, const char *file, bool discard_status) +/*! + * \internal + * \brief Read CIB XML from \c CIBFILE in the \c cib_root directory + * + * \return CIB XML parsed from \c CIBFILE in \c cib_root , or \c NULL if the + * file was not found or if parsing failed + * + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free(). + */ +static xmlNode * +read_current_cib(void) { - struct dirent **namelist = NULL; + char *cibfile_path = pcmk__assert_asprintf("%s/%s", cib_root, CIBFILE); + char *sigfile_path = pcmk__assert_asprintf("%s.sig", cibfile_path); + const char *sigfile = strrchr(sigfile_path, '/') + 1; - int lpc = 0; - char *sigfile = NULL; - char *sigfilepath = NULL; - char *filename = NULL; - const char *name = NULL; - const char *value = NULL; + xmlNode *cib_xml = NULL; + int rc = pcmk_rc_ok; - xmlNode *root = NULL; - xmlNode *status = NULL; + if (!pcmk__daemon_can_write(cib_root, CIBFILE) + || !pcmk__daemon_can_write(cib_root, sigfile)) { - sigfile = pcmk__assert_asprintf("%s.sig", file); - if (pcmk__daemon_can_write(dir, file) == FALSE - || pcmk__daemon_can_write(dir, sigfile) == FALSE) { cib_status = EACCES; - return NULL; + goto done; } - filename = pcmk__assert_asprintf("%s/%s", dir, file); - sigfilepath = pcmk__assert_asprintf("%s/%s", dir, sigfile); - free(sigfile); - cib_status = pcmk_rc_ok; - root = retrieveCib(filename, sigfilepath); - free(filename); - free(sigfilepath); - - if (root == NULL) { - lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort); - if (lpc < 0) { - pcmk__err("Could not check for CIB backups in %s: %s", cib_root, - pcmk_rc_str(errno)); - } - } - while (root == NULL && lpc > 1) { - int rc = pcmk_ok; + rc = cib_file_read_and_verify(cibfile_path, sigfile_path, &cib_xml); + rc = pcmk_legacy2rc(rc); - lpc--; + if (rc == pcmk_rc_ok) { + pcmk__info("Loaded CIB from %s (with digest %s)", cibfile_path, + sigfile_path); + goto done; + } - filename = pcmk__assert_asprintf("%s/%s", cib_root, - namelist[lpc]->d_name); - sigfile = pcmk__assert_asprintf("%s.sig", filename); + pcmk__warn("Continuing but NOT using CIB from %s (with digest %s): %s", + cibfile_path, sigfile_path, pcmk_rc_str(rc)); - rc = cib_file_read_and_verify(filename, sigfile, &root); - if (rc == pcmk_ok) { - pcmk__notice("Loaded CIB from last valid backup %s (with digest " - "%s)", - filename, sigfile); - } else { - pcmk__warn("Not using next most recent CIB backup from %s (with " - "digest %s): %s", - filename, sigfile, pcmk_strerror(rc)); - } - - free(namelist[lpc]); - free(filename); - free(sigfile); + if (rc == pcmk_rc_cib_modified) { + // Archive the original files so the contents are not lost + archive_on_digest_mismatch(cibfile_path, sigfile_path); } - free(namelist); - if (root == NULL) { - root = createEmptyCib(0); - pcmk__warn("Continuing with an empty configuration"); - } +done: + free(cibfile_path); + free(sigfile_path); + return cib_xml; +} - if (cib_writes_enabled - && pcmk__env_option_enabled(PCMK__SERVER_BASED, - PCMK__ENV_VALGRIND_ENABLED)) { +/*! + * \internal + * \brief \c scandir() filter for backup CIB files in \c cib_root + * + * \param[in] entry Directory entry + * + * \retval 1 if the entry is a regular file whose name begins with \c "cib-" and + * does not end with ".sig" + * \retval 0 otherwise + */ +static int +backup_cib_filter(const struct dirent *entry) +{ + char *path = pcmk__assert_asprintf("%s/%s", cib_root, entry->d_name); + struct stat sb; + int rc = stat(path, &sb); - cib_writes_enabled = FALSE; - pcmk__err("*** Disabling disk writes to avoid confusing Valgrind ***"); - } + free(path); - status = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL); - if (discard_status && status != NULL) { - // Strip out the PCMK_XE_STATUS section if there is one - pcmk__xml_free(status); - status = NULL; - } - if (status == NULL) { - pcmk__xe_create(root, PCMK_XE_STATUS); + if (rc != 0) { + pcmk__warn("Filtering %s/%s during scan for backup CIB: stat() failed: " + "%s", cib_root, entry->d_name, strerror(errno)); + return 0; } - /* Do this before schema validation happens */ + return S_ISREG(sb.st_mode) + && g_str_has_prefix(entry->d_name, "cib-") + && !g_str_has_suffix(entry->d_name, ".sig"); +} - /* fill in some defaults */ - value = pcmk__xe_get(root, PCMK_XA_ADMIN_EPOCH); - if (value == NULL) { // Not possible with schema validation enabled - pcmk__warn("Defaulting missing " PCMK_XA_ADMIN_EPOCH " to 0, but " - "cluster may get confused about which node's configuration " - "is most recent"); - pcmk__xe_set_int(root, PCMK_XA_ADMIN_EPOCH, 0); - } +/*! + * \internal + * \brief Get a file's last change time (\c ctime) + * + * The file is assumed to be a backup CIB file in the \c cib_root directory. + * + * \param[in] file Base name of file + * + * \return Last change time of \p file, or 0 on \c stat() failure + */ +static time_t +get_backup_cib_ctime(const char *file) +{ + char *path = pcmk__assert_asprintf("%s/%s", cib_root, file); + struct stat sb; + int rc = stat(path, &sb); - name = PCMK_XA_EPOCH; - value = pcmk__xe_get(root, name); - if (value == NULL) { - pcmk__xe_set_int(root, name, 0); - } + free(path); - name = PCMK_XA_NUM_UPDATES; - value = pcmk__xe_get(root, name); - if (value == NULL) { - pcmk__xe_set_int(root, name, 0); + if (rc != 0) { + pcmk__warn("Failed to stat() %s/%s while sorting backup CIBs: %s", + cib_root, file, strerror(errno)); + return 0; } - // Unset (DC should set appropriate value) - pcmk__xe_remove_attr(root, PCMK_XA_DC_UUID); + return sb.st_ctime; +} - if (discard_status) { - pcmk__log_xml_trace(root, "[on-disk]"); +/*! + * \internal + * \brief Compare directory entries based on their last change times + * + * The entries are assumed to be CIB files in the \c cib_root directory. + * + * \param[in] entry1 First directory entry to compare + * \param[in] entry2 Second directory entry to compare + * + * \retval -1 if \p entry1 was changed more recently than \p entry2 + * \retval 0 if \p entry1 was last changed at the same timestamp as \p entry2 + * \retval 1 if \p entry1 was changed less recently than \p entry2 + */ +static int +compare_backup_cibs(const struct dirent **entry1, const struct dirent **entry2) +{ + time_t ctime1 = get_backup_cib_ctime((*entry1)->d_name); + time_t ctime2 = get_backup_cib_ctime((*entry2)->d_name); + + if (ctime1 > ctime2) { + pcmk__trace("%s/%s (%lld) newer than %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return -1; } - if (!pcmk__configured_schema_validates(root)) { - cib_status = pcmk_rc_schema_validation; + if (ctime1 < ctime2) { + pcmk__trace("%s/%s (%lld) older than %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return 1; } - return root; + + pcmk__trace("%s/%s (%lld) same age as %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return 0; } -void -uninitializeCib(void) +/*! + * \internal + * \brief Read CIB XML from the last valid backup file in \c cib_root + * + * \return CIB XML parsed from the last valid backup file, or \c NULL if none + * was found + */ +static xmlNode * +read_backup_cib(void) { - xmlNode *tmp_cib = the_cib; + xmlNode *cib_xml = NULL; + struct dirent **namelist = NULL; + int num_files = scandir(cib_root, &namelist, backup_cib_filter, + compare_backup_cibs); - if (tmp_cib == NULL) { - return; + if (num_files < 0) { + pcmk__err("Could not check for CIB backups in %s: %s", cib_root, + pcmk_rc_str(errno)); + goto done; } - the_cib = NULL; - pcmk__xml_free(tmp_cib); -} + for (int i = 0; i < num_files; i++) { + const char *cibfile = namelist[i]->d_name; + char *cibfile_path = pcmk__assert_asprintf("%s/%s", cib_root, cibfile); + char *sigfile_path = pcmk__assert_asprintf("%s.sig", cibfile_path); -/* - * This method will free the old CIB pointer on success and the new one - * on failure. - */ -int -activateCibXml(xmlNode *new_cib, bool to_disk, const char *op) -{ - if (new_cib) { - xmlNode *saved_cib = the_cib; - - pcmk__assert(new_cib != saved_cib); - the_cib = new_cib; - pcmk__xml_free(saved_cib); - if (cib_writes_enabled && cib_status == pcmk_rc_ok && to_disk) { - pcmk__debug("Triggering CIB write for %s op", op); - mainloop_set_trigger(cib_writer); + int rc = cib_file_read_and_verify(cibfile_path, sigfile_path, &cib_xml); + + rc = pcmk_legacy2rc(rc); + + if (rc == pcmk_rc_ok) { + pcmk__notice("Loaded CIB from last valid backup %s (with digest " + "%s)", cibfile_path, sigfile_path); + } else { + pcmk__warn("Not using next most recent CIB backup from %s (with " + "digest %s): %s", cibfile_path, sigfile_path, + pcmk_rc_str(rc)); + } + + free(cibfile_path); + free(sigfile_path); + + if (rc == pcmk_rc_ok) { + break; } - return pcmk_ok; } - pcmk__err("Ignoring invalid CIB"); - if (the_cib) { - pcmk__warn("Reverting to last known CIB"); - } else { - pcmk__crit("Could not write out new CIB and no saved version to revert " - "to"); +done: + for (int i = 0; i < num_files; i++) { + free(namelist[i]); } - return -ENODATA; + free(namelist); + + return cib_xml; } +/*! + * \internal + * \brief Set the CIB XML's \c PCMK_XE_STATUS element to empty if appropriate + * + * Delete the current \c PCMK_XE_STATUS element if not running in stand-alone + * mode. Then create an empty \c PCMK_XE_STATUS child if either of the following + * is true: + * * not running in stand-alone mode + * * running in stand-alone mode with no \c PCMK_XE_STATUS element + * + * \param[in,out] cib_xml CIB XML + */ static void -cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) +set_empty_status(xmlNode *cib_xml) { - const char *errmsg = "Could not write CIB to disk"; + xmlNode *status = pcmk__xe_first_child(cib_xml, PCMK_XE_STATUS, NULL, NULL); - if ((exitcode != 0) && cib_writes_enabled) { - cib_writes_enabled = FALSE; - errmsg = "Disabling CIB disk writes after failure"; + if (!stand_alone) { + g_clear_pointer(&status, pcmk__xml_free); } - if ((signo == 0) && (exitcode == 0)) { - pcmk__trace("Disk write [%d] succeeded", (int) pid); - - } else if (signo == 0) { - pcmk__err("%s: process %d exited %d", errmsg, (int) pid, exitcode); + if (status == NULL) { + pcmk__xe_create(cib_xml, PCMK_XE_STATUS); + } +} - } else { - pcmk__err("%s: process %d terminated with signal %d (%s)%s", - errmsg, (int) pid, signo, strsignal(signo), - ((core != 0)? " and dumped core" : "")); +/*! + * \internal + * \brief Set the given CIB version attribute to 0 if it's not already set + * + * \param[in,out] cib_xml CIB XML + * \param[in] version_attr Version attribute + */ +static void +set_default_if_unset(xmlNode *cib_xml, const char *version_attr) +{ + if (pcmk__xe_get(cib_xml, version_attr) != NULL) { + return; } - mainloop_trigger_complete(cib_writer); + pcmk__warn("Defaulting missing %s to 0, but cluster may get confused about " + "which node's configuration is most recent", version_attr); + pcmk__xe_set_int(cib_xml, version_attr, 0); } -int -write_cib_contents(gpointer p) +/*! + * \internal + * \brief Read the most recent CIB from a file in \c cib_root + * + * This function first tries to read the CIB from a file called \c "cib.xml" in + * the \c cib_root directory. + * + * If that fails or there is a digest mismatch, it tries all the backup CIB + * files in \c cib_root, in order from most recently changed to least, moving to + * the next backup file on failure or digest mismatch. + * + * If no valid CIB file is found, this function generates an empty CIB. + * + * \return The most current CIB XML available, or an empty CIB if none is + * available (guaranteed not to be \c NULL) + * + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free(). + */ +xmlNode * +based_read_cib(void) { - int exit_rc = pcmk_ok; - xmlNode *cib_local = NULL; + static const char *version_attrs[] = { + PCMK_XA_ADMIN_EPOCH, + PCMK_XA_EPOCH, + PCMK_XA_NUM_UPDATES, + }; - /* Make a copy of the CIB to write (possibly in a forked child) */ - if (p) { - /* Synchronous write out */ - cib_local = pcmk__xml_copy(NULL, p); + xmlNode *cib_xml = read_current_cib(); - } 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) { - pcmk__err("Disabling disk writes after fork failure: %s", - pcmk_rc_str(errno)); - cib_writes_enabled = FALSE; - return FALSE; - } + if (cib_xml == NULL) { + cib_xml = read_backup_cib(); + } - 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); - } + if (cib_xml == NULL) { + cib_xml = createEmptyCib(0); + pcmk__warn("Continuing with an empty configuration"); + } - return -1; /* -1 means 'still work to do' */ - } + set_empty_status(cib_xml); + + /* Default the three version attributes to 0 if unset. The schema requires + * them to be set, so: + * * It's not possible for them to be unset if schema validation was enabled + * when the CIB file was generated, or if it was generated by Pacemaker + * and then unmodified. + * * We need to set these defaults before schema validation happens. + */ + for (int i = 0; i < PCMK__NELEM(version_attrs); i++) { + set_default_if_unset(cib_xml, version_attrs[i]); + } - /* Asynchronous write-out after a fork() */ + // The DC should set appropriate value for PCMK_XA_DC_UUID + pcmk__xe_remove_attr(cib_xml, PCMK_XA_DC_UUID); - /* In theory, we can scribble on the_cib here and not affect the parent, - * but let's be safe anyway. - */ - cib_local = pcmk__xml_copy(NULL, the_cib); + if (!stand_alone) { + pcmk__log_xml_trace(cib_xml, "on-disk"); } - /* 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 */ - pcmk__xml_free(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; - } + if (!pcmk__configured_schema_validates(cib_xml)) { + cib_status = pcmk_rc_schema_validation; + } + + return cib_xml; +} - /* Use _exit() because exit() could affect the parent adversely */ - pcmk_common_cleanup(); - _exit(exit_code); +/*! + * \internal + * \brief Activate new CIB XML + * + * This function frees the existing \c the_cib and points it to \p new_cib. + * + * \param[in] new_cib CIB XML to activate (must not be \c NULL or equal to + * \c the_cib) + * \param[in] to_disk If \c true and if the CIB status is OK and writes are + * enabled, trigger the new CIB to be written to disk + * \param[in] op Operation that triggered the activation (for logging + * only) + * + * \return Standard Pacemaker return code + * + * \note This function takes ownership of \p new_cib by assigning it to + * \c the_cib. The caller should not free it. + */ +int +based_activate_cib(xmlNode *new_cib, bool to_disk, const char *op) +{ + CRM_CHECK((new_cib != NULL) && (new_cib != the_cib), return ENODATA); + + pcmk__xml_free(the_cib); + the_cib = new_cib; + + if (to_disk && writes_enabled && (cib_status == pcmk_rc_ok)) { + pcmk__debug("Triggering CIB write for %s op", op); + mainloop_set_trigger(write_trigger); } - return exit_rc; + + return pcmk_rc_ok; } diff --git a/daemons/based/based_io.h b/daemons/based/based_io.h new file mode 100644 index 00000000000..f9bb5011a1a --- /dev/null +++ b/daemons/based/based_io.h @@ -0,0 +1,22 @@ +/* + * Copyright 2026 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. + */ + +#ifndef BASED_IO__H +#define BASED_IO__H + +#include + +#include // xmlNode + +void based_io_init(void); +void based_enable_writes(int nsig); +xmlNode *based_read_cib(void); +int based_activate_cib(xmlNode *new_cib, bool to_disk, const char *op); + +#endif // BASED_IO__H diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c index 0aaedec5001..cf6d1ad7a85 100644 --- a/daemons/based/based_messages.c +++ b/daemons/based/based_messages.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,27 +9,25 @@ #include +#include // EINVAL, ENOTCONN, EPROTO #include -#include -#include -#include -#include -#include -#include +#include // NULL +#include // free -#include -#include +#include // g_list_free_full, GList +#include // xmlNode +#include // QB_XS -#include -#include +#include // PCMK__CIB_REQUEST_UPGRADE +#include // pcmk__cluster_send_message +#include // pcmk__info, pcmk__xml_free, etc. +#include // pcmk_ipc_server +#include // CRM_CHECK +#include // pcmk_err, pcmk_ok, pcmk_rc* +#include // PCMK_XA_*, PCMK_XE_* +#include // CRM_FEATURE_SET -#include -#include - -#include -#include - -#include +#include "pacemaker-based.h" /* Maximum number of diffs to ignore while waiting for a resync */ #define MAX_DIFF_RETRY 5 @@ -307,23 +305,16 @@ cib_server_process_diff(const char *op, int options, const char *section, xmlNod // The primary instance should never ignore a diff if (sync_in_progress && !based_is_primary) { - int diff_add_updates = 0; - int diff_add_epoch = 0; - int diff_add_admin_epoch = 0; - - int diff_del_updates = 0; - int diff_del_epoch = 0; - int diff_del_admin_epoch = 0; + int source[] = { 0, 0, 0 }; + int target[] = { 0, 0, 0 }; - cib_diff_version_details(input, - &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, - &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); + pcmk__xml_patchset_versions(input, source, target); sync_in_progress++; pcmk__notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in " "progress)", - diff_del_admin_epoch, diff_del_epoch, diff_del_updates, - diff_add_admin_epoch, diff_add_epoch, diff_add_updates); + source[0], source[1], source[2], + target[0], target[1], target[2]); return -pcmk_err_diff_resync; } diff --git a/daemons/based/based_notify.c b/daemons/based/based_notify.c index dba81da44c3..d987973b145 100644 --- a/daemons/based/based_notify.c +++ b/daemons/based/based_notify.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,27 +9,23 @@ #include -#include +#include // EAGAIN +#include // PRIx64 #include -#include -#include -#include -#include // PRIx64 +#include // NULL +#include // int32_t, uint16_t +#include // ssize_t -#include -#include -#include +#include // gpointer, g_string_free +#include // xmlNode +#include // QB_XS -#include +#include // pcmk__client_t, etc. +#include // pcmk_free_ipc_event +#include // CRM_LOG_ASSERT +#include // pcmk_rc_* -#include -#include - -#include -#include - -#include -#include +#include "pacemaker-based.h" struct cib_notification_s { const xmlNode *msg; @@ -150,20 +146,10 @@ cib_notify_send(const xmlNode *xml) } void -cib_diff_notify(const char *op, int result, const char *call_id, - const char *client_id, const char *client_name, - const char *origin, xmlNode *update, xmlNode *diff) +based_diff_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff) { - int add_updates = 0; - int add_epoch = 0; - int add_admin_epoch = 0; - - int del_updates = 0; - int del_epoch = 0; - int del_admin_epoch = 0; - - uint8_t log_level = LOG_TRACE; - xmlNode *update_msg = NULL; xmlNode *wrapper = NULL; @@ -171,39 +157,6 @@ cib_diff_notify(const char *op, int result, const char *call_id, return; } - if (result != pcmk_ok) { - log_level = LOG_WARNING; - } - - cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates, - &del_admin_epoch, &del_epoch, &del_updates); - - if ((add_admin_epoch != del_admin_epoch) - || (add_epoch != del_epoch) - || (add_updates != del_updates)) { - - do_crm_log(log_level, - "Updated CIB generation %d.%d.%d to %d.%d.%d from client " - "%s%s%s (%s) (%s)", - del_admin_epoch, del_epoch, del_updates, - add_admin_epoch, add_epoch, add_updates, - client_name, - ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), - pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); - - } else if ((add_admin_epoch != 0) - || (add_epoch != 0) - || (add_updates != 0)) { - - do_crm_log(log_level, - "Local-only change to CIB generation %d.%d.%d from client " - "%s%s%s (%s) (%s)", - add_admin_epoch, add_epoch, add_updates, - client_name, - ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), - pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); - } - update_msg = pcmk__xe_create(NULL, PCMK__XE_NOTIFY); pcmk__xe_set(update_msg, PCMK__XA_T, PCMK__VALUE_CIB_NOTIFY); diff --git a/daemons/based/based_notify.h b/daemons/based/based_notify.h new file mode 100644 index 00000000000..0943d8d9dd3 --- /dev/null +++ b/daemons/based/based_notify.h @@ -0,0 +1,19 @@ +/* + * Copyright 2025 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. + */ + +#ifndef BASED_NOTIFY__H +#define BASED_NOTIFY__H + +#include // xmlNode + +void based_diff_notify(const char *op, int result, const char *call_id, + const char *client_id, const char *client_name, + const char *origin, xmlNode *update, xmlNode *diff); + +#endif // BASED_NOTIFY__H diff --git a/daemons/based/based_operation.c b/daemons/based/based_operation.c index 6c8e93f4b83..d8ac4047b15 100644 --- a/daemons/based/based_operation.c +++ b/daemons/based/based_operation.c @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the Pacemaker project contributors + * Copyright 2008-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,13 +9,14 @@ #include -#include +#include // NULL -#include -#include -#include +#include // cib__* +#include // pcmk__assert, PCMK__NELEM -static const cib__op_fn_t cib_op_functions[] = { +#include "pacemaker-based.h" + +static const cib__op_fn_t op_functions[] = { [cib__op_abs_delete] = cib_process_delete_absolute, [cib__op_apply_patch] = cib_server_process_diff, [cib__op_bump] = cib_process_bump, @@ -53,8 +54,8 @@ based_get_op_function(const cib__operation_t *operation) pcmk__assert(type >= 0); - if (type >= PCMK__NELEM(cib_op_functions)) { + if (type >= PCMK__NELEM(op_functions)) { return NULL; } - return cib_op_functions[type]; + return op_functions[type]; } diff --git a/daemons/based/based_operation.h b/daemons/based/based_operation.h new file mode 100644 index 00000000000..ee016466531 --- /dev/null +++ b/daemons/based/based_operation.h @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 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. + */ + +#ifndef BASED_OPERATION__H +#define BASED_OPERATION__H + +#include // cib__* + +cib__op_fn_t based_get_op_function(const cib__operation_t *operation); + +#endif // BASED_OPERATION__H diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c index c51d788d7f7..1c86a2ae91d 100644 --- a/daemons/based/based_remote.c +++ b/daemons/based/based_remote.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,42 +8,41 @@ */ #include -#include -#include +#include // htons +#include // errno, EAGAIN +#include // getgrgid, getgrnam, group +#include // PRIx64 +#include // sockaddr_in, INADDR_ANY #include -#include -#include -#include -#include -#include // PRIx64 -#include -#include - -#include - -#include -#include - -#include -#include - -#include -#include -#include +#include // NULL +#include // calloc, free, getenv +#include // memset +#include // sockaddr{,_storage}, AF_INET, etc. +#include // gid_t +#include // close + +#include // gboolean, gpointer, g_source_remove, etc. +#include // gnutls_bye, gnutls_deinit +#include // xmlNode +#include // QB_XS + +#include // CRM_DAEMON_GROUP +#include // pcmk__client_t, etc. +#include // CRM_CHECK +#include // mainloop_* +#include // pcmk_rc_* +#include // PCMK_XA_* +#include // CRM_OP_REGISTER #include "pacemaker-based.h" -#include - -#include -#include #if HAVE_SECURITY_PAM_APPL_H -# include -# define HAVE_PAM 1 +#include // pam_*, PAM_* +#define HAVE_PAM 1 #elif HAVE_PAM_PAM_APPL_H -# include -# define HAVE_PAM 1 +#include // pam_*, PAM_* +#define HAVE_PAM 1 #endif static pcmk__tls_t *tls = NULL; diff --git a/daemons/based/based_transaction.c b/daemons/based/based_transaction.c index 258861cdea6..021e9073798 100644 --- a/daemons/based/based_transaction.c +++ b/daemons/based/based_transaction.c @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 the Pacemaker project contributors + * Copyright 2023-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,10 +9,17 @@ #include +#include // EOPNOTSUPP #include +#include // NULL +#include // free -#include -#include +#include // xmlNode + +#include // cib__* +#include // pcmk__client_t, pcmk__s, pcmk__xe_*, etc. +#include // CRM_CHECK +#include // pcmk_rc_* #include "pacemaker-based.h" @@ -54,8 +61,8 @@ based_transaction_source_str(const pcmk__client_t *client, const char *origin) * \return Standard Pacemaker return code */ static int -process_transaction_requests(xmlNodePtr transaction, - const pcmk__client_t *client, const char *source) +process_transaction_requests(xmlNode *transaction, const pcmk__client_t *client, + const char *source) { for (xmlNode *request = pcmk__xe_first_child(transaction, PCMK__XE_CIB_COMMAND, NULL, @@ -116,10 +123,10 @@ process_transaction_requests(xmlNodePtr transaction, * success, and for freeing it on failure. */ int -based_commit_transaction(xmlNodePtr transaction, const pcmk__client_t *client, - const char *origin, xmlNodePtr *result_cib) +based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, + const char *origin, xmlNode **result_cib) { - xmlNodePtr saved_cib = the_cib; + xmlNode *saved_cib = the_cib; int rc = pcmk_rc_ok; char *source = NULL; diff --git a/daemons/based/based_transaction.h b/daemons/based/based_transaction.h index 9935c736a68..19dc01ba529 100644 --- a/daemons/based/based_transaction.h +++ b/daemons/based/based_transaction.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 the Pacemaker project contributors + * Copyright 2023-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,15 +10,14 @@ #ifndef BASED_TRANSACTION__H #define BASED_TRANSACTION__H -#include +#include // xmlNode -#include +#include // pcmk__client_t char *based_transaction_source_str(const pcmk__client_t *client, const char *origin); -int based_commit_transaction(xmlNodePtr transaction, - const pcmk__client_t *client, - const char *origin, xmlNodePtr *result_cib); +int based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, + const char *origin, xmlNode **result_cib); #endif // BASED_TRANSACTION__H diff --git a/daemons/based/cib.pam b/daemons/based/cib.pam deleted file mode 100644 index 5d0f6553acc..00000000000 --- a/daemons/based/cib.pam +++ /dev/null @@ -1,6 +0,0 @@ -# login: auth account password session -# may require permission to read /etc/shadow -auth include common-auth -account include common-account -password include common-password -session include common-session diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index 41712b43af7..2392ea2fcee 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,24 +9,31 @@ #include +#include // errno +#include // initgroups +#include // SIGTERM #include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include +#include // NULL, size_t +#include // free +#include // LOG_INFO +#include // gid_t, uid_t +#include // setgid, setuid + +#include // cpg_* +#include // g_*, G_*, etc. +#include // xmlNode + +#include // CRM_CONFIG_DIR, CRM_DAEMON_USER +#include // cib_read_config +#include // pcmk_cluster_* +#include // pcmk__node_update, etc. +#include // crm_ipc_* +#include // crm_log_* +#include // mainloop_add_signal +#include // CRM_EX_*, pcmk_rc_* +#include // PCMK_XA_REMOTE_*_PORT + +#include "pacemaker-based.h" #define SUMMARY "daemon for managing the configuration of a Pacemaker cluster" @@ -37,9 +44,7 @@ pcmk_cluster_t *crm_cluster = NULL; GMainLoop *mainloop = NULL; gchar *cib_root = NULL; -static bool preserve_status = false; -gboolean cib_writes_enabled = TRUE; gboolean stand_alone = FALSE; int remote_fd = 0; @@ -49,18 +54,10 @@ GHashTable *config_hash = NULL; static void cib_init(void); void cib_shutdown(int nsig); -static bool startCib(const char *filename); -extern int write_cib_contents(gpointer p); +static bool startCib(void); static crm_exit_t exit_code = CRM_EX_OK; -static void -cib_enable_writes(int nsig) -{ - pcmk__info("(Re)enabling disk writes"); - cib_writes_enabled = TRUE; -} - /*! * \internal * \brief Set up options, users, and groups for stand-alone mode @@ -76,9 +73,6 @@ setup_stand_alone(GError **error) gid_t gid = 0; int rc = pcmk_rc_ok; - preserve_status = true; - cib_writes_enabled = FALSE; - rc = pcmk__daemon_user(&uid, &gid); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_FATAL; @@ -135,12 +129,20 @@ based_metadata(pcmk__output_t *out) pcmk__opt_based); } +static gboolean +disk_writes_cb(const gchar *option_name, const gchar *optarg, gpointer data, + GError **error) +{ + based_enable_writes(0); + return TRUE; +} + static GOptionEntry entries[] = { { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone, "(Advanced use only) Run in stand-alone mode", NULL }, - { "disk-writes", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, - &cib_writes_enabled, + { "disk-writes", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + disk_writes_cb, "(Advanced use only) Enable disk writes (enabled by default unless in " "stand-alone mode)", NULL }, @@ -206,9 +208,8 @@ main(int argc, char **argv) } mainloop_add_signal(SIGTERM, cib_shutdown); - mainloop_add_signal(SIGPIPE, cib_enable_writes); - cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); + based_io_init(); if ((g_strv_length(processed_args) >= 2) && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { @@ -384,7 +385,7 @@ cib_init(void) config_hash = pcmk__strkey_table(free, free); - if (!startCib("cib.xml")) { + if (!startCib()) { pcmk__crit("Cannot start CIB... terminating"); crm_exit(CRM_EX_NOINPUT); } @@ -407,12 +408,12 @@ cib_init(void) } static bool -startCib(const char *filename) +startCib(void) { - xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); + xmlNode *cib = based_read_cib(); int port = 0; - if (activateCibXml(cib, true, "start") != 0) { + if (based_activate_cib(cib, true, "start") != pcmk_rc_ok) { return false; } diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h index 8ef9396f123..2e018ec77b4 100644 --- a/daemons/based/pacemaker-based.h +++ b/daemons/based/pacemaker-based.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -11,28 +11,19 @@ # define PACEMAKER_BASED__H #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include +#include // uint32_t, UINT64_C -#include "based_transaction.h" +#include // GHashTable, g_hash_table_lookup +#include // xmlNode +#include // qb_ipcs_service_t + +#include // pcmk_cluster_t +#include // pcmk__client_t -#include +#include "based_io.h" +#include "based_operation.h" +#include "based_notify.h" +#include "based_transaction.h" #define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->priv->node_name) @@ -48,8 +39,6 @@ enum cib_client_flags { extern bool based_is_primary; extern GHashTable *config_hash; extern xmlNode *the_cib; -extern crm_trigger_t *cib_writer; -extern gboolean cib_writes_enabled; extern GMainLoop *mainloop; extern pcmk_cluster_t *crm_cluster; @@ -75,10 +64,6 @@ int cib_process_request(xmlNode *request, bool privileged, void cib_shutdown(int nsig); void terminate_cib(int exit_status); -void uninitializeCib(void); -xmlNode *readCibXmlFile(const char *dir, const char *file, bool discard_status); -int activateCibXml(xmlNode *doc, bool to_disk, const char *op); - int cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, @@ -123,11 +108,6 @@ int cib_process_schemas(const char *op, int options, const char *section, void send_sync_request(const char *host); int sync_our_cib(xmlNode *request, bool all); -cib__op_fn_t based_get_op_function(const cib__operation_t *operation); -void cib_diff_notify(const char *op, int result, const char *call_id, - const char *client_id, const char *client_name, - const char *origin, xmlNode *update, xmlNode *diff); - static inline const char * cib_config_lookup(const char *opt) { diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index 40f4fa284b3..da4fece9267 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -201,7 +201,8 @@ get_schema_files(void) * saving them to disk. */ static void -get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) +get_schema_files_complete(mainloop_child_t *p, int core, int signo, + int exitcode) { const char *errmsg = "Could not load additional schema files"; @@ -217,12 +218,12 @@ get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, i } else { if (signo == 0) { - pcmk__err("%s: process %lld exited %d", errmsg, (long long) pid, + pcmk__err("%s: process %lld exited %d", errmsg, (long long) p->pid, exitcode); } else { pcmk__err("%s: process %lld terminated with signal %d (%s)%s", - errmsg, (long long) pid, signo, strsignal(signo), + errmsg, (long long) p->pid, signo, strsignal(signo), ((core != 0)? " and dumped core" : "")); } diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index 7e785159cfc..954b922faf4 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -104,7 +104,8 @@ static bool fatal_error = false; static int child_liveness(pcmkd_child_t *child); static gboolean escalate_shutdown(gpointer data); static int start_child(pcmkd_child_t *child); -static void pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode); +static void pcmk_child_exit(mainloop_child_t *p, int core, int signo, + int exitcode); static void pcmk_process_exit(pcmkd_child_t *child); static gboolean pcmk_shutdown_worker(gpointer user_data); static void stop_child(pcmkd_child_t *child, int signal); @@ -253,7 +254,7 @@ escalate_shutdown(gpointer data) } static void -pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) +pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) { pcmkd_child_t *child = mainloop_child_userdata(p); const char *name = mainloop_child_name(p); @@ -262,7 +263,7 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco // cts-lab looks for this message do_crm_log(((signo == SIGKILL)? LOG_WARNING : LOG_ERR), "%s[%d] terminated with signal %d (%s)%s", - name, pid, signo, strsignal(signo), + name, p->pid, signo, strsignal(signo), (core? " and dumped core" : "")); pcmk_process_exit(child); return; @@ -270,13 +271,13 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco switch(exitcode) { case CRM_EX_OK: - pcmk__info("%s[%d] exited with status %d (%s)", name, pid, exitcode, - crm_exit_str(exitcode)); + pcmk__info("%s[%d] exited with status %d (%s)", name, p->pid, + exitcode, crm_exit_str(exitcode)); break; case CRM_EX_FATAL: pcmk__warn("Shutting cluster down because %s[%d] had fatal failure", - name, pid); + name, p->pid); child->flags &= ~child_respawn; fatal_error = true; pcmk_shutdown(SIGTERM); @@ -289,7 +290,7 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco child->flags &= ~child_respawn; fatal_error = true; msg = pcmk__assert_asprintf("Subdaemon %s[%d] requested panic", - name, pid); + name, p->pid); pcmk__panic(msg); // Should never get here @@ -300,8 +301,8 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco default: // cts-lab looks for this message - pcmk__err("%s[%d] exited with status %d (%s)", name, pid, exitcode, - crm_exit_str(exitcode)); + pcmk__err("%s[%d] exited with status %d (%s)", name, p->pid, + exitcode, crm_exit_str(exitcode)); break; } diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 4c4c54fd473..d493b69e2a2 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -95,9 +95,6 @@ enum cib__op_type { cib__op_schemas, }; -gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, - int *_admin_epoch, int *_epoch, int *_updates); - gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); typedef int (*cib__op_fn_t)(const char *, int, const char *, xmlNode *, diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index 874cd162a89..25f9731870a 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -12,8 +12,8 @@ #define PCMK__INCLUDED_CRM_COMMON_INTERNAL_H -#include #include +#include #include #include #include diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 30bde33111a..e10e0625976 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -44,6 +44,10 @@ typedef struct mainloop_child_s mainloop_child_t; // NOTE: sbd (as of at least 1.5.2) uses this typedef struct mainloop_timer_s mainloop_timer_t; +//! \deprecated This has been for internal use only since its creation. +typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *p, int core, + int signo, int exitcode); + void mainloop_cleanup(void); // NOTE: sbd (as of at least 1.5.2) uses this @@ -167,18 +171,13 @@ void mainloop_del_fd(mainloop_io_t * client); * Create a new tracked process * To track a process group, use -pid */ -void mainloop_child_add(pid_t pid, - int timeout, - const char *desc, +void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *userdata, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); + pcmk__mainloop_child_exit_fn_t exit_fn); -void mainloop_child_add_with_flags(pid_t pid, - int timeout, - const char *desc, - void *userdata, - enum mainloop_child_flags, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); +void mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, + void *userdata, enum mainloop_child_flags, + pcmk__mainloop_child_exit_fn_t exit_fn); void *mainloop_child_userdata(mainloop_child_t * child); int mainloop_child_timeout(mainloop_child_t * child); diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 3b5d07ec1d7..db3a49b64b4 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2015-2026 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,7 +14,9 @@ #ifndef PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H #define PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H -#include // guint +#include // pid_t + +#include // gboolean, guint #include // crm_ipc_t #include // ipc_client_callbacks, mainloop_* @@ -23,6 +25,19 @@ extern "C" { #endif +struct mainloop_child_s { + pid_t pid; + char *desc; + unsigned timerid; + gboolean timeout; + void *privatedata; + + enum mainloop_child_flags flags; + + /* Called when a process dies */ + pcmk__mainloop_child_exit_fn_t exit_fn; +}; + int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 043c57e14ec..70c409ba1f8 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -39,26 +39,6 @@ cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates) return TRUE; } -gboolean -cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, - int *_admin_epoch, int *_epoch, int *_updates) -{ - int add[] = { 0, 0, 0 }; - int del[] = { 0, 0, 0 }; - - pcmk__xml_patchset_versions(diff, del, add); - - *admin_epoch = add[0]; - *epoch = add[1]; - *updates = add[2]; - - *_admin_epoch = del[0]; - *_epoch = del[1]; - *_updates = del[2]; - - return TRUE; -} - /*! * \internal * \brief Get the XML patchset from a CIB diff notification diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index b38ed21dfb7..4d3dd9ae6ac 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -23,19 +23,6 @@ #include -struct mainloop_child_s { - pid_t pid; - char *desc; - unsigned timerid; - gboolean timeout; - void *privatedata; - - enum mainloop_child_flags flags; - - /* Called when a process dies */ - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode); -}; - struct trigger_s { GSource source; gboolean running; @@ -1171,8 +1158,8 @@ child_waitpid(mainloop_child_t *child, int flags) callback_needed = false; } - if (callback_needed && child->callback) { - child->callback(child, child->pid, core, signo, exitcode); + if (callback_needed && child->exit_fn) { + child->exit_fn(child, core, signo, exitcode); } return callback_needed; } @@ -1264,8 +1251,10 @@ mainloop_child_kill(pid_t pid) * completed process. */ void -mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *privatedata, enum mainloop_child_flags flags, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)) +mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, + void *privatedata, + enum mainloop_child_flags flags, + pcmk__mainloop_child_exit_fn_t exit_fn) { static bool need_init = TRUE; mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); @@ -1274,7 +1263,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *pr child->timerid = 0; child->timeout = FALSE; child->privatedata = privatedata; - child->callback = callback; + child->exit_fn = exit_fn; child->flags = flags; child->desc = pcmk__str_copy(desc); @@ -1296,9 +1285,9 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *pr void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)) + pcmk__mainloop_child_exit_fn_t exit_fn) { - mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, callback); + mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, exit_fn); } static gboolean diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 2c1218a96d1..28057b002e3 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -705,19 +705,17 @@ parse_exit_reason_from_stderr(svc_action_t *op) * \brief Process the completion of an asynchronous child process * * \param[in,out] p Child process that completed - * \param[in] pid Process ID of child * \param[in] core (Unused) * \param[in] signo Signal that interrupted child, if any * \param[in] exitcode Exit status of child process */ static void -async_action_complete(mainloop_child_t *p, pid_t pid, int core, int signo, - int exitcode) +async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) { svc_action_t *op = mainloop_child_userdata(p); mainloop_clear_child_userdata(p); - CRM_CHECK(op->pid == pid, + CRM_CHECK(op->pid == p->pid, services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Bug in mainloop handling"); return);