diff --git a/include/crm/common/logging_internal.h b/include/crm/common/logging_internal.h
index 05d857f33f..97adc8876e 100644
--- a/include/crm/common/logging_internal.h
+++ b/include/crm/common/logging_internal.h
@@ -1,88 +1,90 @@
 /*
  * Copyright 2015-2023 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.
  */
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 #ifndef PCMK__LOGGING_INTERNAL_H
 #  define PCMK__LOGGING_INTERNAL_H
 
 #  include <crm/common/logging.h>
 
 /*!
  * \internal
  * \brief Log a configuration error
  *
  * \param[in] fmt   printf(3)-style format string
  * \param[in] ...   Arguments for format string
  */
 #  define pcmk__config_err(fmt...) do {     \
         crm_config_error = TRUE;            \
         crm_err(fmt);                       \
     } while (0)
 
 /*!
  * \internal
  * \brief Log a configuration warning
  *
  * \param[in] fmt   printf(3)-style format string
  * \param[in] ...   Arguments for format string
  */
 #  define pcmk__config_warn(fmt...) do {    \
         crm_config_warning = TRUE;          \
         crm_warn(fmt);                      \
     } while (0)
 
 /*!
  * \internal
  * \brief Execute code depending on whether trace logging is enabled
  *
  * This is similar to \p do_crm_log_unlikely() except instead of logging, it
  * selects one of two code blocks to execute.
  *
  * \param[in] if_action    Code block to execute if trace logging is enabled
  * \param[in] else_action  Code block to execute if trace logging is not enabled
  *
  * \note Neither \p if_action nor \p else_action can contain a \p break or
  *       \p continue statement.
  */
 #  define pcmk__if_tracing(if_action, else_action) do {                 \
         static struct qb_log_callsite *trace_cs = NULL;                 \
                                                                         \
         if (trace_cs == NULL) {                                         \
             trace_cs = qb_log_callsite_get(__func__, __FILE__,          \
                                            "if_tracing", LOG_TRACE,     \
                                            __LINE__, crm_trace_nonlog); \
         }                                                               \
         if (crm_is_callsite_active(trace_cs, LOG_TRACE, 0)) {           \
             if_action;                                                  \
         } else {                                                        \
             else_action;                                                \
         }                                                               \
     } while (0)
 
 /*!
  * \internal
  * \brief Initialize logging for command line tools
  *
  * \param[in] name      The name of the program
  * \param[in] verbosity How verbose to be in logging
  *
  * \note \p verbosity is not the same as the logging level (LOG_ERR, etc.).
  */
 void pcmk__cli_init_logging(const char *name, unsigned int verbosity);
 
 int pcmk__add_logfile(const char *filename);
 
+void pcmk__free_common_logger(void);
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/common/logging.c b/lib/common/logging.c
index e42c097ed3..517a971385 100644
--- a/lib/common/logging.c
+++ b/lib/common/logging.c
@@ -1,1147 +1,1168 @@
 /*
  * Copyright 2004-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/utsname.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <limits.h>
 #include <ctype.h>
 #include <pwd.h>
 #include <grp.h>
 #include <time.h>
 #include <libgen.h>
 #include <signal.h>
 #include <bzlib.h>
 
 #include <qb/qbdefs.h>
 
 #include <crm/crm.h>
 #include <crm/common/mainloop.h>
 
 // Use high-resolution (millisecond) timestamps if libqb supports them
 #ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
 #define TIMESTAMP_FORMAT_SPEC "%%T"
 typedef struct timespec *log_time_t;
 #else
 #define TIMESTAMP_FORMAT_SPEC "%%t"
 typedef time_t log_time_t;
 #endif
 
 unsigned int crm_log_level = LOG_INFO;
 unsigned int crm_trace_nonlog = 0;
 bool pcmk__is_daemon = false;
 char *pcmk__our_nodename = NULL;
 
 static unsigned int crm_log_priority = LOG_NOTICE;
 static GLogFunc glib_log_default = NULL;
+static pcmk__output_t *logger_out = NULL;
 
 static gboolean crm_tracing_enabled(void);
 
 static void
 crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message,
                  gpointer user_data)
 {
     int log_level = LOG_WARNING;
     GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK);
     static struct qb_log_callsite *glib_cs = NULL;
 
     if (glib_cs == NULL) {
         glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
                                       LOG_DEBUG, __LINE__, crm_trace_nonlog);
     }
 
 
     switch (msg_level) {
         case G_LOG_LEVEL_CRITICAL:
             log_level = LOG_CRIT;
 
             if (crm_is_callsite_active(glib_cs, LOG_DEBUG, 0) == FALSE) {
                 /* log and record how we got here */
                 crm_abort(__FILE__, __func__, __LINE__, message, TRUE, TRUE);
             }
             break;
 
         case G_LOG_LEVEL_ERROR:
             log_level = LOG_ERR;
             break;
         case G_LOG_LEVEL_MESSAGE:
             log_level = LOG_NOTICE;
             break;
         case G_LOG_LEVEL_INFO:
             log_level = LOG_INFO;
             break;
         case G_LOG_LEVEL_DEBUG:
             log_level = LOG_DEBUG;
             break;
 
         case G_LOG_LEVEL_WARNING:
         case G_LOG_FLAG_RECURSION:
         case G_LOG_FLAG_FATAL:
         case G_LOG_LEVEL_MASK:
             log_level = LOG_WARNING;
             break;
     }
 
     do_crm_log(log_level, "%s: %s", log_domain, message);
 }
 
 #ifndef NAME_MAX
 #  define NAME_MAX 256
 #endif
 
 /*!
  * \internal
  * \brief Write out a blackbox (enabling blackboxes if needed)
  *
  * \param[in] nsig  Signal number that was received
  *
  * \note This is a true signal handler, and so must be async-safe.
  */
 static void
 crm_trigger_blackbox(int nsig)
 {
     if(nsig == SIGTRAP) {
         /* Turn it on if it wasn't already */
         crm_enable_blackbox(nsig);
     }
     crm_write_blackbox(nsig, NULL);
 }
 
 void
 crm_log_deinit(void)
 {
     if (glib_log_default != NULL) {
         g_log_set_default_handler(glib_log_default, NULL);
     }
 }
 
 #define FMT_MAX 256
 
 /*!
  * \internal
  * \brief Set the log format string based on the passed-in method
  *
  * \param[in] method        The detail level of the log output
  * \param[in] daemon        The daemon ID included in error messages
  * \param[in] use_pid       Cached result of getpid() call, for efficiency
  * \param[in] use_nodename  Cached result of uname() call, for efficiency
  *
  */
 
 /* XXX __attribute__((nonnull)) for use_nodename parameter */
 static void
 set_format_string(int method, const char *daemon, pid_t use_pid,
                   const char *use_nodename)
 {
     if (method == QB_LOG_SYSLOG) {
         // The system log gets a simplified, user-friendly format
         crm_extended_logging(method, QB_FALSE);
         qb_log_format_set(method, "%g %p: %b");
 
     } else {
         // Everything else gets more detail, for advanced troubleshooting
 
         int offset = 0;
         char fmt[FMT_MAX];
 
         if (method > QB_LOG_STDERR) {
             // If logging to file, prefix with timestamp, node name, daemon ID
             offset += snprintf(fmt + offset, FMT_MAX - offset,
                                TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ",
                                 use_nodename, daemon, (unsigned long) use_pid);
         }
 
         // Add function name (in parentheses)
         offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n");
         if (crm_tracing_enabled()) {
             // When tracing, add file and line number
             offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l");
         }
         offset += snprintf(fmt + offset, FMT_MAX - offset, ")");
 
         // Add tag (if any), severity, and actual message
         offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b");
 
         CRM_LOG_ASSERT(offset > 0);
         qb_log_format_set(method, fmt);
     }
 }
 
 #define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
 
 static bool
 logfile_disabled(const char *filename)
 {
     return pcmk__str_eq(filename, PCMK__VALUE_NONE, pcmk__str_casei)
            || pcmk__str_eq(filename, "/dev/null", pcmk__str_none);
 }
 
 /*!
  * \internal
  * \brief Fix log file ownership if group is wrong or doesn't have access
  *
  * \param[in] filename  Log file name (for logging only)
  * \param[in] logfd     Log file descriptor
  *
  * \return Standard Pacemaker return code
  */
 static int
 chown_logfile(const char *filename, int logfd)
 {
     uid_t pcmk_uid = 0;
     gid_t pcmk_gid = 0;
     struct stat st;
     int rc;
 
     // Get the log file's current ownership and permissions
     if (fstat(logfd, &st) < 0) {
         return errno;
     }
 
     // Any other errors don't prevent file from being used as log
 
     rc = pcmk_daemon_user(&pcmk_uid, &pcmk_gid);
     if (rc != pcmk_ok) {
         rc = pcmk_legacy2rc(rc);
         crm_warn("Not changing '%s' ownership because user information "
                  "unavailable: %s", filename, pcmk_rc_str(rc));
         return pcmk_rc_ok;
     }
     if ((st.st_gid == pcmk_gid)
         && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
         return pcmk_rc_ok;
     }
     if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
         crm_warn("Couldn't change '%s' ownership to user %s gid %d: %s",
              filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
     }
     return pcmk_rc_ok;
 }
 
 // Reset log file permissions (using environment variable if set)
 static void
 chmod_logfile(const char *filename, int logfd)
 {
     const char *modestr = getenv("PCMK_logfile_mode");
     mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
 
     if (modestr != NULL) {
         long filemode_l = strtol(modestr, NULL, 8);
 
         if ((filemode_l != LONG_MIN) && (filemode_l != LONG_MAX)) {
             filemode = (mode_t) filemode_l;
         }
     }
     if ((filemode != 0) && (fchmod(logfd, filemode) < 0)) {
         crm_warn("Couldn't change '%s' mode to %04o: %s",
                  filename, filemode, strerror(errno));
     }
 }
 
 // If we're root, correct a log file's permissions if needed
 static int
 set_logfile_permissions(const char *filename, FILE *logfile)
 {
     if (geteuid() == 0) {
         int logfd = fileno(logfile);
         int rc = chown_logfile(filename, logfd);
 
         if (rc != pcmk_rc_ok) {
             return rc;
         }
         chmod_logfile(filename, logfd);
     }
     return pcmk_rc_ok;
 }
 
 // Enable libqb logging to a new log file
 static void
 enable_logfile(int fd)
 {
     qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE);
 #if 0
     qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); // Turn on synchronous writes
 #endif
 
 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
     // Longer than default, for logging long XML lines
     qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800);
 #endif
 
     crm_update_callsites();
 }
 
 static inline void
 disable_logfile(int fd)
 {
     qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_FALSE);
 }
 
 static void
 setenv_logfile(const char *filename)
 {
     // Some resource agents will log only if environment variable is set
     if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
         pcmk__set_env_option(PCMK__ENV_LOGFILE, filename);
     }
 }
 
 /*!
  * \brief Add a file to be used as a Pacemaker detail log
  *
  * \param[in] filename  Name of log file to use
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__add_logfile(const char *filename)
 {
     /* No log messages from this function will be logged to the new log!
      * If another target such as syslog has already been added, the messages
      * should show up there.
      */
 
     int fd = 0;
     int rc = pcmk_rc_ok;
     FILE *logfile = NULL;
     bool is_default = false;
 
     static int default_fd = -1;
     static bool have_logfile = false;
 
     // Use default if caller didn't specify (and we don't already have one)
     if (filename == NULL) {
         if (have_logfile) {
             return pcmk_rc_ok;
         }
         filename = DEFAULT_LOG_FILE;
     }
 
     // If the user doesn't want logging, we're done
     if (logfile_disabled(filename)) {
         return pcmk_rc_ok;
     }
 
     // If the caller wants the default and we already have it, we're done
     is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
     if (is_default && (default_fd >= 0)) {
         return pcmk_rc_ok;
     }
 
     // Check whether we have write access to the file
     logfile = fopen(filename, "a");
     if (logfile == NULL) {
         rc = errno;
         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " uid=%u gid=%u",
                  filename, strerror(rc), geteuid(), getegid());
         return rc;
     }
 
     rc = set_logfile_permissions(filename, logfile);
     if (rc != pcmk_rc_ok) {
         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " permissions",
                  filename, strerror(rc));
         fclose(logfile);
         return rc;
     }
 
     // Close and reopen as libqb logging target
     fclose(logfile);
     fd = qb_log_file_open(filename);
     if (fd < 0) {
         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " qb_log_file_open",
                  filename, strerror(-fd));
         return -fd; // == +errno
     }
 
     if (is_default) {
         default_fd = fd;
         setenv_logfile(filename);
 
     } else if (default_fd >= 0) {
         crm_notice("Switching logging to %s", filename);
         disable_logfile(default_fd);
     }
 
     crm_notice("Additional logging available in %s", filename);
     enable_logfile(fd);
     have_logfile = true;
     return pcmk_rc_ok;
 }
 
 static int blackbox_trigger = 0;
 static volatile char *blackbox_file_prefix = NULL;
 
 static void
 blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
                 const char *msg)
 {
     if(cs && cs->priority < LOG_ERR) {
         crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
     } else {
         crm_write_blackbox(0, cs);
     }
 }
 
 static void
 crm_control_blackbox(int nsig, bool enable)
 {
     int lpc = 0;
 
     if (blackbox_file_prefix == NULL) {
         pid_t pid = getpid();
 
         blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu",
                                                  CRM_BLACKBOX_DIR,
                                                  crm_system_name,
                                                  (unsigned long) pid);
     }
 
     if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */
         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);      /* Setting the size seems to disable it */
 
         /* Enable synchronous logging */
         for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
             qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
         }
 
         crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
 
         /* Save to disk on abnormal termination */
         crm_signal_handler(SIGSEGV, crm_trigger_blackbox);
         crm_signal_handler(SIGABRT, crm_trigger_blackbox);
         crm_signal_handler(SIGILL,  crm_trigger_blackbox);
         crm_signal_handler(SIGBUS,  crm_trigger_blackbox);
         crm_signal_handler(SIGFPE,  crm_trigger_blackbox);
 
         crm_update_callsites();
 
         blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
         qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
         crm_trace("Trigger: %d is %d %d", blackbox_trigger,
                   qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED);
 
         crm_update_callsites();
 
     } else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
 
         /* Disable synchronous logging again when the blackbox is disabled */
         for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
             qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
         }
     }
 }
 
 void
 crm_enable_blackbox(int nsig)
 {
     crm_control_blackbox(nsig, TRUE);
 }
 
 void
 crm_disable_blackbox(int nsig)
 {
     crm_control_blackbox(nsig, FALSE);
 }
 
 /*!
  * \internal
  * \brief Write out a blackbox, if blackboxes are enabled
  *
  * \param[in] nsig  Signal that was received
  * \param[in] cs    libqb callsite
  *
  * \note This may be called via a true signal handler and so must be async-safe.
  * @TODO actually make this async-safe
  */
 void
 crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
 {
     static volatile int counter = 1;
     static volatile time_t last = 0;
 
     char buffer[NAME_MAX];
     time_t now = time(NULL);
 
     if (blackbox_file_prefix == NULL) {
         return;
     }
 
     switch (nsig) {
         case 0:
         case SIGTRAP:
             /* The graceful case - such as assertion failure or user request */
 
             if (nsig == 0 && now == last) {
                 /* Prevent over-dumping */
                 return;
             }
 
             snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++);
             if (nsig == SIGTRAP) {
                 crm_notice("Blackbox dump requested, please see %s for contents", buffer);
 
             } else if (cs) {
                 syslog(LOG_NOTICE,
                        "Problem detected at %s:%d (%s), please see %s for additional details",
                        cs->function, cs->lineno, cs->filename, buffer);
             } else {
                 crm_notice("Problem detected, please see %s for additional details", buffer);
             }
 
             last = now;
             qb_log_blackbox_write_to_file(buffer);
 
             /* Flush the existing contents
              * A size change would also work
              */
             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
             break;
 
         default:
             /* Do as little as possible, just try to get what we have out
              * We logged the filename when the blackbox was enabled
              */
             crm_signal_handler(nsig, SIG_DFL);
             qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
             raise(nsig);
             break;
     }
 }
 
 static const char *
 crm_quark_to_string(uint32_t tag)
 {
     const char *text = g_quark_to_string(tag);
 
     if (text) {
         return text;
     }
     return "";
 }
 
 static void
 crm_log_filter_source(int source, const char *trace_files, const char *trace_fns,
                       const char *trace_fmts, const char *trace_tags, const char *trace_blackbox,
                       struct qb_log_callsite *cs)
 {
     if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
         return;
     } else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) {
         /* Blackbox gets everything if enabled */
         qb_bit_set(cs->targets, source);
 
     } else if (source == blackbox_trigger && blackbox_trigger > 0) {
         /* Should this log message result in the blackbox being dumped */
         if (cs->priority <= LOG_ERR) {
             qb_bit_set(cs->targets, source);
 
         } else if (trace_blackbox) {
             char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno);
 
             if (strstr(trace_blackbox, key) != NULL) {
                 qb_bit_set(cs->targets, source);
             }
             free(key);
         }
 
     } else if (source == QB_LOG_SYSLOG) {       /* No tracing to syslog */
         if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) {
             qb_bit_set(cs->targets, source);
         }
         /* Log file tracing options... */
     } else if (cs->priority <= crm_log_level) {
         qb_bit_set(cs->targets, source);
     } else if (trace_files && strstr(trace_files, cs->filename) != NULL) {
         qb_bit_set(cs->targets, source);
     } else if (trace_fns && strstr(trace_fns, cs->function) != NULL) {
         qb_bit_set(cs->targets, source);
     } else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) {
         qb_bit_set(cs->targets, source);
     } else if (trace_tags
                && cs->tags != 0
                && cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) {
         qb_bit_set(cs->targets, source);
     }
 }
 
 static void
 crm_log_filter(struct qb_log_callsite *cs)
 {
     int lpc = 0;
     static int need_init = 1;
     static const char *trace_fns = NULL;
     static const char *trace_tags = NULL;
     static const char *trace_fmts = NULL;
     static const char *trace_files = NULL;
     static const char *trace_blackbox = NULL;
 
     if (need_init) {
         need_init = 0;
         trace_fns = getenv("PCMK_trace_functions");
         trace_fmts = getenv("PCMK_trace_formats");
         trace_tags = getenv("PCMK_trace_tags");
         trace_files = getenv("PCMK_trace_files");
         trace_blackbox = getenv("PCMK_trace_blackbox");
 
         if (trace_tags != NULL) {
             uint32_t tag;
             char token[500];
             const char *offset = NULL;
             const char *next = trace_tags;
 
             do {
                 offset = next;
                 next = strchrnul(offset, ',');
                 snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset);
 
                 tag = g_quark_from_string(token);
                 crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags);
 
                 if (next[0] != 0) {
                     next++;
                 }
 
             } while (next != NULL && next[0] != 0);
         }
     }
 
     cs->targets = 0;            /* Reset then find targets to enable */
     for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
         crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox,
                               cs);
     }
 }
 
 gboolean
 crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
 {
     gboolean refilter = FALSE;
 
     if (cs == NULL) {
         return FALSE;
     }
 
     if (cs->priority != level) {
         cs->priority = level;
         refilter = TRUE;
     }
 
     if (cs->tags != tags) {
         cs->tags = tags;
         refilter = TRUE;
     }
 
     if (refilter) {
         crm_log_filter(cs);
     }
 
     if (cs->targets == 0) {
         return FALSE;
     }
     return TRUE;
 }
 
 void
 crm_update_callsites(void)
 {
     static gboolean log = TRUE;
 
     if (log) {
         log = FALSE;
         crm_debug
             ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s",
              crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"),
              getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags"));
     }
     qb_log_filter_fn_set(crm_log_filter);
 }
 
 static gboolean
 crm_tracing_enabled(void)
 {
     if (crm_log_level == LOG_TRACE) {
         return TRUE;
     } else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions")
                || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) {
         return TRUE;
     }
     return FALSE;
 }
 
 static int
 crm_priority2int(const char *name)
 {
     struct syslog_names {
         const char *name;
         int priority;
     };
     static struct syslog_names p_names[] = {
         {"emerg", LOG_EMERG},
         {"alert", LOG_ALERT},
         {"crit", LOG_CRIT},
         {"error", LOG_ERR},
         {"warning", LOG_WARNING},
         {"notice", LOG_NOTICE},
         {"info", LOG_INFO},
         {"debug", LOG_DEBUG},
         {NULL, -1}
     };
     int lpc;
 
     for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) {
         if (pcmk__str_eq(p_names[lpc].name, name, pcmk__str_none)) {
             return p_names[lpc].priority;
         }
     }
     return crm_log_priority;
 }
 
 
 /*!
  * \internal
  * \brief Set the identifier for the current process
  *
  * If the identifier crm_system_name is not already set, then it is set as follows:
  * - it is passed to the function via the "entity" parameter, or
  * - it is derived from the executable name
  *
  * The identifier can be used in logs, IPC, and more.
  *
  * This method also sets the PCMK_service environment variable.
  *
  * \param[in] entity  If not NULL, will be assigned to the identifier
  * \param[in] argc    The number of command line parameters
  * \param[in] argv    The command line parameter values
  */
 static void
 set_identity(const char *entity, int argc, char *const *argv)
 {
     if (crm_system_name != NULL) {
         return; // Already set, don't overwrite
     }
 
     if (entity != NULL) {
         crm_system_name = strdup(entity);
 
     } else if ((argc > 0) && (argv != NULL)) {
         char *mutable = strdup(argv[0]);
         char *modified = basename(mutable);
 
         if (strstr(modified, "lt-") == modified) {
             modified += 3;
         }
         crm_system_name = strdup(modified);
         free(mutable);
 
     } else {
         crm_system_name = strdup("Unknown");
     }
 
     CRM_ASSERT(crm_system_name != NULL);
 
     setenv("PCMK_service", crm_system_name, 1);
 }
 
 void
 crm_log_preinit(const char *entity, int argc, char *const *argv)
 {
     /* Configure libqb logging with nothing turned on */
 
     struct utsname res;
     int lpc = 0;
     int32_t qb_facility = 0;
     pid_t pid = getpid();
     const char *nodename = "localhost";
     static bool have_logging = false;
 
     if (have_logging) {
         return;
     }
 
     have_logging = true;
 
     crm_xml_init(); /* Sets buffer allocation strategy */
 
     if (crm_trace_nonlog == 0) {
         crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
     }
 
     umask(S_IWGRP | S_IWOTH | S_IROTH);
 
     /* Redirect messages from glib functions to our handler */
     glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL);
 
     /* and for good measure... - this enum is a bit field (!) */
     g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */
 
     /* Set crm_system_name, which is used as the logging name. It may also
      * be used for other purposes such as an IPC client name.
      */
     set_identity(entity, argc, argv);
 
     qb_facility = qb_log_facility2int("local0");
     qb_log_init(crm_system_name, qb_facility, LOG_ERR);
     crm_log_level = LOG_CRIT;
 
     /* Nuke any syslog activity until it's asked for */
     qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
     // Shorter than default, generous for what we *should* send to syslog
     qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
 #endif
     if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') {
         nodename = res.nodename;
     }
 
     /* Set format strings and disable threading
      * Pacemaker and threads do not mix well (due to the amount of forking)
      */
     qb_log_tags_stringify_fn_set(crm_quark_to_string);
     for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
         qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE);
 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
         // End truncated lines with '...'
         qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
 #endif
         set_format_string(lpc, crm_system_name, pid, nodename);
     }
 
 #ifdef ENABLE_NLS
     /* Enable translations (experimental). Currently we only have a few
      * proof-of-concept translations for some option help. The goal would be to
      * offer translations for option help and man pages rather than logs or
      * documentation, to reduce the burden of maintaining them.
      */
 
     // Load locale information for the local host from the environment
     setlocale(LC_ALL, "");
 
     // Tell gettext where to find Pacemaker message catalogs
     CRM_ASSERT(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
 
     // Tell gettext to use the Pacemaker message catalogs
     CRM_ASSERT(textdomain(PACKAGE) != NULL);
 
     // Tell gettext that the translated strings are stored in UTF-8
     bind_textdomain_codeset(PACKAGE, "UTF-8");
 #endif
 }
 
 gboolean
 crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
              int argc, char **argv, gboolean quiet)
 {
     const char *syslog_priority = NULL;
     const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
     const char *f_copy = facility;
 
     pcmk__is_daemon = daemon;
     crm_log_preinit(entity, argc, argv);
 
     if (level > LOG_TRACE) {
         level = LOG_TRACE;
     }
     if(level > crm_log_level) {
         crm_log_level = level;
     }
 
     /* Should we log to syslog */
     if (facility == NULL) {
         if (pcmk__is_daemon) {
             facility = "daemon";
         } else {
             facility = PCMK__VALUE_NONE;
         }
         pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility);
     }
 
     if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) {
         quiet = TRUE;
 
 
     } else {
         qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
     }
 
     if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
         /* Override the default setting */
         crm_log_level = LOG_DEBUG;
     }
 
     /* What lower threshold do we have for sending to syslog */
     syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
     if (syslog_priority) {
         crm_log_priority = crm_priority2int(syslog_priority);
     }
     qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
                       crm_log_priority);
 
     // Log to syslog unless requested to be quiet
     if (!quiet) {
         qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
     }
 
     /* Should we log to stderr */ 
     if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
         /* Override the default setting */
         to_stderr = TRUE;
     }
     crm_enable_stderr(to_stderr);
 
     // Log to a file if we're a daemon or user asked for one
     {
         const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
 
         if (!pcmk__str_eq(PCMK__VALUE_NONE, logfile, pcmk__str_casei)
             && (pcmk__is_daemon || (logfile != NULL))) {
             // Daemons always get a log file, unless explicitly set to "none"
             pcmk__add_logfile(logfile);
         }
     }
 
     if (pcmk__is_daemon
         && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
         crm_enable_blackbox(0);
     }
 
     /* Summary */
     crm_trace("Quiet: %d, facility %s", quiet, f_copy);
     pcmk__env_option(PCMK__ENV_LOGFILE);
     pcmk__env_option(PCMK__ENV_LOGFACILITY);
 
     crm_update_callsites();
 
     /* Ok, now we can start logging... */
 
     // Disable daemon request if user isn't root or Pacemaker daemon user
     if (pcmk__is_daemon) {
         const char *user = getenv("USER");
 
         if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
             crm_trace("Not switching to corefile directory for %s", user);
             pcmk__is_daemon = false;
         }
     }
 
     if (pcmk__is_daemon) {
         int user = getuid();
         struct passwd *pwent = getpwuid(user);
 
         if (pwent == NULL) {
             crm_perror(LOG_ERR, "Cannot get name for uid: %d", user);
 
         } else if (!pcmk__strcase_any_of(pwent->pw_name, "root", CRM_DAEMON_USER, NULL)) {
             crm_trace("Don't change active directory for regular user: %s", pwent->pw_name);
 
         } else if (chdir(CRM_CORE_DIR) < 0) {
             crm_perror(LOG_INFO, "Cannot change active directory to " CRM_CORE_DIR);
 
         } else {
             crm_info("Changed active directory to " CRM_CORE_DIR);
         }
 
         /* Original meanings from signal(7)
          *
          * Signal       Value     Action   Comment
          * SIGTRAP        5        Core    Trace/breakpoint trap
          * SIGUSR1     30,10,16    Term    User-defined signal 1
          * SIGUSR2     31,12,17    Term    User-defined signal 2
          *
          * Our usage is as similar as possible
          */
         mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
         mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
         mainloop_add_signal(SIGTRAP, crm_trigger_blackbox);
 
     } else if (!quiet) {
         crm_log_args(argc, argv);
     }
 
     return TRUE;
 }
 
 /* returns the old value */
 unsigned int
 set_crm_log_level(unsigned int level)
 {
     unsigned int old = crm_log_level;
 
     if (level > LOG_TRACE) {
         level = LOG_TRACE;
     }
     crm_log_level = level;
     crm_update_callsites();
     crm_trace("New log level: %d", level);
     return old;
 }
 
 void
 crm_enable_stderr(int enable)
 {
     if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
         qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
         crm_update_callsites();
 
     } else if (enable == FALSE) {
         qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
     }
 }
 
 /*!
  * \brief Make logging more verbose
  *
  * If logging to stderr is not already enabled when this function is called,
  * enable it. Otherwise, increase the log level by 1.
  *
  * \param[in] argc  Ignored
  * \param[in] argv  Ignored
  */
 void
 crm_bump_log_level(int argc, char **argv)
 {
     if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
         != QB_LOG_STATE_ENABLED) {
         crm_enable_stderr(TRUE);
     } else {
         set_crm_log_level(crm_log_level + 1);
     }
 }
 
 unsigned int
 get_crm_log_level(void)
 {
     return crm_log_level;
 }
 
 /*!
  * \brief Log the command line (once)
  *
  * \param[in]  Number of values in \p argv
  * \param[in]  Command-line arguments (including command name)
  *
  * \note This function will only log once, even if called with different
  *       arguments.
  */
 void
 crm_log_args(int argc, char **argv)
 {
     static bool logged = false;
     gchar *arg_string = NULL;
 
     if ((argc == 0) || (argv == NULL) || logged) {
         return;
     }
     logged = true;
     arg_string = g_strjoinv(" ", argv);
     crm_notice("Invoked: %s", arg_string);
     g_free(arg_string);
 }
 
 void
 crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
                   const char *output)
 {
     const char *next = NULL;
     const char *offset = NULL;
 
     if (level == LOG_NEVER) {
         return;
     }
 
     if (output == NULL) {
         if (level != LOG_STDOUT) {
             level = LOG_TRACE;
         }
         output = "-- empty --";
     }
 
     next = output;
     do {
         offset = next;
         next = strchrnul(offset, '\n');
         do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix,
                          (int)(next - offset), offset);
         if (next[0] != 0) {
             next++;
         }
 
     } while (next != NULL && next[0] != 0);
 }
 
 void
 pcmk__cli_init_logging(const char *name, unsigned int verbosity)
 {
     crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
 
     for (int i = 0; i < verbosity; i++) {
         /* These arguments are ignored, so pass placeholders. */
         crm_bump_log_level(0, NULL);
     }
 }
 
 /*!
  * \brief Log XML line-by-line in a formatted fashion
  *
  * \param[in] level  Priority at which to log the messages
  * \param[in] text   Prefix for each line
  * \param[in] xml    XML to log
  *
  * \note This does nothing when \p level is \p LOG_STDOUT.
  * \note Do not call this function directly. It should be called only from the
  *       \p do_crm_log_xml() macro.
  */
 void
 pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml)
 {
     if (xml == NULL) {
         do_crm_log(level, "%s%sNo data to dump as XML",
                    pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
+
     } else {
-        pcmk__xml_show(NULL, level, text, xml, 1,
+        if (logger_out == NULL) {
+            CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
+        }
+
+        pcmk__output_set_log_level(logger_out, level);
+        pcmk__xml_show(logger_out, 0, text, xml, 1,
                        pcmk__xml_fmt_pretty
                        |pcmk__xml_fmt_open
                        |pcmk__xml_fmt_children
                        |pcmk__xml_fmt_close);
     }
 }
 
+/*!
+ * \internal
+ * \brief Free the logging library's internal log output object
+ */
+void
+pcmk__free_common_logger(void)
+{
+    if (logger_out != NULL) {
+        logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
+        pcmk__output_free(logger_out);
+        logger_out = NULL;
+    }
+}
+
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/logging_compat.h>
 
 gboolean
 crm_log_cli_init(const char *entity)
 {
     pcmk__cli_init_logging(entity, 0);
     return TRUE;
 }
 
 gboolean
 crm_add_logfile(const char *filename)
 {
     return pcmk__add_logfile(filename) == pcmk_rc_ok;
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/common/results.c b/lib/common/results.c
index ca3d7dd3f0..746433bb1a 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,1032 +1,1033 @@
 /*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <bzlib.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <qb/qbdefs.h>
 
 #include <crm/common/mainloop.h>
 #include <crm/common/xml.h>
 
 G_DEFINE_QUARK(pcmk-rc-error-quark, pcmk__rc_error)
 G_DEFINE_QUARK(pcmk-exitc-error-quark, pcmk__exitc_error)
 
 // General (all result code types)
 
 /*!
  * \brief Get the name and description of a given result code
  *
  * A result code can be interpreted as a member of any one of several families.
  *
  * \param[in]  code  The result code to look up
  * \param[in]  type  How \p code should be interpreted
  * \param[out] name  Where to store the result code's name
  * \param[out] desc  Where to store the result code's description
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name,
                         const char **desc)
 {
     const char *code_name = NULL;
     const char *code_desc = NULL;
 
     switch (type) {
         case pcmk_result_legacy:
             code_name = pcmk_errorname(code);
             code_desc = pcmk_strerror(code);
             break;
         case pcmk_result_rc:
             code_name = pcmk_rc_name(code);
             code_desc = pcmk_rc_str(code);
             break;
         case pcmk_result_exitcode:
             code_name = crm_exit_name(code);
             code_desc = crm_exit_str((crm_exit_t) code);
             break;
         default:
             return pcmk_rc_undetermined;
     }
 
     if (name != NULL) {
         *name = code_name;
     }
     
     if (desc != NULL) {
         *desc = code_desc;
     }
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Get the lower and upper bounds of a result code family
  *
  * \param[in]   type    Type of result code
  * \param[out]  lower   Where to store the lower bound
  * \param[out]  upper   Where to store the upper bound
  *
  * \return Standard Pacemaker return code
  *
  * \note There is no true upper bound on standard Pacemaker return codes or
  *       legacy return codes. All system \p errno values are valid members of
  *       these result code families, and there is no global upper limit nor a
  *       constant by which to refer to the highest \p errno value on a given
  *       system.
  */
 int
 pcmk__result_bounds(enum pcmk_result_type type, int *lower, int *upper)
 {
     CRM_ASSERT((lower != NULL) && (upper != NULL));
 
     switch (type) {
         case pcmk_result_legacy:
             *lower = pcmk_ok;
             *upper = 256;   // should be enough for almost any system error code
             break;
         case pcmk_result_rc:
             *lower = pcmk_rc_error - pcmk__n_rc + 1;
             *upper = 256;
             break;
         case pcmk_result_exitcode:
             *lower = CRM_EX_OK;
             *upper = CRM_EX_MAX;
             break;
         default:
             *lower = 0;
             *upper = -1;
             return pcmk_rc_undetermined;
     }
     return pcmk_rc_ok;
 }
 
 // @COMPAT Legacy function return codes
 
 //! \deprecated Use standard return codes and pcmk_rc_name() instead
 const char *
 pcmk_errorname(int rc)
 {
     rc = abs(rc);
     switch (rc) {
         case pcmk_err_generic: return "pcmk_err_generic";
         case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
         case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
         case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
         case pcmk_err_old_data: return "pcmk_err_old_data";
         case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
         case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
         case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
         case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
         case pcmk_err_cib_save: return "pcmk_err_cib_save";
         case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
         case pcmk_err_multiple: return "pcmk_err_multiple";
         case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
         case pcmk_err_already: return "pcmk_err_already";
         case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
         case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
         default: return pcmk_rc_name(rc); // system errno
     }
 }
 
 //! \deprecated Use standard return codes and pcmk_rc_str() instead
 const char *
 pcmk_strerror(int rc)
 {
     return pcmk_rc_str(pcmk_legacy2rc(rc));
 }
 
 // Standard Pacemaker API return codes
 
 /* This array is used only for nonzero values of pcmk_rc_e. Its values must be
  * kept in the exact reverse order of the enum value numbering (i.e. add new
  * values to the end of the array).
  */
 static const struct pcmk__rc_info {
     const char *name;
     const char *desc;
     int legacy_rc;
 } pcmk__rcs[] = {
     { "pcmk_rc_error",
       "Error",
       -pcmk_err_generic,
     },
     { "pcmk_rc_unknown_format",
       "Unknown output format",
       -pcmk_err_unknown_format,
     },
     { "pcmk_rc_bad_nvpair",
       "Bad name/value pair given",
       -pcmk_err_bad_nvpair,
     },
     { "pcmk_rc_already",
       "Already in requested state",
       -pcmk_err_already,
     },
     { "pcmk_rc_node_unknown",
       "Node not found",
       -pcmk_err_node_unknown,
     },
     { "pcmk_rc_multiple",
       "Resource active on multiple nodes",
       -pcmk_err_multiple,
     },
     { "pcmk_rc_cib_corrupt",
       "Could not parse on-disk configuration",
       -pcmk_err_cib_corrupt,
     },
     { "pcmk_rc_cib_save",
       "Could not save new configuration to disk",
       -pcmk_err_cib_save,
     },
     { "pcmk_rc_cib_backup",
       "Could not archive previous configuration",
       -pcmk_err_cib_backup,
     },
     { "pcmk_rc_cib_modified",
       "On-disk configuration was manually modified",
       -pcmk_err_cib_modified,
     },
     { "pcmk_rc_diff_resync",
       "Application of update diff failed, requesting full refresh",
       -pcmk_err_diff_resync,
     },
     { "pcmk_rc_diff_failed",
       "Application of update diff failed",
       -pcmk_err_diff_failed,
     },
     { "pcmk_rc_old_data",
       "Update was older than existing configuration",
       -pcmk_err_old_data,
     },
     { "pcmk_rc_transform_failed",
       "Schema transform failed",
       -pcmk_err_transform_failed,
     },
     { "pcmk_rc_schema_unchanged",
       "Schema is already the latest available",
       -pcmk_err_schema_unchanged,
     },
     { "pcmk_rc_schema_validation",
       "Update does not conform to the configured schema",
       -pcmk_err_schema_validation,
     },
     { "pcmk_rc_no_quorum",
       "Operation requires quorum",
       -pcmk_err_no_quorum,
     },
     { "pcmk_rc_ipc_unauthorized",
       "IPC server is blocked by unauthorized process",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_unresponsive",
       "IPC server is unresponsive",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_pid_only",
       "IPC server process is active but not accepting connections",
       -pcmk_err_generic,
     },
     { "pcmk_rc_op_unsatisfied",
       "Not applicable under current conditions",
       -pcmk_err_generic,
     },
     { "pcmk_rc_undetermined",
       "Result undetermined",
       -pcmk_err_generic,
     },
     { "pcmk_rc_before_range",
       "Result occurs before given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_within_range",
       "Result occurs within given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_after_range",
       "Result occurs after given range",
       -pcmk_err_generic,
     },
     { "pcmk_rc_no_output",
       "Output message produced no output",
       -pcmk_err_generic,
     },
     { "pcmk_rc_no_input",
       "Input file not available",
       -pcmk_err_generic,
     },
     { "pcmk_rc_underflow",
       "Value too small to be stored in data type",
       -pcmk_err_generic,
     },
     { "pcmk_rc_dot_error",
       "Error writing dot(1) file",
       -pcmk_err_generic,
     },
     { "pcmk_rc_graph_error",
       "Error writing graph file",
       -pcmk_err_generic,
     },
     { "pcmk_rc_invalid_transition",
       "Cluster simulation produced invalid transition",
       -pcmk_err_generic,
     },
     { "pcmk_rc_unpack_error",
       "Unable to parse CIB XML",
       -pcmk_err_generic,
     },
     { "pcmk_rc_duplicate_id",
       "Two or more XML elements have the same ID",
       -pcmk_err_generic,
     },
 };
 
 /*!
  * \internal
  * \brief The number of <tt>enum pcmk_rc_e</tt> values, excluding \c pcmk_rc_ok
  *
  * This constant stores the number of negative standard Pacemaker return codes.
  * These represent Pacemaker-custom error codes. The count does not include
  * positive system error numbers, nor does it include \c pcmk_rc_ok (success).
  */
 const size_t pcmk__n_rc = PCMK__NELEM(pcmk__rcs);
 
 /*!
  * \brief Get a return code constant name as a string
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String of constant name corresponding to rc
  */
 const char *
 pcmk_rc_name(int rc)
 {
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
         return pcmk__rcs[pcmk_rc_error - rc].name;
     }
     switch (rc) {
         case pcmk_rc_ok:        return "pcmk_rc_ok";
         case E2BIG:             return "E2BIG";
         case EACCES:            return "EACCES";
         case EADDRINUSE:        return "EADDRINUSE";
         case EADDRNOTAVAIL:     return "EADDRNOTAVAIL";
         case EAFNOSUPPORT:      return "EAFNOSUPPORT";
         case EAGAIN:            return "EAGAIN";
         case EALREADY:          return "EALREADY";
         case EBADF:             return "EBADF";
         case EBADMSG:           return "EBADMSG";
         case EBUSY:             return "EBUSY";
         case ECANCELED:         return "ECANCELED";
         case ECHILD:            return "ECHILD";
         case ECOMM:             return "ECOMM";
         case ECONNABORTED:      return "ECONNABORTED";
         case ECONNREFUSED:      return "ECONNREFUSED";
         case ECONNRESET:        return "ECONNRESET";
         /* case EDEADLK:        return "EDEADLK"; */
         case EDESTADDRREQ:      return "EDESTADDRREQ";
         case EDOM:              return "EDOM";
         case EDQUOT:            return "EDQUOT";
         case EEXIST:            return "EEXIST";
         case EFAULT:            return "EFAULT";
         case EFBIG:             return "EFBIG";
         case EHOSTDOWN:         return "EHOSTDOWN";
         case EHOSTUNREACH:      return "EHOSTUNREACH";
         case EIDRM:             return "EIDRM";
         case EILSEQ:            return "EILSEQ";
         case EINPROGRESS:       return "EINPROGRESS";
         case EINTR:             return "EINTR";
         case EINVAL:            return "EINVAL";
         case EIO:               return "EIO";
         case EISCONN:           return "EISCONN";
         case EISDIR:            return "EISDIR";
         case ELIBACC:           return "ELIBACC";
         case ELOOP:             return "ELOOP";
         case EMFILE:            return "EMFILE";
         case EMLINK:            return "EMLINK";
         case EMSGSIZE:          return "EMSGSIZE";
 #ifdef EMULTIHOP // Not available on OpenBSD
         case EMULTIHOP:         return "EMULTIHOP";
 #endif
         case ENAMETOOLONG:      return "ENAMETOOLONG";
         case ENETDOWN:          return "ENETDOWN";
         case ENETRESET:         return "ENETRESET";
         case ENETUNREACH:       return "ENETUNREACH";
         case ENFILE:            return "ENFILE";
         case ENOBUFS:           return "ENOBUFS";
         case ENODATA:           return "ENODATA";
         case ENODEV:            return "ENODEV";
         case ENOENT:            return "ENOENT";
         case ENOEXEC:           return "ENOEXEC";
         case ENOKEY:            return "ENOKEY";
         case ENOLCK:            return "ENOLCK";
 #ifdef ENOLINK // Not available on OpenBSD
         case ENOLINK:           return "ENOLINK";
 #endif
         case ENOMEM:            return "ENOMEM";
         case ENOMSG:            return "ENOMSG";
         case ENOPROTOOPT:       return "ENOPROTOOPT";
         case ENOSPC:            return "ENOSPC";
 #ifdef ENOSR
         case ENOSR:             return "ENOSR";
 #endif
 #ifdef ENOSTR
         case ENOSTR:            return "ENOSTR";
 #endif
         case ENOSYS:            return "ENOSYS";
         case ENOTBLK:           return "ENOTBLK";
         case ENOTCONN:          return "ENOTCONN";
         case ENOTDIR:           return "ENOTDIR";
         case ENOTEMPTY:         return "ENOTEMPTY";
         case ENOTSOCK:          return "ENOTSOCK";
 #if ENOTSUP != EOPNOTSUPP
         case ENOTSUP:           return "ENOTSUP";
 #endif
         case ENOTTY:            return "ENOTTY";
         case ENOTUNIQ:          return "ENOTUNIQ";
         case ENXIO:             return "ENXIO";
         case EOPNOTSUPP:        return "EOPNOTSUPP";
         case EOVERFLOW:         return "EOVERFLOW";
         case EPERM:             return "EPERM";
         case EPFNOSUPPORT:      return "EPFNOSUPPORT";
         case EPIPE:             return "EPIPE";
         case EPROTO:            return "EPROTO";
         case EPROTONOSUPPORT:   return "EPROTONOSUPPORT";
         case EPROTOTYPE:        return "EPROTOTYPE";
         case ERANGE:            return "ERANGE";
         case EREMOTE:           return "EREMOTE";
         case EREMOTEIO:         return "EREMOTEIO";
         case EROFS:             return "EROFS";
         case ESHUTDOWN:         return "ESHUTDOWN";
         case ESPIPE:            return "ESPIPE";
         case ESOCKTNOSUPPORT:   return "ESOCKTNOSUPPORT";
         case ESRCH:             return "ESRCH";
         case ESTALE:            return "ESTALE";
         case ETIME:             return "ETIME";
         case ETIMEDOUT:         return "ETIMEDOUT";
         case ETXTBSY:           return "ETXTBSY";
 #ifdef EUNATCH
         case EUNATCH:           return "EUNATCH";
 #endif
         case EUSERS:            return "EUSERS";
         /* case EWOULDBLOCK:    return "EWOULDBLOCK"; */
         case EXDEV:             return "EXDEV";
 
 #ifdef EBADE // Not available on OS X
         case EBADE:             return "EBADE";
         case EBADFD:            return "EBADFD";
         case EBADSLT:           return "EBADSLT";
         case EDEADLOCK:         return "EDEADLOCK";
         case EBADR:             return "EBADR";
         case EBADRQC:           return "EBADRQC";
         case ECHRNG:            return "ECHRNG";
 #ifdef EISNAM // Not available on OS X, Illumos, Solaris
         case EISNAM:            return "EISNAM";
         case EKEYEXPIRED:       return "EKEYEXPIRED";
         case EKEYREVOKED:       return "EKEYREVOKED";
 #endif
         case EKEYREJECTED:      return "EKEYREJECTED";
         case EL2HLT:            return "EL2HLT";
         case EL2NSYNC:          return "EL2NSYNC";
         case EL3HLT:            return "EL3HLT";
         case EL3RST:            return "EL3RST";
         case ELIBBAD:           return "ELIBBAD";
         case ELIBMAX:           return "ELIBMAX";
         case ELIBSCN:           return "ELIBSCN";
         case ELIBEXEC:          return "ELIBEXEC";
 #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
         case ENOMEDIUM:         return "ENOMEDIUM";
         case EMEDIUMTYPE:       return "EMEDIUMTYPE";
 #endif
         case ENONET:            return "ENONET";
         case ENOPKG:            return "ENOPKG";
         case EREMCHG:           return "EREMCHG";
         case ERESTART:          return "ERESTART";
         case ESTRPIPE:          return "ESTRPIPE";
 #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
         case EUCLEAN:           return "EUCLEAN";
 #endif
         case EXFULL:            return "EXFULL";
 #endif // EBADE
         default:                return "Unknown";
     }
 }
 
 /*!
  * \brief Get a user-friendly description of a return code
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String description of rc
  */
 const char *
 pcmk_rc_str(int rc)
 {
     if (rc == pcmk_rc_ok) {
         return "OK";
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
         return pcmk__rcs[pcmk_rc_error - rc].desc;
     }
     if (rc < 0) {
         return "Error";
     }
 
     // Handle values that could be defined by system or by portability.h
     switch (rc) {
 #ifdef PCMK__ENOTUNIQ
         case ENOTUNIQ:      return "Name not unique on network";
 #endif
 #ifdef PCMK__ECOMM
         case ECOMM:         return "Communication error on send";
 #endif
 #ifdef PCMK__ELIBACC
         case ELIBACC:       return "Can not access a needed shared library";
 #endif
 #ifdef PCMK__EREMOTEIO
         case EREMOTEIO:     return "Remote I/O error";
 #endif
 #ifdef PCMK__ENOKEY
         case ENOKEY:        return "Required key not available";
 #endif
 #ifdef PCMK__ENODATA
         case ENODATA:       return "No data available";
 #endif
 #ifdef PCMK__ETIME
         case ETIME:         return "Timer expired";
 #endif
 #ifdef PCMK__EKEYREJECTED
         case EKEYREJECTED:  return "Key was rejected by service";
 #endif
         default:            return strerror(rc);
     }
 }
 
 // This returns negative values for errors
 //! \deprecated Use standard return codes instead
 int
 pcmk_rc2legacy(int rc)
 {
     if (rc >= 0) {
         return -rc; // OK or system errno
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
         return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
     }
     return -pcmk_err_generic;
 }
 
 //! \deprecated Use standard return codes instead
 int
 pcmk_legacy2rc(int legacy_rc)
 {
     legacy_rc = abs(legacy_rc);
     switch (legacy_rc) {
         case pcmk_err_no_quorum:            return pcmk_rc_no_quorum;
         case pcmk_err_schema_validation:    return pcmk_rc_schema_validation;
         case pcmk_err_schema_unchanged:     return pcmk_rc_schema_unchanged;
         case pcmk_err_transform_failed:     return pcmk_rc_transform_failed;
         case pcmk_err_old_data:             return pcmk_rc_old_data;
         case pcmk_err_diff_failed:          return pcmk_rc_diff_failed;
         case pcmk_err_diff_resync:          return pcmk_rc_diff_resync;
         case pcmk_err_cib_modified:         return pcmk_rc_cib_modified;
         case pcmk_err_cib_backup:           return pcmk_rc_cib_backup;
         case pcmk_err_cib_save:             return pcmk_rc_cib_save;
         case pcmk_err_cib_corrupt:          return pcmk_rc_cib_corrupt;
         case pcmk_err_multiple:             return pcmk_rc_multiple;
         case pcmk_err_node_unknown:         return pcmk_rc_node_unknown;
         case pcmk_err_already:              return pcmk_rc_already;
         case pcmk_err_bad_nvpair:           return pcmk_rc_bad_nvpair;
         case pcmk_err_unknown_format:       return pcmk_rc_unknown_format;
         case pcmk_err_generic:              return pcmk_rc_error;
         case pcmk_ok:                       return pcmk_rc_ok;
         default:                            return legacy_rc; // system errno
     }
 }
 
 // Exit status codes
 
 const char *
 crm_exit_name(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "CRM_EX_OK";
         case CRM_EX_ERROR: return "CRM_EX_ERROR";
         case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
         case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
         case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
         case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
         case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
         case CRM_EX_PROMOTED: return "CRM_EX_PROMOTED";
         case CRM_EX_FAILED_PROMOTED: return "CRM_EX_FAILED_PROMOTED";
         case CRM_EX_USAGE: return "CRM_EX_USAGE";
         case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
         case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
         case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
         case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
         case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
         case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
         case CRM_EX_OSERR: return "CRM_EX_OSERR";
         case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
         case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
         case CRM_EX_IOERR: return "CRM_EX_IOERR";
         case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
         case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
         case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
         case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
         case CRM_EX_FATAL: return "CRM_EX_FATAL";
         case CRM_EX_PANIC: return "CRM_EX_PANIC";
         case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
         case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
         case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
         case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
         case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
         case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
         case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
         case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
         case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
         case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
         case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
         case CRM_EX_OLD: return "CRM_EX_OLD";
         case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
         case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED";
         case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED";
         case CRM_EX_NONE: return "CRM_EX_NONE";
         case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
     }
     return "CRM_EX_UNKNOWN";
 }
 
 const char *
 crm_exit_str(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "OK";
         case CRM_EX_ERROR: return "Error occurred";
         case CRM_EX_INVALID_PARAM: return "Invalid parameter";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
         case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
         case CRM_EX_NOT_INSTALLED: return "Not installed";
         case CRM_EX_NOT_CONFIGURED: return "Not configured";
         case CRM_EX_NOT_RUNNING: return "Not running";
         case CRM_EX_PROMOTED: return "Promoted";
         case CRM_EX_FAILED_PROMOTED: return "Failed in promoted role";
         case CRM_EX_USAGE: return "Incorrect usage";
         case CRM_EX_DATAERR: return "Invalid data given";
         case CRM_EX_NOINPUT: return "Input file not available";
         case CRM_EX_NOUSER: return "User does not exist";
         case CRM_EX_NOHOST: return "Host does not exist";
         case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
         case CRM_EX_SOFTWARE: return "Internal software bug";
         case CRM_EX_OSERR: return "Operating system error occurred";
         case CRM_EX_OSFILE: return "System file not available";
         case CRM_EX_CANTCREAT: return "Cannot create output file";
         case CRM_EX_IOERR: return "I/O error occurred";
         case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
         case CRM_EX_PROTOCOL: return "Protocol violated";
         case CRM_EX_NOPERM: return "Insufficient privileges";
         case CRM_EX_CONFIG: return "Invalid configuration";
         case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
         case CRM_EX_PANIC: return "System panic required";
         case CRM_EX_DISCONNECT: return "Not connected";
         case CRM_EX_DIGEST: return "Digest mismatch";
         case CRM_EX_NOSUCH: return "No such object";
         case CRM_EX_QUORUM: return "Quorum required";
         case CRM_EX_UNSAFE: return "Operation not safe";
         case CRM_EX_EXISTS: return "Requested item already exists";
         case CRM_EX_MULTIPLE: return "Multiple items match request";
         case CRM_EX_EXPIRED: return "Requested item has expired";
         case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
         case CRM_EX_INDETERMINATE: return "Could not determine status";
         case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
         case CRM_EX_OLD: return "Update was older than existing configuration";
         case CRM_EX_TIMEOUT: return "Timeout occurred";
         case CRM_EX_DEGRADED: return "Service is active but might fail soon";
         case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon";
         case CRM_EX_NONE: return "No exit status available";
         case CRM_EX_MAX: return "Error occurred";
     }
     if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
         return "Interrupted by signal";
     }
     return "Unknown exit status";
 }
 
 /*!
  * \brief Map a function return code to the most similar exit code
  *
  * \param[in] rc  Function return code
  *
  * \return Most similar exit code
  */
 crm_exit_t
 pcmk_rc2exitc(int rc)
 {
     switch (rc) {
         case pcmk_rc_ok:
             return CRM_EX_OK;
 
         case pcmk_rc_no_quorum:
             return CRM_EX_QUORUM;
 
         case pcmk_rc_old_data:
             return CRM_EX_OLD;
 
         case pcmk_rc_schema_validation:
         case pcmk_rc_transform_failed:
         case pcmk_rc_unpack_error:
             return CRM_EX_CONFIG;
 
         case pcmk_rc_bad_nvpair:
             return CRM_EX_INVALID_PARAM;
 
         case EACCES:
             return CRM_EX_INSUFFICIENT_PRIV;
 
         case EBADF:
         case EINVAL:
         case EFAULT:
         case ENOSYS:
         case EOVERFLOW:
         case pcmk_rc_underflow:
             return CRM_EX_SOFTWARE;
 
         case EBADMSG:
         case EMSGSIZE:
         case ENOMSG:
         case ENOPROTOOPT:
         case EPROTO:
         case EPROTONOSUPPORT:
         case EPROTOTYPE:
             return CRM_EX_PROTOCOL;
 
         case ECOMM:
         case ENOMEM:
             return CRM_EX_OSERR;
 
         case ECONNABORTED:
         case ECONNREFUSED:
         case ECONNRESET:
         case ENOTCONN:
             return CRM_EX_DISCONNECT;
 
         case EEXIST:
         case pcmk_rc_already:
             return CRM_EX_EXISTS;
 
         case EIO:
         case pcmk_rc_no_output:
         case pcmk_rc_dot_error:
         case pcmk_rc_graph_error:
             return CRM_EX_IOERR;
 
         case ENOTSUP:
 #if EOPNOTSUPP != ENOTSUP
         case EOPNOTSUPP:
 #endif
             return CRM_EX_UNIMPLEMENT_FEATURE;
 
         case ENOTUNIQ:
         case pcmk_rc_multiple:
             return CRM_EX_MULTIPLE;
 
         case ENODEV:
         case ENOENT:
         case ENXIO:
         case pcmk_rc_unknown_format:
             return CRM_EX_NOSUCH;
 
         case pcmk_rc_node_unknown:
             return CRM_EX_NOHOST;
 
         case ETIME:
         case ETIMEDOUT:
             return CRM_EX_TIMEOUT;
 
         case EAGAIN:
         case EBUSY:
             return CRM_EX_UNSATISFIED;
 
         case pcmk_rc_before_range:
             return CRM_EX_NOT_YET_IN_EFFECT;
 
         case pcmk_rc_after_range:
             return CRM_EX_EXPIRED;
 
         case pcmk_rc_undetermined:
             return CRM_EX_INDETERMINATE;
 
         case pcmk_rc_op_unsatisfied:
             return CRM_EX_UNSATISFIED;
 
         case pcmk_rc_within_range:
             return CRM_EX_OK;
 
         case pcmk_rc_no_input:
             return CRM_EX_NOINPUT;
 
         case pcmk_rc_duplicate_id:
             return CRM_EX_MULTIPLE;
 
         default:
             return CRM_EX_ERROR;
     }
 }
 
 /*!
  * \brief Map a function return code to the most similar OCF exit code
  *
  * \param[in] rc  Function return code
  *
  * \return Most similar OCF exit code
  */
 enum ocf_exitcode
 pcmk_rc2ocf(int rc)
 {
     switch (rc) {
         case pcmk_rc_ok:
             return PCMK_OCF_OK;
 
         case pcmk_rc_bad_nvpair:
             return PCMK_OCF_INVALID_PARAM;
 
         case EACCES:
             return PCMK_OCF_INSUFFICIENT_PRIV;
 
         case ENOTSUP:
 #if EOPNOTSUPP != ENOTSUP
         case EOPNOTSUPP:
 #endif
             return PCMK_OCF_UNIMPLEMENT_FEATURE;
 
         default:
             return PCMK_OCF_UNKNOWN_ERROR;
     }
 }
 
 
 // Other functions
 
 const char *
 bz2_strerror(int rc)
 {
     // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
     switch (rc) {
         case BZ_OK:
         case BZ_RUN_OK:
         case BZ_FLUSH_OK:
         case BZ_FINISH_OK:
         case BZ_STREAM_END:
             return "Ok";
         case BZ_CONFIG_ERROR:
             return "libbz2 has been improperly compiled on your platform";
         case BZ_SEQUENCE_ERROR:
             return "library functions called in the wrong order";
         case BZ_PARAM_ERROR:
             return "parameter is out of range or otherwise incorrect";
         case BZ_MEM_ERROR:
             return "memory allocation failed";
         case BZ_DATA_ERROR:
             return "data integrity error is detected during decompression";
         case BZ_DATA_ERROR_MAGIC:
             return "the compressed stream does not start with the correct magic bytes";
         case BZ_IO_ERROR:
             return "error reading or writing in the compressed file";
         case BZ_UNEXPECTED_EOF:
             return "compressed file finishes before the logical end of stream is detected";
         case BZ_OUTBUFF_FULL:
             return "output data will not fit into the buffer provided";
     }
     return "Data compression error";
 }
 
 crm_exit_t
 crm_exit(crm_exit_t rc)
 {
     /* A compiler could theoretically use any type for crm_exit_t, but an int
      * should always hold it, so cast to int to keep static analysis happy.
      */
     if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
         rc = CRM_EX_ERROR;
     }
 
     mainloop_cleanup();
     crm_xml_cleanup();
 
     free(pcmk__our_nodename);
 
     if (crm_system_name) {
         crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
         free(crm_system_name);
     } else {
         crm_trace("Exiting with status %d", rc);
     }
+    pcmk__free_common_logger();
     qb_log_fini(); // Don't log anything after this point
 
     exit(rc);
 }
 
 /*
  * External action results
  */
 
 /*!
  * \internal
  * \brief Set the result of an action
  *
  * \param[out] result        Where to set action result
  * \param[in]  exit_status   OCF exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  exit_reason   Human-friendly description of event to set
  */
 void
 pcmk__set_result(pcmk__action_result_t *result, int exit_status,
                  enum pcmk_exec_status exec_status, const char *exit_reason)
 {
     if (result == NULL) {
         return;
     }
 
     result->exit_status = exit_status;
     result->execution_status = exec_status;
 
     if (!pcmk__str_eq(result->exit_reason, exit_reason, pcmk__str_none)) {
         free(result->exit_reason);
         result->exit_reason = (exit_reason == NULL)? NULL : strdup(exit_reason);
     }
 }
 
 
 /*!
  * \internal
  * \brief Set the result of an action, with a formatted exit reason
  *
  * \param[out] result        Where to set action result
  * \param[in]  exit_status   OCF exit status to set
  * \param[in]  exec_status   Execution status to set
  * \param[in]  format        printf-style format for a human-friendly
  *                           description of reason for result
  * \param[in]  ...           arguments for \p format
  */
 G_GNUC_PRINTF(4, 5)
 void
 pcmk__format_result(pcmk__action_result_t *result, int exit_status,
                     enum pcmk_exec_status exec_status,
                     const char *format, ...)
 {
     va_list ap;
     int len = 0;
     char *reason = NULL;
 
     if (result == NULL) {
         return;
     }
 
     result->exit_status = exit_status;
     result->execution_status = exec_status;
 
     if (format != NULL) {
         va_start(ap, format);
         len = vasprintf(&reason, format, ap);
         CRM_ASSERT(len > 0);
         va_end(ap);
     }
     free(result->exit_reason);
     result->exit_reason = reason;
 }
 
 /*!
  * \internal
  * \brief Set the output of an action
  *
  * \param[out] result         Action result to set output for
  * \param[in]  out            Action output to set (must be dynamically
  *                            allocated)
  * \param[in]  err            Action error output to set (must be dynamically
  *                            allocated)
  *
  * \note \p result will take ownership of \p out and \p err, so the caller
  *       should not free them.
  */
 void
 pcmk__set_result_output(pcmk__action_result_t *result, char *out, char *err)
 {
     if (result == NULL) {
         return;
     }
 
     free(result->action_stdout);
     result->action_stdout = out;
 
     free(result->action_stderr);
     result->action_stderr = err;
 }
 
 /*!
  * \internal
  * \brief Clear a result's exit reason, output, and error output
  *
  * \param[in,out] result  Result to reset
  */
 void
 pcmk__reset_result(pcmk__action_result_t *result)
 {
     if (result == NULL) {
         return;
     }
 
     free(result->exit_reason);
     result->exit_reason = NULL;
 
     free(result->action_stdout);
     result->action_stdout = NULL;
 
     free(result->action_stderr);
     result->action_stderr = NULL;
 }
 
 /*!
  * \internal
  * \brief Copy the result of an action
  *
  * \param[in]  src  Result to copy
  * \param[out] dst  Where to copy \p src to
  */
 void
 pcmk__copy_result(const pcmk__action_result_t *src, pcmk__action_result_t *dst)
 {
     CRM_CHECK((src != NULL) && (dst != NULL), return);
     dst->exit_status = src->exit_status;
     dst->execution_status = src->execution_status;
     pcmk__str_update(&dst->exit_reason, src->exit_reason);
     pcmk__str_update(&dst->action_stdout, src->action_stdout);
     pcmk__str_update(&dst->action_stderr, src->action_stderr);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/results_compat.h>
 
 crm_exit_t
 crm_errno2exit(int rc)
 {
     return pcmk_rc2exitc(pcmk_legacy2rc(rc));
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API