diff --git a/include/crm/common/logging.h b/include/crm/common/logging.h
index 5580edc97d..9a0e38aa11 100644
--- a/include/crm/common/logging.h
+++ b/include/crm/common/logging.h
@@ -1,386 +1,393 @@
 /*
  * Copyright 2004-2021 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
 
 /**
  * \file
  * \brief Wrappers for and extensions to libqb logging
  * \ingroup core
  */
 
 #ifndef CRM_LOGGING__H
 #  define CRM_LOGGING__H
 
 #  include <stdio.h>
 #  include <glib.h>
 #  include <qb/qblog.h>
 #  include <libxml/tree.h>
 
 /* Define custom log priorities.
  *
  * syslog(3) uses int for priorities, but libqb's struct qb_log_callsite uses
  * uint8_t, so make sure they fit in the latter.
  */
 
 // Define something even less desired than debug
 #  ifndef LOG_TRACE
 #    define LOG_TRACE   (LOG_DEBUG+1)
 #  endif
 
 // Print message to stdout instead of logging it
 #  ifndef LOG_STDOUT
 #    define LOG_STDOUT  254
 #  endif
 
 // Don't send message anywhere
 #  ifndef LOG_NEVER
 #    define LOG_NEVER   255
 #  endif
 
 /* "Extended information" logging support */
 #ifdef QB_XS
 #  define CRM_XS QB_XS
 #  define crm_extended_logging(t, e) qb_log_ctl((t), QB_LOG_CONF_EXTENDED, (e))
 #else
 #  define CRM_XS "|"
 
 /* A caller might want to check the return value, so we can't define this as a
  * no-op, and we can't simply define it to be 0 because gcc will then complain
  * when the value isn't checked.
  */
 static inline int
 crm_extended_logging(int t, int e)
 {
     return 0;
 }
 #endif
 
 extern unsigned int crm_log_level;
 extern unsigned int crm_trace_nonlog;
 
 /*! \deprecated Pacemaker library functions set this when a configuration
  *              error is found, which turns on extra messages at the end of
  *              processing. It should not be used directly and will be removed
  *              from the public C API in a future release.
  */
 extern gboolean crm_config_error;
 
 /*! \deprecated Pacemaker library functions set this when a configuration
  *              warning is found, which turns on extra messages at the end of
  *              processing. It should not be used directly and will be removed
  *              from the public C API in a future release.
  */
 extern gboolean crm_config_warning;
 
 enum xml_log_options
 {
     xml_log_option_filtered   = 0x0001,
     xml_log_option_formatted  = 0x0002,
     xml_log_option_text       = 0x0004, /* add this option to dump text into xml */
     xml_log_option_full_fledged = 0x0008, // Use libxml when converting XML to text
     xml_log_option_diff_plus  = 0x0010,
     xml_log_option_diff_minus = 0x0020,
     xml_log_option_diff_short = 0x0040,
     xml_log_option_diff_all   = 0x0100,
     xml_log_option_dirty_add  = 0x1000,
     xml_log_option_open       = 0x2000,
     xml_log_option_children   = 0x4000,
     xml_log_option_close      = 0x8000,
 };
 
 void crm_enable_blackbox(int nsig);
 void crm_disable_blackbox(int nsig);
 void crm_write_blackbox(int nsig, struct qb_log_callsite *callsite);
 
 void crm_update_callsites(void);
 
 void crm_log_deinit(void);
 
+/*!
+ * \brief Initializes the logging system and defaults to the least verbose output level
+ *
+ * \param[in] entity  If not NULL, will be used as the identity for logging purposes
+ * \param[in] argc    The number of command line parameters
+ * \param[in] argv    The command line parameter values
+ */
 void crm_log_preinit(const char *entity, int argc, char **argv);
 gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon,
                       gboolean to_stderr, int argc, char **argv, gboolean quiet);
 
 void crm_log_args(int argc, char **argv);
 void crm_log_output_fn(const char *file, const char *function, int line, int level,
                        const char *prefix, const char *output);
 
 // Log a block of text line by line
 #define crm_log_output(level, prefix, output)   \
     crm_log_output_fn(__FILE__, __func__, __LINE__, level, prefix, output)
 
 void crm_bump_log_level(int argc, char **argv);
 
 void crm_enable_stderr(int enable);
 
 gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags);
 
 void log_data_element(int log_level, const char *file, const char *function, int line,
                       const char *prefix, xmlNode * data, int depth, gboolean formatted);
 
 /* returns the old value */
 unsigned int set_crm_log_level(unsigned int level);
 
 unsigned int get_crm_log_level(void);
 
 /*
  * Throughout the macros below, note the leading, pre-comma, space in the
  * various ' , ##args' occurrences to aid portability across versions of 'gcc'.
  * https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html#Variadic-Macros
  */
 #if defined(__clang__)
 #    define CRM_TRACE_INIT_DATA(name)
 #  else
 #    include <assert.h> // required by QB_LOG_INIT_DATA() macro
 #    define CRM_TRACE_INIT_DATA(name) QB_LOG_INIT_DATA(name)
 #endif
 
 /* Using "switch" instead of "if" in these macro definitions keeps
  * static analysis from complaining about constant evaluations
  */
 
 /*!
  * \brief Log a message
  *
  * \param[in] level  Priority at which to log the message
  * \param[in] fmt    printf-style format string literal for message
  * \param[in] args   Any arguments needed by format string
  *
  * \note This is a macro, and \p level may be evaluated more than once.
  */
 #  define do_crm_log(level, fmt, args...) do {                              \
         switch (level) {                                                    \
             case LOG_STDOUT:                                                \
                 printf(fmt "\n" , ##args);                                  \
                 break;                                                      \
             case LOG_NEVER:                                                 \
                 break;                                                      \
             default:                                                        \
                 qb_log_from_external_source(__func__, __FILE__, fmt,        \
                     (level),   __LINE__, 0 , ##args);                       \
                 break;                                                      \
         }                                                                   \
     } while (0)
 
 /*!
  * \brief Log a message that is likely to be filtered out
  *
  * \param[in] level  Priority at which to log the message
  * \param[in] fmt    printf-style format string for message
  * \param[in] args   Any arguments needed by format string
  *
  * \note This is a macro, and \p level may be evaluated more than once.
  *       This does nothing when level is LOG_STDOUT.
  */
 #  define do_crm_log_unlikely(level, fmt, args...) do {                     \
         switch (level) {                                                    \
             case LOG_STDOUT: case LOG_NEVER:                                \
                 break;                                                      \
             default: {                                                      \
                 static struct qb_log_callsite *trace_cs = NULL;             \
                 if (trace_cs == NULL) {                                     \
                     trace_cs = qb_log_callsite_get(__func__, __FILE__, fmt, \
                                                    (level), __LINE__, 0);   \
                 }                                                           \
                 if (crm_is_callsite_active(trace_cs, (level), 0)) {         \
                     qb_log_from_external_source(__func__, __FILE__, fmt,    \
                         (level), __LINE__, 0 , ##args);                     \
                 }                                                           \
             }                                                               \
             break;                                                          \
         }                                                                   \
     } while (0)
 
 #  define CRM_LOG_ASSERT(expr) do {					\
         if (!(expr)) {                                                  \
             static struct qb_log_callsite *core_cs = NULL;              \
             if(core_cs == NULL) {                                       \
                 core_cs = qb_log_callsite_get(__func__, __FILE__,       \
                                               "log-assert", LOG_TRACE,  \
                                               __LINE__, 0);             \
             }                                                           \
             crm_abort(__FILE__, __func__, __LINE__, #expr,              \
                       core_cs?core_cs->targets:FALSE, TRUE);            \
         }                                                               \
     } while(0)
 
 /* 'failure_action' MUST NOT be 'continue' as it will apply to the
  * macro's do-while loop
  */
 #  define CRM_CHECK(expr, failure_action) do {				            \
         if (!(expr)) {                                                  \
             static struct qb_log_callsite *core_cs = NULL;              \
             if (core_cs == NULL) {                                      \
                 core_cs = qb_log_callsite_get(__func__, __FILE__,       \
                                               "check-assert",           \
                                               LOG_TRACE, __LINE__, 0);  \
             }                                                           \
 	        crm_abort(__FILE__, __func__, __LINE__, #expr,	            \
 		        (core_cs? core_cs->targets: FALSE), TRUE);              \
 	        failure_action;						                        \
 	    }								                                \
     } while(0)
 
 /*!
  * \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 is a macro, and \p level may be evaluated more than once.
  *       This does nothing when level is LOG_STDOUT.
  */
 #  define do_crm_log_xml(level, text, xml) do {                             \
         switch (level) {                                                    \
             case LOG_STDOUT: case LOG_NEVER:                                \
                 break;                                                      \
             default: {                                                      \
                 static struct qb_log_callsite *xml_cs = NULL;               \
                 if (xml_cs == NULL) {                                       \
                     xml_cs = qb_log_callsite_get(__func__, __FILE__,        \
                                         "xml-blob", (level), __LINE__, 0);  \
                 }                                                           \
                 if (crm_is_callsite_active(xml_cs, (level), 0)) {           \
                     log_data_element((level), __FILE__, __func__,           \
                          __LINE__, text, xml, 1, xml_log_option_formatted); \
                 }                                                           \
             }                                                               \
             break;                                                          \
         }                                                                   \
     } while(0)
 
 /*!
  * \brief Log a message as if it came from a different code location
  *
  * \param[in] level     Priority at which to log the message
  * \param[in] file      Source file name to use instead of __FILE__
  * \param[in] function  Source function name to use instead of __func__
  * \param[in] line      Source line number to use instead of __line__
  * \param[in] fmt       printf-style format string literal for message
  * \param[in] args      Any arguments needed by format string
  *
  * \note This is a macro, and \p level may be evaluated more than once.
  */
 #  define do_crm_log_alias(level, file, function, line, fmt, args...) do {  \
         switch (level) {                                                    \
             case LOG_STDOUT:                                                \
                 printf(fmt "\n" , ##args);                                  \
                 break;                                                      \
             case LOG_NEVER:                                                 \
                 break;                                                      \
             default:                                                        \
                 qb_log_from_external_source(function, file, fmt, (level),   \
                                             line, 0 , ##args);              \
                 break;                                                      \
         }                                                                   \
     } while (0)
 
 /*!
  * \brief Send a system error message to both the log and stderr
  *
  * \param[in] level  Priority at which to log the message
  * \param[in] fmt    printf-style format string for message
  * \param[in] args   Any arguments needed by format string
  *
  * \deprecated One of the other logging functions should be used with
  *             pcmk_strerror() instead.
  * \note This is a macro, and \p level may be evaluated more than once.
  * \note Because crm_perror() adds the system error message and error number
  *       onto the end of fmt, that information will become extended information
  *       if CRM_XS is used inside fmt and will not show up in syslog.
  */
 #  define crm_perror(level, fmt, args...) do {                              \
         switch (level) {                                                    \
             case LOG_NEVER:                                                 \
                 break;                                                      \
             default: {                                                      \
                 const char *err = strerror(errno);                          \
                 /* cast to int makes coverity happy when level == 0 */      \
                 if ((level) <= (int) crm_log_level) {                       \
                     fprintf(stderr, fmt ": %s (%d)\n" , ##args, err, errno);\
                 }                                                           \
                 do_crm_log((level), fmt ": %s (%d)" , ##args, err, errno);  \
             }                                                               \
             break;                                                          \
         }                                                                   \
     } while (0)
 
 /*!
  * \brief Log a message with a tag (for use with PCMK_trace_tags)
  *
  * \param[in] level  Priority at which to log the message
  * \param[in] tag    String to tag message with
  * \param[in] fmt    printf-style format string for message
  * \param[in] args   Any arguments needed by format string
  *
  * \note This is a macro, and \p level may be evaluated more than once.
  *       This does nothing when level is LOG_STDOUT.
  */
 #  define crm_log_tag(level, tag, fmt, args...)    do {                     \
         switch (level) {                                                    \
             case LOG_STDOUT: case LOG_NEVER:                                \
                 break;                                                      \
             default: {                                                      \
                 static struct qb_log_callsite *trace_tag_cs = NULL;         \
                 int converted_tag = g_quark_try_string(tag);                \
                 if (trace_tag_cs == NULL) {                                 \
                     trace_tag_cs = qb_log_callsite_get(__func__, __FILE__,  \
                                     fmt, (level), __LINE__, converted_tag); \
                 }                                                           \
                 if (crm_is_callsite_active(trace_tag_cs, (level),           \
                                            converted_tag)) {                \
                     qb_log_from_external_source(__func__, __FILE__, fmt,    \
                                 (level), __LINE__, converted_tag , ##args); \
                 }                                                           \
             }                                                               \
         }                                                                   \
     } while (0)
 
 #  define crm_emerg(fmt, args...)   qb_log(LOG_EMERG,       fmt , ##args)
 #  define crm_crit(fmt, args...)    qb_logt(LOG_CRIT,    0, fmt , ##args)
 #  define crm_err(fmt, args...)     qb_logt(LOG_ERR,     0, fmt , ##args)
 #  define crm_warn(fmt, args...)    qb_logt(LOG_WARNING, 0, fmt , ##args)
 #  define crm_notice(fmt, args...)  qb_logt(LOG_NOTICE,  0, fmt , ##args)
 #  define crm_info(fmt, args...)    qb_logt(LOG_INFO,    0, fmt , ##args)
 
 #  define crm_debug(fmt, args...)   do_crm_log_unlikely(LOG_DEBUG, fmt , ##args)
 #  define crm_trace(fmt, args...)   do_crm_log_unlikely(LOG_TRACE, fmt , ##args)
 
 #  define crm_log_xml_crit(xml, text)    do_crm_log_xml(LOG_CRIT,    text, xml)
 #  define crm_log_xml_err(xml, text)     do_crm_log_xml(LOG_ERR,     text, xml)
 #  define crm_log_xml_warn(xml, text)    do_crm_log_xml(LOG_WARNING, text, xml)
 #  define crm_log_xml_notice(xml, text)  do_crm_log_xml(LOG_NOTICE,  text, xml)
 #  define crm_log_xml_info(xml, text)    do_crm_log_xml(LOG_INFO,    text, xml)
 #  define crm_log_xml_debug(xml, text)   do_crm_log_xml(LOG_DEBUG,   text, xml)
 #  define crm_log_xml_trace(xml, text)   do_crm_log_xml(LOG_TRACE,   text, xml)
 
 #  define crm_log_xml_explicit(xml, text)  do {                 \
         static struct qb_log_callsite *digest_cs = NULL;        \
         digest_cs = qb_log_callsite_get(                        \
             __func__, __FILE__, text, LOG_TRACE, __LINE__,      \
             crm_trace_nonlog);                                  \
         if (digest_cs && digest_cs->targets) {                  \
             do_crm_log_xml(LOG_TRACE,   text, xml);             \
         }                                                       \
     } while(0)
 
 #  define crm_str(x)    (const char*)(x?x:"<null>")
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/common/logging_compat.h>
 #endif
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index 8617d04df3..4fab7da159 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,303 +1,308 @@
 /*
  * Copyright 2004-2021 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 CRM_COMMON_XML__H
 #  define CRM_COMMON_XML__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Wrappers for and extensions to libxml2
  * \ingroup core
  */
 
 #  include <stdio.h>
 #  include <sys/types.h>
 #  include <unistd.h>
 
 #  include <stdlib.h>
 #  include <errno.h>
 #  include <fcntl.h>
 
 #  include <libxml/tree.h>
 #  include <libxml/xpath.h>
 
 #  include <crm/crm.h>
 #  include <crm/common/nvpair.h>
 
 /* Define compression parameters for IPC messages
  *
  * Compression costs a LOT, so we don't want to do it unless we're hitting
  * message limits. Currently, we use 128KB as the threshold, because higher
  * values don't play well with the heartbeat stack. With an earlier limit of
  * 10KB, compressing 184 of 1071 messages accounted for 23% of the total CPU
  * used by the cib.
  */
 #  define CRM_BZ2_BLOCKS		4
 #  define CRM_BZ2_WORK		20
 #  define CRM_BZ2_THRESHOLD	128 * 1024
 
 #  define XML_PARANOIA_CHECKS 0
 
 typedef const xmlChar *pcmkXmlStr;
 
 gboolean add_message_xml(xmlNode * msg, const char *field, xmlNode * xml);
 xmlNode *get_message_xml(xmlNode * msg, const char *field);
 
 xmlDoc *getDocPtr(xmlNode * node);
 
 /*
  * Replacement function for xmlCopyPropList which at the very least,
  * doesn't work the way *I* would expect it to.
  *
  * Copy all the attributes/properties from src into target.
  *
  * Not recursive, does not return anything.
  *
  */
 void copy_in_properties(xmlNode * target, xmlNode * src);
 void expand_plus_plus(xmlNode * target, const char *name, const char *value);
 void fix_plus_plus_recursive(xmlNode * target);
 
 /*
  * Create a node named "name" as a child of "parent"
  * If parent is NULL, creates an unconnected node.
  *
  * Returns the created node
  *
  */
 xmlNode *create_xml_node(xmlNode * parent, const char *name);
 
 /*
  * Create a node named "name" as a child of "parent", giving it the provided
  * text content.
  * If parent is NULL, creates an unconnected node.
  *
  * Returns the created node
  *
  */
 xmlNode *pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content);
 
 /*
  * Create a new HTML node named "element_name" as a child of "parent", giving it the
  * provided text content.  Optionally, apply a CSS #id and #class.
  *
  * Returns the created node.
  */
 xmlNode *pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
                                const char *class_name, const char *text);
 
 /*
  *
  */
 void purge_diff_markers(xmlNode * a_node);
 
 /*
  * Returns a deep copy of src_node
  *
  */
 xmlNode *copy_xml(xmlNode * src_node);
 
 /*
  * Add a copy of xml_node to new_parent
  */
 xmlNode *add_node_copy(xmlNode * new_parent, xmlNode * xml_node);
 
 int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child);
 
 /*
  * XML I/O Functions
  *
  * Whitespace between tags is discarded.
  */
 xmlNode *filename2xml(const char *filename);
 
 xmlNode *stdin2xml(void);
 
 xmlNode *string2xml(const char *input);
 
 int write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress);
 int write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress);
 
 char *dump_xml_formatted(xmlNode * msg);
 /* Also dump the text node with xml_log_option_text enabled */ 
 char *dump_xml_formatted_with_text(xmlNode * msg);
 
 char *dump_xml_unformatted(xmlNode * msg);
 
 /*
  * Diff related Functions
  */
 xmlNode *diff_xml_object(xmlNode * left, xmlNode * right, gboolean suppress);
 
 xmlNode *subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
                              gboolean full, gboolean * changed, const char *marker);
 
 gboolean can_prune_leaf(xmlNode * xml_node);
 
 /*
  * Searching & Modifying
  */
 xmlNode *find_xml_node(xmlNode * cib, const char *node_path, gboolean must_find);
 
 void xml_remove_prop(xmlNode * obj, const char *name);
 
 gboolean replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update,
                            gboolean delete_only);
 
 gboolean update_xml_child(xmlNode * child, xmlNode * to_update);
 
 int find_xml_children(xmlNode ** children, xmlNode * root,
                       const char *tag, const char *field, const char *value,
                       gboolean search_matches);
 
 xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
 xmlNode *get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level);
 
 static inline const char *
 crm_element_name(const xmlNode *xml)
 {
     return xml? (const char *)(xml->name) : NULL;
 }
 
 static inline const char *
 crm_map_element_name(const xmlNode *xml)
 {
     const char *name = crm_element_name(xml);
 
     if (strcmp(name, "master") == 0) {
         return "clone";
     } else {
         return name;
     }
 }
 
 gboolean xml_has_children(const xmlNode * root);
 
 char *calculate_on_disk_digest(xmlNode * local_cib);
 char *calculate_operation_digest(xmlNode * local_cib, const char *version);
 char *calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
                                      const char *version);
 
 /* schema-related functions (from schemas.c) */
 gboolean validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs);
 gboolean validate_xml_verbose(xmlNode * xml_blob);
 
 /*!
  * \brief Update CIB XML to most recent schema version
  *
  * "Update" means either actively employ XSLT-based transformation(s)
  * (if intermediate product to transform valid per its declared schema version,
  * transformation available, proceeded successfully with a result valid per
  * expectated newer schema version), or just try to bump the marked validating
  * schema until all gradually rising schema versions attested or the first
  * such attempt subsequently fails to validate.   Which of the two styles will
  * be used depends on \p transform parameter (positive/negative, respectively).
  *
  * \param[in,out] xml_blob   XML tree representing CIB, may be swapped with
  *                           an "updated" one
  * \param[out]    best       The highest configuration version (per its index
  *                           in the global schemas table) it was possible to
  *                           reach during the update steps while ensuring
  *                           the validity of the result; if no validation
  *                           success was observed against possibly multiple
  *                           schemas, the value is less or equal the result
  *                           of \c get_schema_version applied on the input
  *                           \p xml_blob value (unless that function maps it
  *                           to -1, then 0 would be used instead)
  * \param[in]     max        When \p transform is positive, this allows to
  *                           set upper boundary schema (per its index in the
  *                           global schemas table) beyond which it's forbidden
  *                           to update by the means of XSLT transformation
  * \param[in]     transform  Whether to employ XSLT-based transformation so
  *                           as to allow overcoming possible incompatibilities
  *                           between major schema versions (see above)
  * \param[in]     to_logs    If true, output notable progress info to
  *                           internal log streams; if false, to stderr
  *
  * \return \c pcmk_ok if no non-recoverable error encountered (up to
  *         caller to evaluate if the update satisfies the requirements
  *         per returned \p best value), negative value carrying the reason
  *         otherwise
  */
 int update_validation(xmlNode **xml_blob, int *best, int max,
                       gboolean transform, gboolean to_logs);
 
 int get_schema_version(const char *name);
 const char *get_schema_name(int version);
 const char *xml_latest_schema(void);
 gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
 
+/*!
+ * \brief Initialize the CRM XML subsystem
+ *
+ * This method sets global XML settings and loads pacemaker schemas into the cache.
+ */
 void crm_xml_init(void);
 void crm_xml_cleanup(void);
 
 void pcmk_free_xml_subtree(xmlNode *xml);
 void free_xml(xmlNode * child);
 
 xmlNode *first_named_child(const xmlNode *parent, const char *name);
 xmlNode *crm_next_same_xml(const xmlNode *sibling);
 
 xmlNode *sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive);
 xmlXPathObjectPtr xpath_search(xmlNode * xml_top, const char *path);
 void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
                               void (*helper)(xmlNode*, void*), void *user_data);
 xmlNode *expand_idref(xmlNode * input, xmlNode * top);
 
 void freeXpathObject(xmlXPathObjectPtr xpathObj);
 xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
 void dedupXpathResults(xmlXPathObjectPtr xpathObj);
 
 static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
 {
     if(xpathObj == NULL || xpathObj->nodesetval == NULL) {
         return 0;
     }
     return xpathObj->nodesetval->nodeNr;
 }
 
 bool xml_tracking_changes(xmlNode * xml);
 bool xml_document_dirty(xmlNode *xml);
 void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
 void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
 void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
 void xml_accept_changes(xmlNode * xml);
 void xml_log_changes(uint8_t level, const char *function, xmlNode *xml);
 void xml_log_patchset(uint8_t level, const char *function, xmlNode *xml);
 bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3]);
 
 xmlNode *xml_create_patchset(
     int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
 int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
 
 void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
 
 void save_xml_to_file(xmlNode * xml, const char *desc, const char *filename);
 char *xml_get_path(xmlNode *xml);
 
 char * crm_xml_escape(const char *text);
 void crm_xml_sanitize_id(char *id);
 void crm_xml_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
 /*!
  * \brief xmlNode destructor which can be used in glib collections
  */
 void crm_destroy_xml(gpointer data);
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/common/xml_compat.h>
 #endif
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/common/logging.c b/lib/common/logging.c
index 4ceb48b5eb..d70637c59e 100644
--- a/lib/common/logging.c
+++ b/lib/common/logging.c
@@ -1,1082 +1,1102 @@
 /*
  * Copyright 2004-2021 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;
 
 static unsigned int crm_log_priority = LOG_NOTICE;
 static GLogFunc glib_log_default = 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
+ */
 static void
 set_format_string(int method, const char *daemon)
 {
     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) {
             struct utsname res;
             const char *nodename = "localhost";
 
             if (uname(&res) == 0) {
                 nodename = res.nodename;
             }
 
             // If logging to file, prefix with timestamp, node name, daemon ID
             offset += snprintf(fmt + offset, FMT_MAX - offset,
                                TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ",
                                nodename, daemon, (unsigned long) getpid());
         }
 
         // 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, "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, 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;
 }
 
 
+/*!
+ * \brief Set PCMK_service environment variable
+ *
+ * The environemnt variable is set in one of two ways:
+ * - It is passed to the function via the "entity" parameter
+ * - It is derived from the executable name
+ *
+ * If it is already set, then it is not reset.
+ *
+ * \param[in] entity  If not NULL, will be assigned to the environment variable
+ * \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 **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 **argv)
 {
     /* Configure libqb logging with nothing turned on */
 
     int lpc = 0;
     int32_t qb_facility = 0;
 
     static bool have_logging = FALSE;
 
     if(have_logging == FALSE) {
         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
 
         /* 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);
         }
     }
 }
 
 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 = "none";
         }
         pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility);
     }
 
     if (pcmk__str_eq(facility, "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("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();
         const char *base = CRM_CORE_DIR;
         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(base) < 0) {
             crm_perror(LOG_INFO, "Cannot change active directory to %s", base);
 
         } else {
             crm_info("Changed active directory to %s", base);
 #if 0
             {
                 char path[512];
 
                 snprintf(path, 512, "%s-%lu", crm_system_name, (unsigned long) getpid());
                 mkdir(path, 0750);
                 chdir(path);
                 crm_info("Changed active directory to %s/%s/%s", base, pwent->pw_name, path);
             }
 #endif
         }
 
         /* 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);
     }
 }
 
 // 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