diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h
index 810483bbfd..fc64a70c1d 100644
--- a/include/crm/common/output_internal.h
+++ b/include/crm/common/output_internal.h
@@ -1,936 +1,936 @@
 /*
  * Copyright 2019-2022 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 PCMK__OUTPUT_INTERNAL__H
 #  define PCMK__OUTPUT_INTERNAL__H
 
 #  include <stdbool.h>
 #  include <stdio.h>
 #  include <libxml/tree.h>
 #  include <libxml/HTMLtree.h>
 
 #  include <glib.h>
 #  include <crm/common/results.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Formatted output for pacemaker tools
  */
 
 
-#  define PCMK__API_VERSION "2.25"
+#  define PCMK__API_VERSION "2.26"
 
 #if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS)
 #  define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS)))
 #else
 #  define PCMK__OUTPUT_ARGS(ARGS...)
 #endif
 
 typedef struct pcmk__output_s pcmk__output_t;
 
 /*!
  * \internal
  * \brief The type of a function that creates a ::pcmk__output_t.
  *
  * Instances of this type are passed to pcmk__register_format(), stored in an
  * internal data structure, and later accessed by pcmk__output_new().  For 
  * examples, see pcmk__mk_xml_output() and pcmk__mk_text_output().
  *
  * \param[in] argv The list of command line arguments.
  */
 typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv);
 
 /*!
  * \internal
  * \brief The type of a custom message formatting function.
  *
  * These functions are defined by various libraries to support formatting of
  * types aside from the basic types provided by a ::pcmk__output_t.
  *
  * The meaning of the return value will be different for each message.
  * In general, however, 0 should be returned on success and a positive value
  * on error.
  *
  * \param[in,out] out   Output object to use to display message
  * \param[in,out] args  Message-specific arguments needed
  *
  * \note These functions must not call va_start or va_end - that is done
  *       automatically before the custom formatting function is called.
  */
 typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args);
 
 /*!
  * \internal
  * \brief Internal type for tracking custom messages.
  *
  * Each library can register functions that format custom message types.  These
  * are commonly used to handle some library-specific type.  Registration is
  * done by first defining a table of ::pcmk__message_entry_t structures and
  * then passing that table to pcmk__register_messages().  Separate handlers
  * can be defined for the same message, but for different formats (xml vs.
  * text).  Unknown formats will be ignored.
  *
  * Additionally, a "default" value for fmt_table can be used.  In this case,
  * fn will be registered for all supported formats.  It is also possible to
  * register a default and then override that registration with a format-specific
  * function if necessary.
  *
  * \note The ::pcmk__message_entry_t table is processed in one pass, in order,
  * from top to bottom.  This means later entries with the same message_id will
  * override previous ones.  Thus, any default entry must come before any
  * format-specific entries for the same message_id.
  */
 typedef struct pcmk__message_entry_s {
     /*!
      * \brief The message to be handled.
      *
      * This must be the same ID that is passed to the message function of
      * a ::pcmk__output_t.  Unknown message IDs will be ignored.
      */
     const char *message_id;
 
     /*!
      * \brief The format type this handler is for.
      *
      * This name must match the fmt_name of the currently active formatter in
      * order for the registered function to be called.  It is valid to have
      * multiple entries for the same message_id but with different fmt_name
      * values.
      */
     const char *fmt_name;
 
     /*!
      * \brief The function to be called for message_id given a match on
      *        fmt_name.  See comments on ::pcmk__message_fn_t.
      */
     pcmk__message_fn_t fn;
 } pcmk__message_entry_t;
 
 /*!
  * \internal
  * \brief This structure contains everything needed to add support for a
  *        single output formatter to a command line program.
  */
 typedef struct pcmk__supported_format_s {
     /*!
      * \brief The name of this output formatter, which should match the
      *        fmt_name parameter in some ::pcmk__output_t structure.
      */
     const char *name;
 
     /*!
      * \brief A function that creates a ::pcmk__output_t.
      */
     pcmk__output_factory_t create;
 
     /*!
      * \brief Format-specific command line options.  This can be NULL if
      *        no command line options should be supported.
      */
     GOptionEntry *options;
 } pcmk__supported_format_t;
 
 /* The following three blocks need to be updated each time a new base formatter
  * is added.
  */
 
 extern GOptionEntry pcmk__html_output_entries[];
 extern GOptionEntry pcmk__log_output_entries[];
 extern GOptionEntry pcmk__none_output_entries[];
 extern GOptionEntry pcmk__text_output_entries[];
 extern GOptionEntry pcmk__xml_output_entries[];
 
 pcmk__output_t *pcmk__mk_html_output(char **argv);
 pcmk__output_t *pcmk__mk_log_output(char **argv);
 pcmk__output_t *pcmk__mk_none_output(char **argv);
 pcmk__output_t *pcmk__mk_text_output(char **argv);
 pcmk__output_t *pcmk__mk_xml_output(char **argv);
 
 #define PCMK__SUPPORTED_FORMAT_HTML { "html", pcmk__mk_html_output, pcmk__html_output_entries }
 #define PCMK__SUPPORTED_FORMAT_LOG  { "log", pcmk__mk_log_output, pcmk__log_output_entries }
 #define PCMK__SUPPORTED_FORMAT_NONE { PCMK__VALUE_NONE, pcmk__mk_none_output,   \
                                       pcmk__none_output_entries }
 #define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries }
 #define PCMK__SUPPORTED_FORMAT_XML  { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries }
 
 /*!
  * \brief This structure contains everything that makes up a single output
  *        formatter.
  *
  * Instances of this structure may be created by calling pcmk__output_new()
  * with the name of the desired formatter.  They should later be freed with
  * pcmk__output_free().
  */
 struct pcmk__output_s {
     /*!
      * \brief The name of this output formatter.
      */
     const char *fmt_name;
 
     /*!
      * \brief Should this formatter supress most output?
      *
      * \note This setting is not respected by all formatters.  In general,
      *       machine-readable output formats will not support this while
      *       user-oriented formats will.  Callers should use is_quiet()
      *       to test whether to print or not.
      */
     bool quiet;
 
     /*!
      * \brief A copy of the request that generated this output.
      *
      * In the case of command line usage, this would be the command line
      * arguments.  For other use cases, it could be different.
      */
     gchar *request;
 
     /*!
      * \brief Where output should be written.
      *
      * This could be a file handle, or stdout or stderr.  This is really only
      * useful internally.
      */
     FILE *dest;
 
     /*!
      * \brief Custom messages that are currently registered on this formatter.
      *
      * Keys are the string message IDs, values are ::pcmk__message_fn_t function
      * pointers.
      */
     GHashTable *messages;
 
     /*!
      * \brief Implementation-specific private data.
      *
      * Each individual formatter may have some private data useful in its
      * implementation.  This points to that data.  Callers should not rely on
      * its contents or structure.
      */
     void *priv;
 
     /*!
      * \internal
      * \brief Take whatever actions are necessary to prepare out for use.  This is
      *        called by pcmk__output_new().  End users should not need to call this.
      *
      * \note For formatted output implementers - This function should be written in
      *       such a way that it can be called repeatedly on an already initialized
      *       object without causing problems, or on a previously finished object
      *       without crashing.
      *
      * \param[in,out] out The output functions structure.
      *
      * \return true on success, false on error.
      */
     bool (*init) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Free the private formatter-specific data.
      *
      * This is called from pcmk__output_free() and does not typically need to be
      * called directly.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*free_priv) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Take whatever actions are necessary to end formatted output.
      *
      * This could include flushing output to a file, but does not include freeing
      * anything.  The finish method can potentially be fairly complicated, adding
      * additional information to the internal data structures or doing whatever
      * else.  It is therefore suggested that finish only be called once.
      *
      * \note The print parameter will only affect those formatters that do all
      *       their output at the end.  Console-oriented formatters typically print
      *       a line at a time as they go, so this parameter will not affect them.
      *       Structured formatters will honor it, however.
      *
      * \note The copy_dest parameter does not apply to all formatters.  Console-
      *       oriented formatters do not build up a structure as they go, and thus
      *       do not have anything to return.  Structured formatters will honor it,
      *       however.  Note that each type of formatter will return a different
      *       type of value in this parameter.  To use this parameter, call this
      *       function like so:
      *
      * \code
      * xmlNode *dest = NULL;
      * out->finish(out, exit_code, false, (void **) &dest);
      * \endcode
      *
      * \param[in,out] out         The output functions structure.
      * \param[in]     exit_status The exit value of the whole program.
      * \param[in]     print       Whether this function should write any output.
      * \param[out]    copy_dest   A destination to store a copy of the internal
      *                            data structure for this output, or NULL if no
      *                            copy is required.  The caller should free this
      *                            memory when done with it.
      */
     void (*finish) (pcmk__output_t *out, crm_exit_t exit_status, bool print,
                     void **copy_dest);
 
     /*!
      * \internal
      * \brief Finalize output and then immediately set back up to start a new set
      *        of output.
      *
      * This is conceptually the same as calling finish and then init, though in
      * practice more be happening behind the scenes.
      *
      * \note This function differs from finish in that no exit_status is added.
      *       The idea is that the program is not shutting down, so there is not
      *       yet a final exit code.  Call finish on the last time through if this
      *       is needed.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*reset) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Register a custom message.
      *
      * \param[in,out] out        The output functions structure.
      * \param[in]     message_id The name of the message to register.  This name
      *                           will be used as the message_id parameter to the
      *                           message function in order to call the custom
      *                           format function.
      * \param[in]     fn         The custom format function to call for message_id.
      */
     void (*register_message) (pcmk__output_t *out, const char *message_id,
                               pcmk__message_fn_t fn);
 
     /*!
      * \internal
      * \brief Call a previously registered custom message.
      *
      * \param[in,out] out        The output functions structure.
      * \param[in]     message_id The name of the message to call.  This name must
      *                           be the same as the message_id parameter of some
      *                           previous call to register_message.
      * \param[in] ...            Arguments to be passed to the registered function.
      *
      * \return A standard Pacemaker return code.  Generally: 0 if a function was
      *         registered for the message, that function was called, and returned
      *         successfully; EINVAL if no function was registered; or pcmk_rc_no_output
      *         if a function was called but produced no output.
      */
     int (*message) (pcmk__output_t *out, const char *message_id, ...);
 
     /*!
      * \internal
      * \brief Format the output of a completed subprocess.
      *
      * \param[in,out] out         The output functions structure.
      * \param[in]     exit_status The exit value of the subprocess.
      * \param[in]     proc_stdout stdout from the completed subprocess.
      * \param[in]     proc_stderr stderr from the completed subprocess.
      */
     void (*subprocess_output) (pcmk__output_t *out, int exit_status,
                                const char *proc_stdout, const char *proc_stderr);
 
     /*!
      * \internal
      * \brief Format version information.  This is useful for the --version
      *        argument of command line tools.
      *
      * \param[in,out] out      The output functions structure.
      * \param[in]     extended Add additional version information.
      */
     void (*version) (pcmk__output_t *out, bool extended);
 
     /*!
      * \internal
      * \brief Format an informational message that should be shown to
      *        to an interactive user.  Not all formatters will do this.
      *
      * \note A newline will automatically be added to the end of the format
      *       string, so callers should not include a newline.
      *
      * \note It is possible for a formatter that supports this method to
      *       still not print anything out if is_quiet returns true.
      *
      * \param[in,out] out The output functions structure.
      * \param[in]     buf The message to be printed.
      * \param[in]     ... Arguments to be formatted.
      *
      * \return A standard Pacemaker return code.  Generally: pcmk_rc_ok
      *         if output was produced and pcmk_rc_no_output if it was not.
      *         As not all formatters implement this function, those that
      *         do not will always just return pcmk_rc_no_output.
      */
     int (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
     /*!
      * \internal
      * \brief Like \p info() but for messages that should appear only
      *        transiently. Not all formatters will do this.
      *
      * The originally envisioned use case is for console output, where a
      * transient status-related message may be quickly overwritten by a refresh.
      *
      * \param[in,out] out     The output functions structure.
      * \param[in]     format  The format string of the message to be printed.
      * \param[in]     ...     Arguments to be formatted.
      *
      * \return A standard Pacemaker return code. Generally: \p pcmk_rc_ok if
      *         output was produced and \p pcmk_rc_no_output if it was not. As
      *         not all formatters implement this function, those that do not
      *         will always just return \p pcmk_rc_no_output.
      */
     int (*transient) (pcmk__output_t *out, const char *format, ...)
         G_GNUC_PRINTF(2, 3);
 
     /*!
      * \internal
      * \brief Format an error message that should be shown to an interactive
      *        user.  Not all formatters will do this.
      *
      * \note A newline will automatically be added to the end of the format
      *       string, so callers should not include a newline.
      *
      * \note Formatters that support this method should always generate output,
      *       even if is_quiet returns true.
      *
      * \param[in,out] out The output functions structure.
      * \param[in]     buf The message to be printed.
      * \param[in]     ... Arguments to be formatted.
      */
     void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
     /*!
      * \internal
      * \brief Format already formatted XML.
      *
      * \param[in,out] out  The output functions structure.
      * \param[in]     name A name to associate with the XML.
      * \param[in]     buf  The XML in a string.
      */
     void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf);
 
     /*!
      * \internal
      * \brief Start a new list of items.
      *
      * \note For text output, this corresponds to another level of indentation.  For
      *       XML output, this corresponds to wrapping any following output in another
      *       layer of tags.
      *
      * \note If singular_noun and plural_noun are non-NULL, calling end_list will
      *       result in a summary being added.
      *
      * \param[in,out] out           The output functions structure.
      * \param[in]     singular_noun When outputting the summary for a list with
      *                              one item, the noun to use.
      * \param[in]     plural_noun   When outputting the summary for a list with
      *                              more than one item, the noun to use.
      * \param[in]     format        The format string.
      * \param[in]     ...           Arguments to be formatted.
      */
     void (*begin_list) (pcmk__output_t *out, const char *singular_noun,
                         const char *plural_noun, const char *format, ...)
                         G_GNUC_PRINTF(4, 5);
 
     /*!
      * \internal
      * \brief Format a single item in a list.
      *
      * \param[in,out] out     The output functions structure.
      * \param[in]     name    A name to associate with this item.
      * \param[in]     format  The format string.
      * \param[in]     ...     Arguments to be formatted.
      */
     void (*list_item) (pcmk__output_t *out, const char *name, const char *format, ...)
                       G_GNUC_PRINTF(3, 4);
 
     /*!
      * \internal
      * \brief Increment the internal counter of the current list's length.
      *
      * Typically, this counter is maintained behind the scenes as a side effect
      * of calling list_item().  However, custom functions that maintain lists
      * some other way will need to manage this counter manually.  This is
      * useful for implementing custom message functions and should not be
      * needed otherwise.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*increment_list) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Conclude a list.
      *
      * \note If begin_list was called with non-NULL for both the singular_noun
      *       and plural_noun arguments, this function will output a summary.
      *       Otherwise, no summary will be added.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*end_list) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Should anything be printed to the user?
      *
      * \note This takes into account both the \p quiet value as well as the
      *       current formatter.
      *
      * \param[in,out] out The output functions structure.
      *
      * \return true if output should be supressed, false otherwise.
      */
     bool (*is_quiet) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Output a spacer.  Not all formatters will do this.
      *
      * \param[in,out] out The output functions structure.
      */
     void (*spacer) (pcmk__output_t *out);
 
     /*!
      * \internal
      * \brief Output a progress indicator.  This is likely only useful for
      *        plain text, console based formatters.
      *
      * \param[in,out] out  The output functions structure
      * \param[in]     end  If true, output a newline afterwards (this should
      *                     only be used the last time this function is called)
      *
      */
     void (*progress) (pcmk__output_t *out, bool end);
 
     /*!
      * \internal
      * \brief Prompt the user for input.  Not all formatters will do this.
      *
      * \note This function is part of pcmk__output_t, but unlike all other
      *       function it does not take that as an argument.  In general, a
      *       prompt will go directly to the screen and therefore bypass any
      *       need to use the formatted output code to decide where and how
      *       to display.
      *
      * \param[in]  prompt The prompt to display.  This is required.
      * \param[in]  echo   If true, echo the user's input to the screen.  Set
      *                    to false for password entry.
      * \param[out] dest   Where to store the user's response.  This is
      *                    required.
      */
     void (*prompt) (const char *prompt, bool echo, char **dest);
 };
 
 /*!
  * \internal
  * \brief Call a formatting function for a previously registered message.
  *
  * \note This function is for implementing custom formatters.  It should not
  *       be called directly.  Instead, call out->message.
  *
  * \param[in,out] out        The output functions structure.
  * \param[in]     message_id The message to be handled.  Unknown messages
  *                           will be ignored.
  * \param[in]     ...        Arguments to be passed to the registered function.
  */
 int
 pcmk__call_message(pcmk__output_t *out, const char *message_id, ...);
 
 /*!
  * \internal
  * \brief Free a ::pcmk__output_t structure that was previously created by
  *        pcmk__output_new().
  *
  * \note While the create and finish functions are designed in such a way that
  *       they can be called repeatedly, this function will completely free the
  *       memory of the object.  Once this function has been called, producing
  *       more output requires starting over from pcmk__output_new().
  *
  * \param[in,out] out         The output structure.
  */
 void pcmk__output_free(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Create a new ::pcmk__output_t structure.
  *
  * \param[in,out] out      The destination of the new ::pcmk__output_t.
  * \param[in]     fmt_name How should output be formatted?
  * \param[in]     filename Where should formatted output be written to?  This
  *                         can be a filename (which will be overwritten if it
  *                         already exists), or NULL or "-" for stdout.  For no
  *                         output, pass a filename of "/dev/null".
  * \param[in]     argv     The list of command line arguments.
  *
  * \return Standard Pacemaker return code
  */
 int pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
                      const char *filename, char **argv);
 
 /*!
  * \internal
  * \brief Register a new output formatter, making it available for use
  *        the same as a base formatter.
  *
  * \param[in,out] group   A ::GOptionGroup that formatted output related command
  *                        line arguments should be added to.  This can be NULL
  *                        for use outside of command line programs.
  * \param[in]     name    The name of the format.  This will be used to select a
  *                        format from command line options and for displaying help.
  * \param[in]     create  A function that creates a ::pcmk__output_t.
  * \param[in]     options Format-specific command line options.  These will be
  *                        added to the context.  This argument can also be NULL.
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__register_format(GOptionGroup *group, const char *name,
                       pcmk__output_factory_t create,
                       const GOptionEntry *options);
 
 /*!
  * \internal
  * \brief Register an entire table of output formatters at once.
  *
  * \param[in,out] group A ::GOptionGroup that formatted output related command
  *                      line arguments should be added to.  This can be NULL
  *                      for use outside of command line programs.
  * \param[in]     table An array of ::pcmk__supported_format_t which should
  *                      all be registered.  This array must be NULL-terminated.
  *
  */
 void
 pcmk__register_formats(GOptionGroup *group,
                        const pcmk__supported_format_t *table);
 
 /*!
  * \internal
  * \brief Unregister a previously registered table of custom formatting
  *        functions and destroy the internal data structures associated with them.
  */
 void
 pcmk__unregister_formats(void);
 
 /*!
  * \internal
  * \brief Register a function to handle a custom message.
  *
  * \note This function is for implementing custom formatters.  It should not
  *       be called directly.  Instead, call out->register_message.
  *
  * \param[in,out] out        The output functions structure.
  * \param[in]     message_id The message to be handled.
  * \param[in]     fn         The custom format function to call for message_id.
  */
 void
 pcmk__register_message(pcmk__output_t *out, const char *message_id,
                        pcmk__message_fn_t fn);
 
 /*!
  * \internal
  * \brief Register an entire table of custom formatting functions at once.
  *
  * This table can contain multiple formatting functions for the same message ID
  * if they are for different format types.
  *
  * \param[in,out] out   The output functions structure.
  * \param[in]     table An array of ::pcmk__message_entry_t values which should
  *                      all be registered.  This array must be NULL-terminated.
  */
 void
 pcmk__register_messages(pcmk__output_t *out,
                         const pcmk__message_entry_t *table);
 
 /* Functions that are useful for implementing custom message formatters */
 
 /*!
  * \internal
  * \brief A printf-like function.
  *
  * This function writes to out->dest and indents the text to the current level
  * of the text formatter's nesting.  This function should be used when implementing
  * custom message functions for the text output format.  It should not be used
  * for any other purpose.
  *
  * Typically, this function should be used instead of printf.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     ...    Arguments to be passed to the format string.
  */
 void
 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
 /*!
  * \internal
  * \brief A vprintf-like function.
  *
  * This function is like pcmk__indented_printf(), except it takes a va_list instead
  * of a list of arguments.  This function should be used when implementing custom
  * functions for the text output format.  It should not be used for any other purpose.
  *
  * Typically, this function should be used instead of vprintf.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     args   A list of arguments to apply to the format string.
  */
 void
 pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 
 
 /*!
  * \internal
  * \brief A printf-like function.
  *
  * This function writes to out->dest without indenting the text.  This function
  * should be used when implementing custom message functions for the text output
  * format.  It should not be used for any other purpose.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     ...    Arguments to be passed to the format string.
  */
 void
 pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 
 /*!
  * \internal
  * \brief A vprintf-like function.
  *
  * This function is like pcmk__formatted_printf(), except it takes a va_list instead
  * of a list of arguments.  This function should be used when implementing custom
  * message functions for the text output format.  It should not be used for any
  * other purpose.
  *
  * \param[in,out] out    The output functions structure.
  * \param[in]     format The format string.
  * \param[in]     args   A list of arguments to apply to the format string.
  */
 void
 pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 
 /*!
  * \internal
  * \brief Prompt the user for input.
  *
  * \param[in]  prompt The prompt to display
  * \param[in]  echo   If true, echo the user's input to the screen.  Set
  *                    to false for password entry.
  * \param[out] dest   Where to store the user's response.
  */
 void
 pcmk__text_prompt(const char *prompt, bool echo, char **dest);
 
 /*!
  * \internal
  * \brief Set the log level used by the formatted output logger.
  *
  * \param[in,out] out       The output functions structure.
  * \param[in]     log_level The log level constant (LOG_INFO, LOG_ERR, etc.)
  *                          to use.
  *
  * \note By default, LOG_INFO is used.
  * \note Almost all formatted output messages will respect this setting.
  *       However, out->err will always log at LOG_ERR.
  */
 void
 pcmk__output_set_log_level(pcmk__output_t *out, int log_level);
 
 /*!
  * \internal
  * \brief Create and return a new XML node with the given name, as a child of the
  *        current list parent.  The new node is then added as the new list parent,
  *        meaning all subsequent nodes will be its children.  This is used when
  *        implementing custom functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     name The name of the node to be created.
  * \param[in]     ...     Name/value pairs to set as XML properties.
  */
 xmlNodePtr
 pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Add the given node as a child of the current list parent.  This is
  *        used when implementing custom message functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     node An XML node to be added as a child.
  */
 void
 pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node);
 
 /*!
  * \internal
  * \brief Create and return a new XML node with the given name, as a child of the
  *        current list parent.  This is used when implementing custom functions.
  *
  * \param[in,out] out  The output functions structure.
  * \param[in]     name The name of the node to be created.
  * \param[in]     ...     Name/value pairs to set as XML properties.
  */
 xmlNodePtr
 pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Like pcmk__output_create_xml_node(), but add the given text content to the
  *        new node.
  *
  * \param[in,out] out     The output functions structure.
  * \param[in]     name    The name of the node to be created.
  * \param[in]     content The text content of the node.
  */
 xmlNodePtr
 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content);
 
 /*!
  * \internal
  * \brief Push a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * The XML output formatter maintains an internal stack to keep track of which nodes
  * are parents in order to build up the tree structure.  This function can be used
  * to temporarily push a new node onto the stack.  After calling this function, any
  * other formatting functions will have their nodes added as children of this new
  * parent.
  *
  * \param[in,out] out     The output functions structure
  * \param[in]     parent  XML node to add
  */
 void
 pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent);
 
 /*!
  * \internal
  * \brief Pop a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * This function removes a parent node from the stack.  See pcmk__xml_push_parent()
  * for more details.
  *
  * \note Little checking is done with this function.  Be sure you only pop parents
  * that were previously pushed.  In general, it is best to keep the code between
  * push and pop simple.
  *
  * \param[in,out] out The output functions structure.
  */
 void
 pcmk__output_xml_pop_parent(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Peek a parent XML node onto the stack.  This is used when implementing
  *        custom message functions.
  *
  * This function peeks a parent node on stack.  See pcmk__xml_push_parent()
  * for more details. It has no side-effect and can be called for an empty stack.
  *
  * \note Little checking is done with this function.
  *
  * \param[in,out] out The output functions structure.
  *
  * \return NULL if stack is empty, otherwise the parent of the stack.
  */
 xmlNodePtr
 pcmk__output_xml_peek_parent(pcmk__output_t *out);
 
 /*!
  * \internal
  * \brief Create a new XML node consisting of the provided text inside an HTML
  *        element node of the given name.
  *
  * \param[in,out] out          The output functions structure.
  * \param[in]     element_name The name of the new HTML element.
  * \param[in]     id           The CSS ID selector to apply to this element.
  *                             If NULL, no ID is added.
  * \param[in]     class_name   The CSS class selector to apply to this element.
  *                             If NULL, no class is added.
  * \param[in]     text         The text content of the node.
  */
 xmlNodePtr
 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
                               const char *class_name, const char *text);
 
 /*!
  * \internal
  * \brief Add an HTML tag to the <head> section.
  *
  * The arguments after name are a NULL-terminated list of keys and values,
  * all of which will be added as attributes to the given tag.  For instance,
  * the following code would generate the tag "<meta http-equiv='refresh' content='19'>":
  *
  * \code
  * pcmk__html_add_header("meta", "http-equiv", "refresh", "content", "19", NULL);
  * \endcode
  *
  * \param[in]     name   The HTML tag for the new node.
  * \param[in]     ...    A NULL-terminated key/value list of attributes.
  */
 void
 pcmk__html_add_header(const char *name, ...)
 G_GNUC_NULL_TERMINATED;
 
 /*!
  * \internal
  * \brief Handle end-of-program error reporting
  *
  * \param[in,out] error A GError object potentially containing some error.
  *                      If NULL, do nothing.
  * \param[in,out] out   The output functions structure.  If NULL, any errors
  *                      will simply be printed to stderr.
  */
 void pcmk__output_and_clear_error(GError *error, pcmk__output_t *out);
 
 int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml);
 void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml);
 int pcmk__log_output_new(pcmk__output_t **out);
 
 #if defined(PCMK__UNIT_TESTING)
 /* If we are building libcrmcommon_test.a, add this accessor function so we can
  * inspect the internal formatters hash table.
  */
 GHashTable *pcmk__output_formatters(void);
 #endif
 
 #define PCMK__OUTPUT_SPACER_IF(out_obj, cond)   \
     if (cond) {                                 \
         out->spacer(out);                       \
     }
 
 #define PCMK__OUTPUT_LIST_HEADER(out_obj, cond, retcode, title...)  \
     if (retcode == pcmk_rc_no_output) {                             \
         PCMK__OUTPUT_SPACER_IF(out_obj, cond);                      \
         retcode = pcmk_rc_ok;                                       \
         out_obj->begin_list(out_obj, NULL, NULL, title);            \
     }
 
 #define PCMK__OUTPUT_LIST_FOOTER(out_obj, retcode)  \
     if (retcode == pcmk_rc_ok) {                    \
         out_obj->end_list(out_obj);                 \
     }
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/include/pcmki/pcmki_status.h b/include/pcmki/pcmki_status.h
index 9f442e7aaa..3efb9e26fe 100644
--- a/include/pcmki/pcmki_status.h
+++ b/include/pcmki/pcmki_status.h
@@ -1,59 +1,60 @@
 /*
  * Copyright 2022 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 PCMK__PCMKI_PCMKI_STATUS__H
 #define PCMK__PCMKI_PCMKI_STATUS__H
 
 #include <stdbool.h>
 #include <stdint.h>
 
 #include <crm/cib/cib_types.h>
 #include <crm/pengine/pe_types.h>
 #include <crm/common/output_internal.h>
 #include <pcmki/pcmki_fence.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /*!
  * \internal
  * \brief Print one-line status suitable for use with monitoring software
  *
  * \param[in] data_set  Working set of CIB state
  *
  * \return Standard Pacemaker return code
  *
  * \note This function's output (and the return code when the program exits)
  *       should conform to https://www.monitoring-plugins.org/doc/guidelines.html
  *
  * \note This function is planned to be deprecated and then removed in the
  *       future.  It should only be called from crm_mon, and no additional
  *       callers should be added.
  */
 int pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set);
 
 int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith,
                                 cib_t *cib, xmlNode *current_cib,
+                                enum pcmk_pacemakerd_state pcmkd_state,
                                 enum pcmk__fence_history fence_history,
                                 uint32_t show, uint32_t show_opts,
                                 const char *only_node, const char *only_rsc,
                                 const char *neg_location_prefix,
                                 bool simple_output);
 
 int pcmk__status(pcmk__output_t *out, cib_t *cib,
                  enum pcmk__fence_history fence_history, uint32_t show,
                  uint32_t show_opts, const char *only_node,
                  const char *only_rsc, const char *neg_location_prefix,
                  bool simple_output, unsigned int timeout_ms);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 4774ae99be..fc1330406a 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,819 +1,819 @@
 /*
  * Original copyright 2004 International Business Machines
  * Later changes copyright 2008-2022 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 <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <sys/utsname.h>
 
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/cib/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/pengine/rules.h>
 
 xmlNode *
 cib_get_generation(cib_t * cib)
 {
     xmlNode *the_cib = NULL;
     xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
 
     cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
     if (the_cib != NULL) {
         copy_in_properties(generation, the_cib);
         free_xml(the_cib);
     }
 
     return generation;
 }
 
 gboolean
 cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
 {
     *epoch = -1;
     *updates = -1;
     *admin_epoch = -1;
 
     if (cib == NULL) {
         return FALSE;
 
     } else {
         crm_element_value_int(cib, XML_ATTR_GENERATION, epoch);
         crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates);
         crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch);
     }
     return TRUE;
 }
 
 gboolean
 cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
                          int *_admin_epoch, int *_epoch, int *_updates)
 {
     int add[] = { 0, 0, 0 };
     int del[] = { 0, 0, 0 };
 
     xml_patch_versions(diff, add, del);
 
     *admin_epoch = add[0];
     *epoch = add[1];
     *updates = add[2];
 
     *_admin_epoch = del[0];
     *_epoch = del[1];
     *_updates = del[2];
 
     return TRUE;
 }
 
 /*!
  * \brief Create XML for a new (empty) CIB
  *
  * \param[in] cib_epoch   What to use as "epoch" CIB property
  *
  * \return Newly created XML for empty CIB
  * \note It is the caller's responsibility to free the result with free_xml().
  */
 xmlNode *
 createEmptyCib(int cib_epoch)
 {
     xmlNode *cib_root = NULL, *config = NULL;
 
     cib_root = create_xml_node(NULL, XML_TAG_CIB);
     crm_xml_add(cib_root, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
     crm_xml_add(cib_root, XML_ATTR_VALIDATION, xml_latest_schema());
 
     crm_xml_add_int(cib_root, XML_ATTR_GENERATION, cib_epoch);
     crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0);
     crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0);
 
     config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION);
     create_xml_node(cib_root, XML_CIB_TAG_STATUS);
 
     create_xml_node(config, XML_CIB_TAG_CRMCONFIG);
     create_xml_node(config, XML_CIB_TAG_NODES);
     create_xml_node(config, XML_CIB_TAG_RESOURCES);
     create_xml_node(config, XML_CIB_TAG_CONSTRAINTS);
 
 #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
     {
         xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG);
         xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS);
         xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR);
 
         crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults");
         crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS);
         crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS);
         crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE,
                         PCMK__RESOURCE_STICKINESS_DEFAULT);
     }
 #endif
     return cib_root;
 }
 
 static bool
 cib_acl_enabled(xmlNode *xml, const char *user)
 {
     bool rc = FALSE;
 
     if(pcmk_acl_required(user)) {
         const char *value = NULL;
         GHashTable *options = pcmk__strkey_table(free, free);
 
         cib_read_config(options, xml);
         value = cib_pref(options, "enable-acl");
         rc = crm_is_true(value);
         g_hash_table_destroy(options);
     }
 
     crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
     return rc;
 }
 
 int
 cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query,
                const char *section, xmlNode * req, xmlNode * input,
                gboolean manage_counters, gboolean * config_changed,
                xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output)
 {
     int rc = pcmk_ok;
     gboolean check_schema = TRUE;
     xmlNode *top = NULL;
     xmlNode *scratch = NULL;
     xmlNode *local_diff = NULL;
 
     const char *new_version = NULL;
     static struct qb_log_callsite *diff_cs = NULL;
     const char *user = crm_element_value(req, F_CIB_USER);
     bool with_digest = FALSE;
 
     crm_trace("Begin %s%s%s op",
               (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
               (is_query? "read-only " : ""), op);
 
     CRM_CHECK(output != NULL, return -ENOMSG);
     CRM_CHECK(result_cib != NULL, return -ENOMSG);
     CRM_CHECK(config_changed != NULL, return -ENOMSG);
 
     if(output) {
         *output = NULL;
     }
 
     *result_cib = NULL;
     *config_changed = FALSE;
 
     if (fn == NULL) {
         return -EINVAL;
     }
 
     if (is_query) {
         xmlNode *cib_ro = current_cib;
         xmlNode *cib_filtered = NULL;
 
         if(cib_acl_enabled(cib_ro, user)) {
             if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) {
                 if (cib_filtered == NULL) {
                     crm_debug("Pre-filtered the entire cib");
                     return -EACCES;
                 }
                 cib_ro = cib_filtered;
                 crm_log_xml_trace(cib_ro, "filtered");
             }
         }
 
         rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
 
         if(output == NULL || *output == NULL) {
             /* nothing */
 
         } else if(cib_filtered == *output) {
             cib_filtered = NULL; /* Let them have this copy */
 
         } else if(*output == current_cib) {
             /* They already know not to free it */
 
         } else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
             /* We're about to free the document of which *output is a part */
             *output = copy_xml(*output);
 
         } else if((*output)->doc == current_cib->doc) {
             /* Give them a copy they can free */
             *output = copy_xml(*output);
         }
 
         free_xml(cib_filtered);
         return rc;
     }
 
 
     if (pcmk_is_set(call_options, cib_zero_copy)) {
         /* Conditional on v2 patch style */
 
         scratch = current_cib;
 
         /* Create a shallow copy of current_cib for the version details */
         current_cib = create_xml_node(NULL, (const char *)scratch->name);
         copy_in_properties(current_cib, scratch);
         top = current_cib;
 
         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
         rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
 
     } else {
         scratch = copy_xml(current_cib);
         xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
         rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output);
 
         if(scratch && xml_tracking_changes(scratch) == FALSE) {
             crm_trace("Inferring changes after %s op", op);
             xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user));
             xml_calculate_changes(current_cib, scratch);
         }
         CRM_CHECK(current_cib != scratch, return -EINVAL);
     }
 
     xml_acl_disable(scratch); /* Allow the system to make any additional changes */
 
     if (rc == pcmk_ok && scratch == NULL) {
         rc = -EINVAL;
         goto done;
 
     } else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
         crm_trace("ACL rejected part or all of the proposed changes");
         rc = -EACCES;
         goto done;
 
     } else if (rc != pcmk_ok) {
         goto done;
     }
 
     if (scratch) {
         new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION);
 
         if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) {
             crm_err("Discarding update with feature set '%s' greater than our own '%s'",
                     new_version, CRM_FEATURE_SET);
             rc = -EPROTONOSUPPORT;
             goto done;
         }
     }
 
     if (current_cib) {
         int old = 0;
         int new = 0;
 
         crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new);
         crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old);
 
         if (old > new) {
             crm_err("%s went backwards: %d -> %d (Opts: %#x)",
                     XML_ATTR_GENERATION_ADMIN, old, new, call_options);
             crm_log_xml_warn(req, "Bad Op");
             crm_log_xml_warn(input, "Bad Data");
             rc = -pcmk_err_old_data;
 
         } else if (old == new) {
             crm_element_value_int(scratch, XML_ATTR_GENERATION, &new);
             crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old);
             if (old > new) {
                 crm_err("%s went backwards: %d -> %d (Opts: %#x)",
                         XML_ATTR_GENERATION, old, new, call_options);
                 crm_log_xml_warn(req, "Bad Op");
                 crm_log_xml_warn(input, "Bad Data");
                 rc = -pcmk_err_old_data;
             }
         }
     }
 
     crm_trace("Massaging CIB contents");
     pcmk__strip_xml_text(scratch);
     fix_plus_plus_recursive(scratch);
 
     if (pcmk_is_set(call_options, cib_zero_copy)) {
         /* At this point, current_cib is just the 'cib' tag and its properties,
          *
          * The v1 format would barf on this, but we know the v2 patch
          * format only needs it for the top-level version fields
          */
         local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters);
 
     } else {
         static time_t expires = 0;
         time_t tm_now = time(NULL);
 
         if (expires < tm_now) {
             expires = tm_now + 60;  /* Validate clients are correctly applying v2-style diffs at most once a minute */
             with_digest = TRUE;
         }
 
         local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters);
     }
 
     xml_log_changes(LOG_TRACE, __func__, scratch);
     xml_accept_changes(scratch);
 
     if (diff_cs == NULL) {
         diff_cs = qb_log_callsite_get(__PRETTY_FUNCTION__, __FILE__, "diff-validation", LOG_DEBUG, __LINE__, crm_trace_nonlog);
     }
 
     if(local_diff) {
         patchset_process_digest(local_diff, current_cib, scratch, with_digest);
 
         xml_log_patchset(LOG_INFO, __func__, local_diff);
         crm_log_xml_trace(local_diff, "raw patch");
     }
 
     if (!pcmk_is_set(call_options, cib_zero_copy) // Original to compare against doesn't exist
         && local_diff
         && crm_is_callsite_active(diff_cs, LOG_TRACE, 0)) {
 
         /* Validate the calculated patch set */
         int test_rc, format = 1;
         xmlNode * c = copy_xml(current_cib);
 
         crm_element_value_int(local_diff, "format", &format);
         test_rc = xml_apply_patchset(c, local_diff, manage_counters);
 
         if(test_rc != pcmk_ok) {
             save_xml_to_file(c,           "PatchApply:calculated", NULL);
             save_xml_to_file(current_cib, "PatchApply:input", NULL);
             save_xml_to_file(scratch,     "PatchApply:actual", NULL);
             save_xml_to_file(local_diff,  "PatchApply:diff", NULL);
             crm_err("v%d patchset error, patch failed to apply: %s (%d)", format, pcmk_strerror(test_rc), test_rc);
         }
         free_xml(c);
     }
 
     if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
         /* Throttle the amount of costly validation we perform due to status updates
          * a) we don't really care whats in the status section
          * b) we don't validate any of its contents at the moment anyway
          */
         check_schema = FALSE;
     }
 
     /* === scratch must not be modified after this point ===
      * Exceptions, anything in:
 
      static filter_t filter[] = {
      { 0, XML_ATTR_ORIGIN },
      { 0, XML_CIB_ATTR_WRITTEN },
      { 0, XML_ATTR_UPDATE_ORIG },
      { 0, XML_ATTR_UPDATE_CLIENT },
      { 0, XML_ATTR_UPDATE_USER },
      };
      */
 
     if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
         const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION);
 
         pcmk__xe_add_last_written(scratch);
         if (schema) {
             static int minimum_schema = 0;
             int current_schema = get_schema_version(schema);
 
             if (minimum_schema == 0) {
                 minimum_schema = get_schema_version("pacemaker-1.2");
             }
 
             /* Does the CIB support the "update-*" attributes... */
             if (current_schema >= minimum_schema) {
                 const char *origin = crm_element_value(req, F_ORIG);
 
                 CRM_LOG_ASSERT(origin != NULL);
                 crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin);
                 crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT,
                                 crm_element_value(req, F_CIB_CLIENTNAME));
                 crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER));
             }
         }
     }
 
     crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
     if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) {
         const char *current_schema = crm_element_value(scratch,
                                                        XML_ATTR_VALIDATION);
 
         crm_warn("Updated CIB does not validate against %s schema",
                  pcmk__s(current_schema, "unspecified"));
         rc = -pcmk_err_schema_validation;
     }
 
   done:
 
     *result_cib = scratch;
     if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) {
         if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) {
             if (*result_cib == NULL) {
                 crm_debug("Pre-filtered the entire cib result");
             }
             free_xml(scratch);
         }
     }
 
     if(diff) {
         *diff = local_diff;
     } else {
         free_xml(local_diff);
     }
 
     free_xml(top);
     crm_trace("Done");
     return rc;
 }
 
 xmlNode *
 cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section,
               xmlNode * data, int call_options, const char *user_name)
 {
     xmlNode *op_msg = create_xml_node(NULL, "cib_command");
 
     CRM_CHECK(op_msg != NULL, return NULL);
     CRM_CHECK(token != NULL, return NULL);
 
     crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command");
 
     crm_xml_add(op_msg, F_TYPE, T_CIB);
     crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token);
     crm_xml_add(op_msg, F_CIB_OPERATION, op);
     crm_xml_add(op_msg, F_CIB_HOST, host);
     crm_xml_add(op_msg, F_CIB_SECTION, section);
     crm_xml_add_int(op_msg, F_CIB_CALLID, call_id);
     if (user_name) {
         crm_xml_add(op_msg, F_CIB_USER, user_name);
     }
     crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
     crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options);
 
     if (data != NULL) {
         add_message_xml(op_msg, F_CIB_CALLDATA, data);
     }
 
     if (call_options & cib_inhibit_bcast) {
         CRM_CHECK((call_options & cib_scope_local), return NULL);
     }
     return op_msg;
 }
 
 void
 cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
 {
     xmlNode *output = NULL;
     cib_callback_client_t *blob = NULL;
 
     if (msg != NULL) {
         crm_element_value_int(msg, F_CIB_RC, &rc);
         crm_element_value_int(msg, F_CIB_CALLID, &call_id);
         output = get_message_xml(msg, F_CIB_CALLDATA);
     }
 
     blob = cib__lookup_id(call_id);
 
     if (blob == NULL) {
         crm_trace("No callback found for call %d", call_id);
     }
 
     if (cib == NULL) {
         crm_debug("No cib object supplied");
     }
 
     if (rc == -pcmk_err_diff_resync) {
         /* This is an internal value that clients do not and should not care about */
         rc = pcmk_ok;
     }
 
     if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
         crm_trace("Invoking callback %s for call %d",
                   pcmk__s(blob->id, "without ID"), call_id);
         blob->callback(msg, call_id, rc, output, blob->user_data);
 
     } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
         crm_warn("CIB command failed: %s", pcmk_strerror(rc));
         crm_log_xml_debug(msg, "Failed CIB Update");
     }
 
     /* This may free user_data, so do it after the callback */
     if (blob) {
         remove_cib_op_callback(call_id, FALSE);
     }
 
     if (cib && cib->op_callback != NULL) {
         crm_trace("Invoking global callback for call %d", call_id);
         cib->op_callback(msg, call_id, rc, output);
     }
     crm_trace("OP callback activated for %d", call_id);
 }
 
 void
 cib_native_notify(gpointer data, gpointer user_data)
 {
     xmlNode *msg = user_data;
     cib_notify_client_t *entry = data;
     const char *event = NULL;
 
     if (msg == NULL) {
         crm_warn("Skipping callback - NULL message");
         return;
     }
 
     event = crm_element_value(msg, F_SUBTYPE);
 
     if (entry == NULL) {
         crm_warn("Skipping callback - NULL callback client");
         return;
 
     } else if (entry->callback == NULL) {
         crm_warn("Skipping callback - NULL callback");
         return;
 
     } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
         crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
         return;
     }
 
     crm_trace("Invoking callback for %p/%s event...", entry, event);
     entry->callback(event, msg);
     crm_trace("Callback invoked...");
 }
 
 static pcmk__cluster_option_t cib_opts[] = {
     /* name, legacy name, type, allowed values,
      * default value, validator,
      * short description,
      * long description
      */
     {
         "enable-acl", NULL, "boolean", NULL,
         "false", pcmk__valid_boolean,
         N_("Enable Access Control Lists (ACLs) for the CIB"),
         NULL
     },
     {
         "cluster-ipc-limit", NULL, "integer", NULL,
         "500", pcmk__valid_positive_number,
         N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
         N_("Raise this if log has \"Evicting client\" messages for cluster daemon"
             " PIDs (a good value is the number of resources in the cluster"
             " multiplied by the number of nodes).")
     },
 };
 
 void
 cib_metadata(void)
 {
     const char *desc_short = "Cluster Information Base manager options";
     const char *desc_long = "Cluster options used by Pacemaker's Cluster "
                             "Information Base manager";
 
     gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short,
                                             desc_long, cib_opts,
                                             PCMK__NELEM(cib_opts));
     printf("%s", s);
     g_free(s);
 }
 
 void
 verify_cib_options(GHashTable * options)
 {
     pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts));
 }
 
 const char *
 cib_pref(GHashTable * options, const char *name)
 {
     return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts),
                                 name);
 }
 
 gboolean
 cib_read_config(GHashTable * options, xmlNode * current_cib)
 {
     xmlNode *config = NULL;
     crm_time_t *now = NULL;
 
     if (options == NULL || current_cib == NULL) {
         return FALSE;
     }
 
     now = crm_time_new(NULL);
 
     g_hash_table_remove_all(options);
 
     config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG);
     if (config) {
         pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL,
                           options, CIB_OPTIONS_FIRST, TRUE, now, NULL);
     }
 
     verify_cib_options(options);
 
     crm_time_free(now);
 
     return TRUE;
 }
 
 /* v2 and v2 patch formats */
 #define XPATH_CONFIG_CHANGE \
     "//" XML_CIB_TAG_CRMCONFIG " | " \
     "//" XML_DIFF_CHANGE "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]"
 
 gboolean
 cib_internal_config_changed(xmlNode *diff)
 {
     gboolean changed = FALSE;
 
     if (diff) {
         xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE);
 
         if (numXpathResults(xpathObj) > 0) {
             changed = TRUE;
         }
         freeXpathObject(xpathObj);
     }
     return changed;
 }
 
 int
 cib_internal_op(cib_t * cib, const char *op, const char *host,
                 const char *section, xmlNode * data,
                 xmlNode ** output_data, int call_options, const char *user_name)
 {
     int (*delegate) (cib_t * cib, const char *op, const char *host,
                      const char *section, xmlNode * data,
                      xmlNode ** output_data, int call_options, const char *user_name) =
         cib->delegate_fn;
 
     if(user_name == NULL) {
         user_name = getenv("CIB_user");
     }
 
     return delegate(cib, op, host, section, data, output_data, call_options, user_name);
 }
 
 /*!
  * \brief Apply a CIB update patch to a given CIB
  *
  * \param[in]  event   CIB update patch
  * \param[in]  input   CIB to patch
  * \param[out] output  Resulting CIB after patch
  * \param[in]  level   Log the patch at this log level (unless LOG_CRIT)
  *
  * \return Legacy Pacemaker return code
  * \note sbd calls this function
  */
 int
 cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
                       int level)
 {
     int rc = pcmk_err_generic;
 
     xmlNode *diff = NULL;
 
     CRM_ASSERT(event);
     CRM_ASSERT(input);
     CRM_ASSERT(output);
 
     crm_element_value_int(event, F_CIB_RC, &rc);
     diff = get_message_xml(event, F_CIB_UPDATE_RESULT);
 
     if (rc < pcmk_ok || diff == NULL) {
         return rc;
     }
 
     if (level > LOG_CRIT) {
         xml_log_patchset(level, "Config update", diff);
     }
 
     if (input != NULL) {
         rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
                               NULL);
 
         if (rc != pcmk_ok) {
             crm_debug("Update didn't apply: %s (%d) %p",
                       pcmk_strerror(rc), rc, *output);
 
             if (rc == -pcmk_err_old_data) {
                 crm_trace("Masking error, we already have the supplied update");
                 return pcmk_ok;
             }
             free_xml(*output);
             *output = NULL;
             return rc;
         }
     }
     return rc;
 }
 
 #define log_signon_query_err(out, fmt, args...) do {    \
         if (out != NULL) {                              \
             out->err(out, fmt, ##args);                 \
         } else {                                        \
             crm_err(fmt, ##args);                       \
         }                                               \
     } while (0)
 
 int
 cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
 {
     int rc = pcmk_rc_ok;
     cib_t *cib_conn = NULL;
 
     CRM_ASSERT(cib_object != NULL);
 
     if (cib == NULL) {
         cib_conn = cib_new();
     } else {
         if (*cib == NULL) {
             *cib = cib_new();
         }
         cib_conn = *cib;
     }
 
     if (cib_conn == NULL) {
         return ENOMEM;
     }
 
     if (cib_conn->state == cib_disconnected) {
         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
         rc = pcmk_legacy2rc(rc);
     }
 
     if (rc != pcmk_rc_ok) {
         log_signon_query_err(out, "Could not connect to the CIB: %s",
                              pcmk_rc_str(rc));
         goto done;
     }
 
     if (out != NULL) {
         out->transient(out, "Querying CIB...");
     }
     rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
                                cib_scope_local|cib_sync_call);
     rc = pcmk_legacy2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
     }
 
 done:
     if (cib == NULL) {
         cib__clean_up_connection(&cib_conn);
     }
 
-    if (*cib_object == NULL) {
+    if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
         return pcmk_rc_no_input;
     }
     return rc;
 }
 
 int
 cib__clean_up_connection(cib_t **cib)
 {
     int rc;
 
     if (*cib == NULL) {
         return pcmk_rc_ok;
     }
 
     rc = (*cib)->cmds->signoff(*cib);
     cib_delete(*cib);
     *cib = NULL;
     return pcmk_legacy2rc(rc);
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/cib/util_compat.h>
 
 const char *
 get_object_path(const char *object_type)
 {
     return pcmk_cib_xpath_for(object_type);
 }
 
 const char *
 get_object_parent(const char *object_type)
 {
     return pcmk_cib_parent_name_for(object_type);
 }
 
 xmlNode *
 get_object_root(const char *object_type, xmlNode *the_root)
 {
     return pcmk_find_cib_element(the_root, object_type);
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c
index 79a6b53b95..1f3d1ecde9 100644
--- a/lib/pacemaker/pcmk_output.c
+++ b/lib/pacemaker/pcmk_output.c
@@ -1,2245 +1,2256 @@
 /*
  * Copyright 2019-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 #include <crm/msg_xml.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/pengine/internal.h>
 #include <libxml/tree.h>
 #include <pacemaker-internal.h>
 
 #include <stdint.h>
 
 static char *
 colocations_header(pe_resource_t *rsc, pcmk__colocation_t *cons,
                    bool dependents) {
     char *retval = NULL;
 
     if (cons->primary_role > RSC_ROLE_STARTED) {
         retval = crm_strdup_printf("%s (score=%s, %s role=%s, id=%s)",
                                    rsc->id, pcmk_readable_score(cons->score),
                                    (dependents? "needs" : "with"),
                                    role2text(cons->primary_role), cons->id);
     } else {
         retval = crm_strdup_printf("%s (score=%s, id=%s)",
                                    rsc->id, pcmk_readable_score(cons->score),
                                    cons->id);
     }
     return retval;
 }
 
 static void
 colocations_xml_node(pcmk__output_t *out, pe_resource_t *rsc,
                      pcmk__colocation_t *cons) {
     xmlNodePtr node = NULL;
 
     node = pcmk__output_create_xml_node(out, XML_CONS_TAG_RSC_DEPEND,
                                         "id", cons->id,
                                         "rsc", cons->dependent->id,
                                         "with-rsc", cons->primary->id,
                                         "score", pcmk_readable_score(cons->score),
                                         NULL);
 
     if (cons->node_attribute) {
         xmlSetProp(node, (pcmkXmlStr) "node-attribute", (pcmkXmlStr) cons->node_attribute);
     }
 
     if (cons->dependent_role != RSC_ROLE_UNKNOWN) {
         xmlSetProp(node, (pcmkXmlStr) "rsc-role",
                    (pcmkXmlStr) role2text(cons->dependent_role));
     }
 
     if (cons->primary_role != RSC_ROLE_UNKNOWN) {
         xmlSetProp(node, (pcmkXmlStr) "with-rsc-role",
                    (pcmkXmlStr) role2text(cons->primary_role));
     }
 }
 
 static int
 do_locations_list_xml(pcmk__output_t *out, pe_resource_t *rsc, bool add_header)
 {
     GList *lpc = NULL;
     GList *list = rsc->rsc_location;
     int rc = pcmk_rc_no_output;
 
     for (lpc = list; lpc != NULL; lpc = lpc->next) {
         pe__location_t *cons = lpc->data;
 
         GList *lpc2 = NULL;
 
         for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) {
             pe_node_t *node = (pe_node_t *) lpc2->data;
 
             if (add_header) {
                 PCMK__OUTPUT_LIST_HEADER(out, false, rc, "locations");
             }
 
             pcmk__output_create_xml_node(out, XML_CONS_TAG_RSC_LOCATION,
                                          "node", node->details->uname,
                                          "rsc", rsc->id,
                                          "id", cons->id,
                                          "score", pcmk_readable_score(node->weight),
                                          NULL);
         }
     }
 
     if (add_header) {
         PCMK__OUTPUT_LIST_FOOTER(out, rc);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rsc-action-item", "const char *", "pe_resource_t *",
                   "pe_node_t *", "pe_node_t *", "pe_action_t *",
                   "pe_action_t *")
 static int
 rsc_action_item(pcmk__output_t *out, va_list args)
 {
     const char *change = va_arg(args, const char *);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *origin = va_arg(args, pe_node_t *);
     pe_node_t *destination = va_arg(args, pe_node_t *);
     pe_action_t *action = va_arg(args, pe_action_t *);
     pe_action_t *source = va_arg(args, pe_action_t *);
 
     int len = 0;
     char *reason = NULL;
     char *details = NULL;
     bool same_host = false;
     bool same_role = false;
     bool need_role = false;
 
     static int rsc_width = 5;
     static int detail_width = 5;
 
     CRM_ASSERT(action);
     CRM_ASSERT(destination != NULL || origin != NULL);
 
     if(source == NULL) {
         source = action;
     }
 
     len = strlen(rsc->id);
     if(len > rsc_width) {
         rsc_width = len + 2;
     }
 
     if ((rsc->role > RSC_ROLE_STARTED)
         || (rsc->next_role > RSC_ROLE_UNPROMOTED)) {
         need_role = true;
     }
 
     if(origin != NULL && destination != NULL && origin->details == destination->details) {
         same_host = true;
     }
 
     if(rsc->role == rsc->next_role) {
         same_role = true;
     }
 
     if (need_role && (origin == NULL)) {
         /* Starting and promoting a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", role2text(rsc->role),
                                     role2text(rsc->next_role),
                                     pe__node_name(destination));
 
     } else if (origin == NULL) {
         /* Starting a resource */
         details = crm_strdup_printf("%s", pe__node_name(destination));
 
     } else if (need_role && (destination == NULL)) {
         /* Stopping a promotable clone instance */
         details = crm_strdup_printf("%s %s", role2text(rsc->role),
                                     pe__node_name(origin));
 
     } else if (destination == NULL) {
         /* Stopping a resource */
         details = crm_strdup_printf("%s", pe__node_name(origin));
 
     } else if (need_role && same_role && same_host) {
         /* Recovering, restarting or re-promoting a promotable clone instance */
         details = crm_strdup_printf("%s %s", role2text(rsc->role),
                                     pe__node_name(origin));
 
     } else if (same_role && same_host) {
         /* Recovering or Restarting a normal resource */
         details = crm_strdup_printf("%s", pe__node_name(origin));
 
     } else if (need_role && same_role) {
         /* Moving a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", pe__node_name(origin),
                                     pe__node_name(destination),
                                     role2text(rsc->role));
 
     } else if (same_role) {
         /* Moving a normal resource */
         details = crm_strdup_printf("%s -> %s", pe__node_name(origin),
                                     pe__node_name(destination));
 
     } else if (same_host) {
         /* Promoting or demoting a promotable clone instance */
         details = crm_strdup_printf("%s -> %s %s", role2text(rsc->role),
                                     role2text(rsc->next_role),
                                     pe__node_name(origin));
 
     } else {
         /* Moving and promoting/demoting */
         details = crm_strdup_printf("%s %s -> %s %s", role2text(rsc->role),
                                     pe__node_name(origin),
                                     role2text(rsc->next_role),
                                     pe__node_name(destination));
     }
 
     len = strlen(details);
     if(len > detail_width) {
         detail_width = len;
     }
 
     if(source->reason && !pcmk_is_set(action->flags, pe_action_runnable)) {
         reason = crm_strdup_printf("due to %s (blocked)", source->reason);
 
     } else if(source->reason) {
         reason = crm_strdup_printf("due to %s", source->reason);
 
     } else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
         reason = strdup("blocked");
 
     }
 
     out->list_item(out, NULL, "%-8s   %-*s   ( %*s )%s%s", change, rsc_width,
                    rsc->id, detail_width, details, reason ? "  " : "", reason ? reason : "");
 
     free(details);
     free(reason);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("rsc-action-item", "const char *", "pe_resource_t *",
                   "pe_node_t *", "pe_node_t *", "pe_action_t *",
                   "pe_action_t *")
 static int
 rsc_action_item_xml(pcmk__output_t *out, va_list args)
 {
     const char *change = va_arg(args, const char *);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *origin = va_arg(args, pe_node_t *);
     pe_node_t *destination = va_arg(args, pe_node_t *);
     pe_action_t *action = va_arg(args, pe_action_t *);
     pe_action_t *source = va_arg(args, pe_action_t *);
 
     char *change_str = NULL;
 
     bool same_host = false;
     bool same_role = false;
     bool need_role = false;
     xmlNode *xml = NULL;
 
     CRM_ASSERT(action);
     CRM_ASSERT(destination != NULL || origin != NULL);
 
     if (source == NULL) {
         source = action;
     }
 
     if ((rsc->role > RSC_ROLE_STARTED)
         || (rsc->next_role > RSC_ROLE_UNPROMOTED)) {
         need_role = true;
     }
 
     if(origin != NULL && destination != NULL && origin->details == destination->details) {
         same_host = true;
     }
 
     if(rsc->role == rsc->next_role) {
         same_role = true;
     }
 
     change_str = g_ascii_strdown(change, -1);
     xml = pcmk__output_create_xml_node(out, "rsc_action",
                                        "action", change_str,
                                        "resource", rsc->id,
                                        NULL);
     g_free(change_str);
 
     if (need_role && (origin == NULL)) {
         /* Starting and promoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "next-role", role2text(rsc->next_role),
                            "dest", destination->details->uname,
                            NULL);
 
     } else if (origin == NULL) {
         /* Starting a resource */
         crm_xml_add(xml, "node", destination->details->uname);
 
     } else if (need_role && (destination == NULL)) {
         /* Stopping a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "node", origin->details->uname,
                            NULL);
 
     } else if (destination == NULL) {
         /* Stopping a resource */
         crm_xml_add(xml, "node", origin->details->uname);
 
     } else if (need_role && same_role && same_host) {
         /* Recovering, restarting or re-promoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "source", origin->details->uname,
                            NULL);
 
     } else if (same_role && same_host) {
         /* Recovering or Restarting a normal resource */
         crm_xml_add(xml, "source", origin->details->uname);
 
     } else if (need_role && same_role) {
         /* Moving a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "source", origin->details->uname,
                            "dest", destination->details->uname,
                            "role", role2text(rsc->role),
                            NULL);
 
     } else if (same_role) {
         /* Moving a normal resource */
         pcmk__xe_set_props(xml,
                            "source", origin->details->uname,
                            "dest", destination->details->uname,
                            NULL);
 
     } else if (same_host) {
         /* Promoting or demoting a promotable clone instance */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "next-role", role2text(rsc->next_role),
                            "source", origin->details->uname,
                            NULL);
 
     } else {
         /* Moving and promoting/demoting */
         pcmk__xe_set_props(xml,
                            "role", role2text(rsc->role),
                            "source", origin->details->uname,
                            "next-role", role2text(rsc->next_role),
                            "dest", destination->details->uname,
                            NULL);
     }
 
     if (source->reason && !pcmk_is_set(action->flags, pe_action_runnable)) {
         pcmk__xe_set_props(xml,
                            "reason", source->reason,
                            "blocked", "true",
                            NULL);
 
     } else if(source->reason) {
         crm_xml_add(xml, "reason", source->reason);
 
     } else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
         pcmk__xe_set_bool_attr(xml, "blocked", true);
 
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("rsc-is-colocated-with-list", "pe_resource_t *", "bool")
 static int
 rsc_is_colocated_with_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool recursive = va_arg(args, int);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
         char *hdr = NULL;
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources %s is colocated with", rsc->id);
 
         if (pcmk_is_set(cons->primary->flags, pe_rsc_allocating)) {
             out->list_item(out, NULL, "%s (id=%s - loop)",
                            cons->primary->id, cons->id);
             continue;
         }
 
         hdr = colocations_header(cons->primary, cons, false);
         out->list_item(out, NULL, "%s", hdr);
         free(hdr);
 
         /* Empty list header just for indentation of information about this resource. */
         out->begin_list(out, NULL, NULL, NULL);
 
         out->message(out, "locations-list", cons->primary);
         if (recursive) {
             out->message(out, "rsc-is-colocated-with-list",
                          cons->primary, recursive);
         }
 
         out->end_list(out);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rsc-is-colocated-with-list", "pe_resource_t *", "bool")
 static int
 rsc_is_colocated_with_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool recursive = va_arg(args, int);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
         if (pcmk_is_set(cons->primary->flags, pe_rsc_allocating)) {
             colocations_xml_node(out, cons->primary, cons);
             continue;
         }
 
         colocations_xml_node(out, cons->primary, cons);
         do_locations_list_xml(out, cons->primary, false);
 
         if (recursive) {
             out->message(out, "rsc-is-colocated-with-list",
                          cons->primary, recursive);
         }
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rscs-colocated-with-list", "pe_resource_t *", "bool")
 static int
 rscs_colocated_with_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool recursive = va_arg(args, int);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
         char *hdr = NULL;
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources colocated with %s", rsc->id);
 
         if (pcmk_is_set(cons->dependent->flags, pe_rsc_allocating)) {
             out->list_item(out, NULL, "%s (id=%s - loop)",
                            cons->dependent->id, cons->id);
             continue;
         }
 
         hdr = colocations_header(cons->dependent, cons, true);
         out->list_item(out, NULL, "%s", hdr);
         free(hdr);
 
         /* Empty list header just for indentation of information about this resource. */
         out->begin_list(out, NULL, NULL, NULL);
 
         out->message(out, "locations-list", cons->dependent);
         if (recursive) {
             out->message(out, "rscs-colocated-with-list",
                          cons->dependent, recursive);
         }
 
         out->end_list(out);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("rscs-colocated-with-list", "pe_resource_t *", "bool")
 static int
 rscs_colocated_with_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool recursive = va_arg(args, int);
 
     int rc = pcmk_rc_no_output;
 
     if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
         return rc;
     }
 
     pe__set_resource_flags(rsc, pe_rsc_allocating);
     for (GList *lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
 
         if (pcmk_is_set(cons->dependent->flags, pe_rsc_allocating)) {
             colocations_xml_node(out, cons->dependent, cons);
             continue;
         }
 
         colocations_xml_node(out, cons->dependent, cons);
         do_locations_list_xml(out, cons->dependent, false);
 
         if (recursive) {
             out->message(out, "rscs-colocated-with-list",
                          cons->dependent, recursive);
         }
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *")
 static int
 locations_list(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
 
     GList *lpc = NULL;
     GList *list = rsc->rsc_location;
     int rc = pcmk_rc_no_output;
 
     for (lpc = list; lpc != NULL; lpc = lpc->next) {
         pe__location_t *cons = lpc->data;
 
         GList *lpc2 = NULL;
 
         for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) {
             pe_node_t *node = (pe_node_t *) lpc2->data;
 
             PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Locations");
             out->list_item(out, NULL, "Node %s (score=%s, id=%s, rsc=%s)",
                            pe__node_name(node),
                            pcmk_readable_score(node->weight), cons->id,
                            rsc->id);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *")
 static int
 locations_list_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     return do_locations_list_xml(out, rsc, true);
 }
 
 PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "bool")
 static int
 stacks_and_constraints(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     bool recursive = va_arg(args, int);
 
     pcmk__unpack_constraints(data_set);
 
     // Constraints apply to group/clone, not member/instance
     rsc = uber_parent(rsc);
 
     out->message(out, "locations-list", rsc);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rscs-colocated-with-list", rsc, recursive);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rsc-is-colocated-with-list", rsc, recursive);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "bool")
 static int
 stacks_and_constraints_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     bool recursive = va_arg(args, int);
 
     pcmk__unpack_constraints(data_set);
 
     // Constraints apply to group/clone, not member/instance
     rsc = uber_parent(rsc);
 
     pcmk__output_xml_create_parent(out, "constraints", NULL);
     do_locations_list_xml(out, rsc, false);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rscs-colocated-with-list", rsc, recursive);
 
     pe__clear_resource_flags_on_all(data_set, pe_rsc_allocating);
     out->message(out, "rsc-is-colocated-with-list", rsc, recursive);
 
     pcmk__output_xml_pop_parent(out);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
 static int
 health(pcmk__output_t *out, va_list args)
 {
     const char *sys_from G_GNUC_UNUSED = va_arg(args, const char *);
     const char *host_from = va_arg(args, const char *);
     const char *fsa_state = va_arg(args, const char *);
     const char *result = va_arg(args, const char *);
 
     return out->info(out, "Controller on %s in state %s: %s",
                      pcmk__s(host_from, "unknown node"),
                      pcmk__s(fsa_state, "unknown"),
                      pcmk__s(result, "unknown result"));
 }
 
 PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
 static int
 health_text(pcmk__output_t *out, va_list args)
 {
     if (!out->is_quiet(out)) {
         return health(out, args);
     } else {
         const char *sys_from G_GNUC_UNUSED = va_arg(args, const char *);
         const char *host_from G_GNUC_UNUSED = va_arg(args, const char *);
         const char *fsa_state = va_arg(args, const char *);
         const char *result G_GNUC_UNUSED = va_arg(args, const char *);
 
         if (fsa_state != NULL) {
             pcmk__formatted_printf(out, "%s\n", fsa_state);
             return pcmk_rc_ok;
         }
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("health", "const char *", "const char *", "const char *", "const char *")
 static int
 health_xml(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     const char *host_from = va_arg(args, const char *);
     const char *fsa_state = va_arg(args, const char *);
     const char *result = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, pcmk__s(sys_from, ""),
                                  "node_name", pcmk__s(host_from, ""),
                                  "state", pcmk__s(fsa_state, ""),
                                  "result", pcmk__s(result, ""),
                                  NULL);
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "int", "const char *",
-                  "long long")
+PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *",
+                  "enum pcmk_pacemakerd_state", "const char *", "long long")
 static int
 pacemakerd_health(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     enum pcmk_pacemakerd_state state =
         (enum pcmk_pacemakerd_state) va_arg(args, int);
     const char *state_s = va_arg(args, const char *);
     time_t last_updated = (time_t) va_arg(args, long long);
 
     char *last_updated_s = NULL;
     int rc = pcmk_rc_ok;
 
     if (sys_from == NULL) {
         if (state == pcmk_pacemakerd_state_remote) {
             sys_from = "pacemaker-remoted";
         } else {
             sys_from = CRM_SYSTEM_MCP;
         }
     }
 
     if (state_s == NULL) {
         state_s = pcmk__pcmkd_state_enum2friendly(state);
     }
 
     if (last_updated != 0) {
         last_updated_s = pcmk__epoch2str(&last_updated,
                                          crm_time_log_date
                                          |crm_time_log_timeofday
                                          |crm_time_log_with_timezone);
     }
 
     rc = out->info(out, "Status of %s: '%s' (last updated %s)",
                    sys_from, state_s,
                    pcmk__s(last_updated_s, "at unknown time"));
 
     free(last_updated_s);
     return rc;
 }
 
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "int", "const char *",
-                  "long long")
+PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *",
+                  "enum pcmk_pacemakerd_state", "const char *", "long long")
 static int
 pacemakerd_health_html(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     enum pcmk_pacemakerd_state state =
         (enum pcmk_pacemakerd_state) va_arg(args, int);
     const char *state_s = va_arg(args, const char *);
     time_t last_updated = (time_t) va_arg(args, long long);
 
     char *last_updated_s = NULL;
     char *msg = NULL;
 
     if (sys_from == NULL) {
         if (state == pcmk_pacemakerd_state_remote) {
             sys_from = "pacemaker-remoted";
         } else {
             sys_from = CRM_SYSTEM_MCP;
         }
     }
 
     if (state_s == NULL) {
         state_s = pcmk__pcmkd_state_enum2friendly(state);
     }
 
     if (last_updated != 0) {
         last_updated_s = pcmk__epoch2str(&last_updated,
                                          crm_time_log_date
                                          |crm_time_log_timeofday
                                          |crm_time_log_with_timezone);
     }
 
     msg = crm_strdup_printf("Status of %s: '%s' (last updated %s)",
                             sys_from, state_s,
                             pcmk__s(last_updated_s, "at unknown time"));
     pcmk__output_create_html_node(out, "li", NULL, NULL, msg);
 
     free(msg);
     free(last_updated_s);
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "int", "const char *",
-                  "long long")
+PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *",
+                  "enum pcmk_pacemakerd_state", "const char *", "long long")
 static int
 pacemakerd_health_text(pcmk__output_t *out, va_list args)
 {
     if (!out->is_quiet(out)) {
         return pacemakerd_health(out, args);
     } else {
         const char *sys_from G_GNUC_UNUSED = va_arg(args, const char *);
         enum pcmk_pacemakerd_state state =
             (enum pcmk_pacemakerd_state) va_arg(args, int);
         const char *state_s = va_arg(args, const char *);
         time_t last_updated G_GNUC_UNUSED = (time_t) va_arg(args, long long);
 
         if (state_s == NULL) {
             state_s = pcmk_pacemakerd_api_daemon_state_enum2text(state);
         }
         pcmk__formatted_printf(out, "%s\n", state_s);
         return pcmk_rc_ok;
     }
 }
 
-PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *", "int", "const char *",
-                  "long long")
+PCMK__OUTPUT_ARGS("pacemakerd-health", "const char *",
+                  "enum pcmk_pacemakerd_state", "const char *", "long long")
 static int
 pacemakerd_health_xml(pcmk__output_t *out, va_list args)
 {
     const char *sys_from = va_arg(args, const char *);
     enum pcmk_pacemakerd_state state =
         (enum pcmk_pacemakerd_state) va_arg(args, int);
     const char *state_s = va_arg(args, const char *);
     time_t last_updated = (time_t) va_arg(args, long long);
 
     char *last_updated_s = NULL;
 
     if (sys_from == NULL) {
         if (state == pcmk_pacemakerd_state_remote) {
             sys_from = "pacemaker-remoted";
         } else {
             sys_from = CRM_SYSTEM_MCP;
         }
     }
 
     if (state_s == NULL) {
-        state_s = pcmk__pcmkd_state_enum2friendly(state);
+        state_s = pcmk_pacemakerd_api_daemon_state_enum2text(state);
     }
 
     if (last_updated != 0) {
         last_updated_s = pcmk__epoch2str(&last_updated,
                                          crm_time_log_date
                                          |crm_time_log_timeofday
                                          |crm_time_log_with_timezone);
     }
 
     pcmk__output_create_xml_node(out, "pacemakerd",
                                  "sys_from", sys_from,
                                  "state", state_s,
                                  "last_updated", last_updated_s,
                                  NULL);
     free(last_updated_s);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_default(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     out->list_item(out, NULL, "Testing %s ... %.2f secs", xml_file,
                    (end - start) / (float) CLOCKS_PER_SEC);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("profile", "const char *", "clock_t", "clock_t")
 static int
 profile_xml(pcmk__output_t *out, va_list args) {
     const char *xml_file = va_arg(args, const char *);
     clock_t start = va_arg(args, clock_t);
     clock_t end = va_arg(args, clock_t);
 
     char *duration = pcmk__ftoa((end - start) / (float) CLOCKS_PER_SEC);
 
     pcmk__output_create_xml_node(out, "timing",
                                  "file", xml_file,
                                  "duration", duration,
                                  NULL);
 
     free(duration);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("dc", "const char *")
 static int
 dc(pcmk__output_t *out, va_list args)
 {
     const char *dc = va_arg(args, const char *);
 
     return out->info(out, "Designated Controller is: %s",
                      pcmk__s(dc, "not yet elected"));
 }
 
 PCMK__OUTPUT_ARGS("dc", "const char *")
 static int
 dc_text(pcmk__output_t *out, va_list args)
 {
     if (!out->is_quiet(out)) {
         return dc(out, args);
     } else {
         const char *dc = va_arg(args, const char *);
 
         if (dc != NULL) {
             pcmk__formatted_printf(out, "%s\n", pcmk__s(dc, ""));
             return pcmk_rc_ok;
         }
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("dc", "const char *")
 static int
 dc_xml(pcmk__output_t *out, va_list args)
 {
     const char *dc = va_arg(args, const char *);
 
     pcmk__output_create_xml_node(out, "dc",
                                  "node_name", pcmk__s(dc, ""),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *", "bool")
 static int
 crmadmin_node(pcmk__output_t *out, va_list args)
 {
     const char *type = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *id = va_arg(args, const char *);
     bool bash_export = va_arg(args, int);
 
     if (bash_export) {
         return out->info(out, "export %s=%s",
                          pcmk__s(name, "<null>"), pcmk__s(id, ""));
     } else {
         return out->info(out, "%s node: %s (%s)", type ? type : "cluster",
                          pcmk__s(name, "<null>"), pcmk__s(id, "<null>"));
     }
 }
 
 PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *", "bool")
 static int
 crmadmin_node_text(pcmk__output_t *out, va_list args)
 {
     if (!out->is_quiet(out)) {
         return crmadmin_node(out, args);
     } else {
         const char *type G_GNUC_UNUSED = va_arg(args, const char *);
         const char *name = va_arg(args, const char *);
         const char *id G_GNUC_UNUSED = va_arg(args, const char *);
         bool bash_export G_GNUC_UNUSED = va_arg(args, int);
 
         pcmk__formatted_printf(out, "%s\n", pcmk__s(name, "<null>"));
         return pcmk_rc_ok;
     }
 }
 
 PCMK__OUTPUT_ARGS("crmadmin-node", "const char *", "const char *", "const char *", "bool")
 static int
 crmadmin_node_xml(pcmk__output_t *out, va_list args)
 {
     const char *type = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *id = va_arg(args, const char *);
     bool bash_export G_GNUC_UNUSED = va_arg(args, int);
 
     pcmk__output_create_xml_node(out, "node",
                                  "type", type ? type : "cluster",
                                  "name", pcmk__s(name, ""),
                                  "id", pcmk__s(id, ""),
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *",
                   "guint", "op_digest_cache_t *")
 static int
 digests_text(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *task = va_arg(args, const char *);
     guint interval_ms = va_arg(args, guint);
     op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *);
 
     char *action_desc = NULL;
     const char *rsc_desc = "unknown resource";
     const char *node_desc = "unknown node";
 
     if (interval_ms != 0) {
         action_desc = crm_strdup_printf("%ums-interval %s action", interval_ms,
                                         ((task == NULL)? "unknown" : task));
     } else if (pcmk__str_eq(task, "monitor", pcmk__str_none)) {
         action_desc = strdup("probe action");
     } else {
         action_desc = crm_strdup_printf("%s action",
                                         ((task == NULL)? "unknown" : task));
     }
     if ((rsc != NULL) && (rsc->id != NULL)) {
         rsc_desc = rsc->id;
     }
     if ((node != NULL) && (node->details->uname != NULL)) {
         node_desc = node->details->uname;
     }
     out->begin_list(out, NULL, NULL, "Digests for %s %s on %s",
                     rsc_desc, action_desc, node_desc);
     free(action_desc);
 
     if (digests == NULL) {
         out->list_item(out, NULL, "none");
         out->end_list(out);
         return pcmk_rc_ok;
     }
     if (digests->digest_all_calc != NULL) {
         out->list_item(out, NULL, "%s (all parameters)",
                        digests->digest_all_calc);
     }
     if (digests->digest_secure_calc != NULL) {
         out->list_item(out, NULL, "%s (non-private parameters)",
                        digests->digest_secure_calc);
     }
     if (digests->digest_restart_calc != NULL) {
         out->list_item(out, NULL, "%s (non-reloadable parameters)",
                        digests->digest_restart_calc);
     }
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 static void
 add_digest_xml(xmlNode *parent, const char *type, const char *digest,
                xmlNode *digest_source)
 {
     if (digest != NULL) {
         xmlNodePtr digest_xml = create_xml_node(parent, "digest");
 
         crm_xml_add(digest_xml, "type", ((type == NULL)? "unspecified" : type));
         crm_xml_add(digest_xml, "hash", digest);
         if (digest_source != NULL) {
             add_node_copy(digest_xml, digest_source);
         }
     }
 }
 
 PCMK__OUTPUT_ARGS("digests", "pe_resource_t *", "pe_node_t *", "const char *",
                   "guint", "op_digest_cache_t *")
 static int
 digests_xml(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *task = va_arg(args, const char *);
     guint interval_ms = va_arg(args, guint);
     op_digest_cache_t *digests = va_arg(args, op_digest_cache_t *);
 
     char *interval_s = crm_strdup_printf("%ums", interval_ms);
     xmlNode *xml = NULL;
 
     xml = pcmk__output_create_xml_node(out, "digests",
                                        "resource", pcmk__s(rsc->id, ""),
                                        "node", pcmk__s(node->details->uname, ""),
                                        "task", pcmk__s(task, ""),
                                        "interval", interval_s,
                                        NULL);
     free(interval_s);
     if (digests != NULL) {
         add_digest_xml(xml, "all", digests->digest_all_calc,
                        digests->params_all);
         add_digest_xml(xml, "nonprivate", digests->digest_secure_calc,
                        digests->params_secure);
         add_digest_xml(xml, "nonreloadable", digests->digest_restart_calc,
                        digests->params_restart);
     }
     return pcmk_rc_ok;
 }
 
 #define STOP_SANITY_ASSERT(lineno) do {                                 \
         if(current && current->details->unclean) {                      \
             /* It will be a pseudo op */                                \
         } else if(stop == NULL) {                                       \
             crm_err("%s:%d: No stop action exists for %s",              \
                     __func__, lineno, rsc->id);                         \
             CRM_ASSERT(stop != NULL);                                   \
         } else if (pcmk_is_set(stop->flags, pe_action_optional)) {      \
             crm_err("%s:%d: Action %s is still optional",               \
                     __func__, lineno, stop->uuid);                      \
             CRM_ASSERT(!pcmk_is_set(stop->flags, pe_action_optional));  \
         }                                                               \
     } while(0)
 
 PCMK__OUTPUT_ARGS("rsc-action", "pe_resource_t *", "pe_node_t *", "pe_node_t *")
 static int
 rsc_action_default(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *current = va_arg(args, pe_node_t *);
     pe_node_t *next = va_arg(args, pe_node_t *);
 
     GList *possible_matches = NULL;
     char *key = NULL;
     int rc = pcmk_rc_no_output;
     bool moving = false;
 
     pe_node_t *start_node = NULL;
     pe_action_t *start = NULL;
     pe_action_t *stop = NULL;
     pe_action_t *promote = NULL;
     pe_action_t *demote = NULL;
 
     if (!pcmk_is_set(rsc->flags, pe_rsc_managed)
         || (current == NULL && next == NULL)) {
         pe_rsc_info(rsc, "Leave   %s\t(%s%s)",
                     rsc->id, role2text(rsc->role),
                     !pcmk_is_set(rsc->flags, pe_rsc_managed)? " unmanaged" : "");
         return rc;
     }
 
     moving = (current != NULL) && (next != NULL)
              && (current->details != next->details);
 
     possible_matches = pe__resource_actions(rsc, next, RSC_START, false);
     if (possible_matches) {
         start = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     if ((start == NULL) || !pcmk_is_set(start->flags, pe_action_runnable)) {
         start_node = NULL;
     } else {
         start_node = current;
     }
     possible_matches = pe__resource_actions(rsc, start_node, RSC_STOP, false);
     if (possible_matches) {
         stop = possible_matches->data;
         g_list_free(possible_matches);
     } else if (pcmk_is_set(rsc->flags, pe_rsc_stop_unexpected)) {
         /* The resource is multiply active with multiple-active set to
          * stop_unexpected, and not stopping on its current node, but it should
          * be stopping elsewhere.
          */
         possible_matches = pe__resource_actions(rsc, NULL, RSC_STOP, false);
         if (possible_matches != NULL) {
             stop = possible_matches->data;
             g_list_free(possible_matches);
         }
     }
 
     possible_matches = pe__resource_actions(rsc, next, RSC_PROMOTE, false);
     if (possible_matches) {
         promote = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     possible_matches = pe__resource_actions(rsc, next, RSC_DEMOTE, false);
     if (possible_matches) {
         demote = possible_matches->data;
         g_list_free(possible_matches);
     }
 
     if (rsc->role == rsc->next_role) {
         pe_action_t *migrate_op = NULL;
 
         CRM_CHECK(next != NULL, return rc);
 
         possible_matches = pe__resource_actions(rsc, next, RSC_MIGRATED, false);
         if (possible_matches) {
             migrate_op = possible_matches->data;
         }
 
         if ((migrate_op != NULL) && (current != NULL)
                    && pcmk_is_set(migrate_op->flags, pe_action_runnable)) {
             rc = out->message(out, "rsc-action-item", "Migrate", rsc, current,
                               next, start, NULL);
 
         } else if (pcmk_is_set(rsc->flags, pe_rsc_reload)) {
             rc = out->message(out, "rsc-action-item", "Reload", rsc, current,
                               next, start, NULL);
 
         } else if (start == NULL || pcmk_is_set(start->flags, pe_action_optional)) {
             if ((demote != NULL) && (promote != NULL)
                 && !pcmk_is_set(demote->flags, pe_action_optional)
                 && !pcmk_is_set(promote->flags, pe_action_optional)) {
                 rc = out->message(out, "rsc-action-item", "Re-promote", rsc,
                                   current, next, promote, demote);
             } else {
                 pe_rsc_info(rsc, "Leave   %s\t(%s %s)", rsc->id,
                             role2text(rsc->role), pe__node_name(next));
             }
 
         } else if (!pcmk_is_set(start->flags, pe_action_runnable)) {
             rc = out->message(out, "rsc-action-item", "Stop", rsc, current,
                               NULL, stop, (stop && stop->reason)? stop : start);
             STOP_SANITY_ASSERT(__LINE__);
 
         } else if (moving && current) {
             rc = out->message(out, "rsc-action-item", pcmk_is_set(rsc->flags, pe_rsc_failed)? "Recover" : "Move",
                               rsc, current, next, stop, NULL);
 
         } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
             rc = out->message(out, "rsc-action-item", "Recover", rsc, current,
                               NULL, stop, NULL);
             STOP_SANITY_ASSERT(__LINE__);
 
         } else {
             rc = out->message(out, "rsc-action-item", "Restart", rsc, current,
                               next, start, NULL);
             /* STOP_SANITY_ASSERT(__LINE__); False positive for migrate-fail-7 */
         }
 
         g_list_free(possible_matches);
         return rc;
     }
 
     if(stop
        && (rsc->next_role == RSC_ROLE_STOPPED
            || (start && !pcmk_is_set(start->flags, pe_action_runnable)))) {
 
         GList *gIter = NULL;
 
         key = stop_key(rsc);
         for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) {
             pe_node_t *node = (pe_node_t *) gIter->data;
             pe_action_t *stop_op = NULL;
 
             possible_matches = find_actions(rsc->actions, key, node);
             if (possible_matches) {
                 stop_op = possible_matches->data;
                 g_list_free(possible_matches);
             }
 
             if (stop_op && (stop_op->flags & pe_action_runnable)) {
                 STOP_SANITY_ASSERT(__LINE__);
             }
 
             if (out->message(out, "rsc-action-item", "Stop", rsc, node, NULL,
                              stop_op, (stop_op && stop_op->reason)? stop_op : start) == pcmk_rc_ok) {
                 rc = pcmk_rc_ok;
             }
         }
 
         free(key);
 
     } else if ((stop != NULL)
                && pcmk_all_flags_set(rsc->flags, pe_rsc_failed|pe_rsc_stop)) {
         /* 'stop' may be NULL if the failure was ignored */
         rc = out->message(out, "rsc-action-item", "Recover", rsc, current,
                           next, stop, start);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (moving) {
         rc = out->message(out, "rsc-action-item", "Move", rsc, current, next,
                           stop, NULL);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (pcmk_is_set(rsc->flags, pe_rsc_reload)) {
         rc = out->message(out, "rsc-action-item", "Reload", rsc, current, next,
                           start, NULL);
 
     } else if (stop != NULL && !pcmk_is_set(stop->flags, pe_action_optional)) {
         rc = out->message(out, "rsc-action-item", "Restart", rsc, current,
                           next, start, NULL);
         STOP_SANITY_ASSERT(__LINE__);
 
     } else if (rsc->role == RSC_ROLE_PROMOTED) {
         CRM_LOG_ASSERT(current != NULL);
         rc = out->message(out, "rsc-action-item", "Demote", rsc, current,
                           next, demote, NULL);
 
     } else if (rsc->next_role == RSC_ROLE_PROMOTED) {
         CRM_LOG_ASSERT(next);
         rc = out->message(out, "rsc-action-item", "Promote", rsc, current,
                           next, promote, NULL);
 
     } else if (rsc->role == RSC_ROLE_STOPPED && rsc->next_role > RSC_ROLE_STOPPED) {
         rc = out->message(out, "rsc-action-item", "Start", rsc, current, next,
                           start, NULL);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-action", "char *", "char *", "char *")
 static int
 node_action(pcmk__output_t *out, va_list args)
 {
     char *task = va_arg(args, char *);
     char *node_name = va_arg(args, char *);
     char *reason = va_arg(args, char *);
 
     if (task == NULL) {
         return pcmk_rc_no_output;
     } else if (reason) {
         out->list_item(out, NULL, "%s %s '%s'", task, node_name, reason);
     } else {
         crm_notice(" * %s %s", task, node_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-action", "char *", "char *", "char *")
 static int
 node_action_xml(pcmk__output_t *out, va_list args)
 {
     char *task = va_arg(args, char *);
     char *node_name = va_arg(args, char *);
     char *reason = va_arg(args, char *);
 
     if (task == NULL) {
         return pcmk_rc_no_output;
     } else if (reason) {
         pcmk__output_create_xml_node(out, "node_action",
                                      "task", task,
                                      "node", node_name,
                                      "reason", reason,
                                      NULL);
     } else {
         crm_notice(" * %s %s", task, node_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-cluster-action", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_cluster_action(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
     xmlNodePtr rsc = va_arg(args, xmlNodePtr);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if(rsc) {
         out->list_item(out, NULL, "Cluster action:  %s for %s on %s", task, ID(rsc), node);
     } else {
         out->list_item(out, NULL, "Cluster action:  %s on %s", task, node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-cluster-action", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_cluster_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
     xmlNodePtr rsc = va_arg(args, xmlNodePtr);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "cluster_action",
                                             "task", task,
                                             "node", node,
                                             NULL);
 
     if (rsc) {
         crm_xml_add(xml_node, "id", ID(rsc));
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-fencing-action", "char *", "const char *")
 static int
 inject_fencing_action(pcmk__output_t *out, va_list args)
 {
     char *target = va_arg(args, char *);
     const char *op = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Fencing %s (%s)", target, op);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-fencing-action", "char *", "const char *")
 static int
 inject_fencing_action_xml(pcmk__output_t *out, va_list args)
 {
     char *target = va_arg(args, char *);
     const char *op = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "fencing_action",
                                  "target", target,
                                  "op", op,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-attr", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_attr(pcmk__output_t *out, va_list args)
 {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     xmlNodePtr cib_node = va_arg(args, xmlNodePtr);
 
     xmlChar *node_path = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node_path = xmlGetNodePath(cib_node);
 
     out->list_item(out, NULL, "Injecting attribute %s=%s into %s '%s'",
                    name, value, node_path, ID(cib_node));
 
     free(node_path);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-attr", "const char *", "const char *", "xmlNodePtr")
 static int
 inject_attr_xml(pcmk__output_t *out, va_list args)
 {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     xmlNodePtr cib_node = va_arg(args, xmlNodePtr);
 
     xmlChar *node_path = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node_path = xmlGetNodePath(cib_node);
 
     pcmk__output_create_xml_node(out, "inject_attr",
                                  "name", name,
                                  "value", value,
                                  "node_path", node_path,
                                  "cib_node", ID(cib_node),
                                  NULL);
     free(node_path);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-spec", "const char *")
 static int
 inject_spec(pcmk__output_t *out, va_list args)
 {
     const char *spec = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Injecting %s into the configuration", spec);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-spec", "const char *")
 static int
 inject_spec_xml(pcmk__output_t *out, va_list args)
 {
     const char *spec = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "inject_spec",
                                  "spec", spec,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-config", "char *", "char *")
 static int
 inject_modify_config(pcmk__output_t *out, va_list args)
 {
     char *quorum = va_arg(args, char *);
     char *watchdog = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->begin_list(out, NULL, NULL, "Performing Requested Modifications");
 
     if (quorum) {
         out->list_item(out, NULL, "Setting quorum: %s", quorum);
     }
 
     if (watchdog) {
         out->list_item(out, NULL, "Setting watchdog: %s", watchdog);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-config", "char *", "char *")
 static int
 inject_modify_config_xml(pcmk__output_t *out, va_list args)
 {
     char *quorum = va_arg(args, char *);
     char *watchdog = va_arg(args, char *);
 
     xmlNodePtr node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     node = pcmk__output_xml_create_parent(out, "modifications", NULL);
 
     if (quorum) {
         crm_xml_add(node, "quorum", quorum);
     }
 
     if (watchdog) {
         crm_xml_add(node, "watchdog", watchdog);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-node", "const char *", "char *")
 static int
 inject_modify_node(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *node = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (pcmk__str_eq(action, "Online", pcmk__str_none)) {
         out->list_item(out, NULL, "Bringing node %s online", node);
         return pcmk_rc_ok;
     } else if (pcmk__str_eq(action, "Offline", pcmk__str_none)) {
         out->list_item(out, NULL, "Taking node %s offline", node);
         return pcmk_rc_ok;
     } else if (pcmk__str_eq(action, "Failing", pcmk__str_none)) {
         out->list_item(out, NULL, "Failing node %s", node);
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-node", "const char *", "char *")
 static int
 inject_modify_node_xml(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *node = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "modify_node",
                                  "action", action,
                                  "node", node,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-ticket", "const char *", "char *")
 static int
 inject_modify_ticket(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *ticket = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (pcmk__str_eq(action, "Standby", pcmk__str_none)) {
         out->list_item(out, NULL, "Making ticket %s standby", ticket);
     } else {
         out->list_item(out, NULL, "%s ticket %s", action, ticket);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-modify-ticket", "const char *", "char *")
 static int
 inject_modify_ticket_xml(pcmk__output_t *out, va_list args)
 {
     const char *action = va_arg(args, const char *);
     char *ticket = va_arg(args, char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     pcmk__output_create_xml_node(out, "modify_ticket",
                                  "action", action,
                                  "ticket", ticket,
                                  NULL);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-pseudo-action", "const char *", "const char *")
 static int
 inject_pseudo_action(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     out->list_item(out, NULL, "Pseudo action:   %s%s%s", task, node ? " on " : "",
                    node ? node : "");
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-pseudo-action", "const char *", "const char *")
 static int
 inject_pseudo_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *node = va_arg(args, const char *);
     const char *task = va_arg(args, const char *);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "pseudo_action",
                                             "task", task,
                                             NULL);
     if (node) {
         crm_xml_add(xml_node, "node", node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-rsc-action", "const char *", "const char *", "char *", "guint")
 static int
 inject_rsc_action(pcmk__output_t *out, va_list args)
 {
     const char *rsc = va_arg(args, const char *);
     const char *operation = va_arg(args, const char *);
     char *node = va_arg(args, char *);
     guint interval_ms = va_arg(args, guint);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     if (interval_ms) {
         out->list_item(out, NULL, "Resource action: %-15s %s=%u on %s",
                        rsc, operation, interval_ms, node);
     } else {
         out->list_item(out, NULL, "Resource action: %-15s %s on %s",
                        rsc, operation, node);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("inject-rsc-action", "const char *", "const char *", "char *", "guint")
 static int
 inject_rsc_action_xml(pcmk__output_t *out, va_list args)
 {
     const char *rsc = va_arg(args, const char *);
     const char *operation = va_arg(args, const char *);
     char *node = va_arg(args, char *);
     guint interval_ms = va_arg(args, guint);
 
     xmlNodePtr xml_node = NULL;
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     xml_node = pcmk__output_create_xml_node(out, "rsc_action",
                                             "resource", rsc,
                                             "op", operation,
                                             "node", node,
                                             NULL);
 
     if (interval_ms) {
         char *interval_s = pcmk__itoa(interval_ms);
 
         crm_xml_add(xml_node, "interval", interval_s);
         free(interval_s);
     }
 
     return pcmk_rc_ok;
 }
 
 #define CHECK_RC(retcode, retval)   \
     if (retval == pcmk_rc_ok) {     \
         retcode = pcmk_rc_ok;       \
     }
 
-PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
-                  "enum pcmk__fence_history", "uint32_t", "uint32_t", "const char *", "GList *",
-                  "GList *")
+PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "crm_exit_t",
+                  "stonith_history_t *", "enum pcmk__fence_history", "uint32_t",
+                  "uint32_t", "const char *", "GList *", "GList *")
 int
 pcmk__cluster_status_text(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     enum pcmk__fence_history fence_history = va_arg(args, int);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
 
     int rc = pcmk_rc_no_output;
     bool already_printed_failure = false;
 
-    CHECK_RC(rc, out->message(out, "cluster-summary", data_set,
+    CHECK_RC(rc, out->message(out, "cluster-summary", data_set, pcmkd_state,
                               section_opts, show_opts));
 
     if (pcmk_is_set(section_opts, pcmk_section_nodes) && unames) {
         CHECK_RC(rc, out->message(out, "node-list", data_set->nodes, unames,
                                   resources, show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         CHECK_RC(rc, out->message(out, "resource-list", data_set, show_opts,
                                   true, unames, resources, rc == pcmk_rc_ok));
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         CHECK_RC(rc, out->message(out, "node-attribute-list", data_set,
                                   show_opts, rc == pcmk_rc_ok, unames, resources));
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         CHECK_RC(rc, out->message(out, "node-summary", data_set, unames,
                                   resources, section_opts, show_opts, rc == pcmk_rc_ok));
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         CHECK_RC(rc, out->message(out, "failed-action-list", data_set, unames,
                                   resources, show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print failed stonith actions */
     if (pcmk_is_set(section_opts, pcmk_section_fence_failed) &&
         fence_history != pcmk__fence_history_none) {
         if (history_rc == 0) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "failed-fencing-list",
                                           stonith_history, unames, section_opts,
                                           show_opts, rc == pcmk_rc_ok));
             }
         } else {
             PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
             out->list_item(out, NULL, "Failed to get fencing history: %s",
                            crm_exit_str(history_rc));
             out->end_list(out);
 
             already_printed_failure = true;
         }
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         CHECK_RC(rc, out->message(out, "ticket-list", data_set, rc == pcmk_rc_ok));
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         CHECK_RC(rc, out->message(out, "ban-list", data_set, prefix, resources,
                                   show_opts, rc == pcmk_rc_ok));
     }
 
     /* Print stonith history */
     if (pcmk_any_flags_set(section_opts, pcmk_section_fencing_all) &&
         fence_history != pcmk__fence_history_none) {
         if (history_rc != 0) {
             if (!already_printed_failure) {
                 PCMK__OUTPUT_SPACER_IF(out, rc == pcmk_rc_ok);
                 out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
                 out->list_item(out, NULL, "Failed to get fencing history: %s",
                                crm_exit_str(history_rc));
                 out->end_list(out);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_worked)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "fencing-list", hp, unames,
                                           section_opts, show_opts,
                                           rc == pcmk_rc_ok));
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_pending)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL);
 
             if (hp) {
                 CHECK_RC(rc, out->message(out, "pending-fencing-list", hp,
                                           unames, section_opts, show_opts,
                                           rc == pcmk_rc_ok));
             }
         }
     }
 
     return rc;
 }
 
-PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
-                  "enum pcmk__fence_history", "uint32_t", "uint32_t", "const char *", "GList *",
-                  "GList *")
+PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "crm_exit_t",
+                  "stonith_history_t *", "enum pcmk__fence_history", "uint32_t",
+                  "uint32_t", "const char *", "GList *", "GList *")
 static int
 cluster_status_xml(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     enum pcmk__fence_history fence_history = va_arg(args, int);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
 
-    out->message(out, "cluster-summary", data_set, section_opts, show_opts);
+    out->message(out, "cluster-summary", data_set, pcmkd_state, section_opts,
+                 show_opts);
 
     /*** NODES ***/
     if (pcmk_is_set(section_opts, pcmk_section_nodes)) {
         out->message(out, "node-list", data_set->nodes, unames, resources,
                      show_opts, false);
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         /* XML output always displays full details. */
         uint32_t full_show_opts = show_opts & ~pcmk_show_brief;
 
         out->message(out, "resource-list", data_set, full_show_opts,
                      false, unames, resources, false);
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         out->message(out, "node-attribute-list", data_set, show_opts, false,
                      unames, resources);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         out->message(out, "node-summary", data_set, unames,
                      resources, section_opts, show_opts, false);
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         out->message(out, "failed-action-list", data_set, unames, resources,
                      show_opts, false);
     }
 
     /* Print stonith history */
     if (pcmk_is_set(section_opts, pcmk_section_fencing_all) &&
         fence_history != pcmk__fence_history_none) {
         out->message(out, "full-fencing-list", history_rc, stonith_history,
                      unames, section_opts, show_opts, false);
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         out->message(out, "ticket-list", data_set, false);
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         out->message(out, "ban-list", data_set, prefix, resources, show_opts,
                      false);
     }
 
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *",
-                  "enum pcmk__fence_history", "uint32_t", "uint32_t", "const char *", "GList *",
-                  "GList *")
+PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "crm_exit_t",
+                  "stonith_history_t *", "enum pcmk__fence_history", "uint32_t",
+                  "uint32_t", "const char *", "GList *", "GList *")
 static int
 cluster_status_html(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
     crm_exit_t history_rc = va_arg(args, crm_exit_t);
     stonith_history_t *stonith_history = va_arg(args, stonith_history_t *);
     enum pcmk__fence_history fence_history = va_arg(args, int);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
     const char *prefix = va_arg(args, const char *);
     GList *unames = va_arg(args, GList *);
     GList *resources = va_arg(args, GList *);
     bool already_printed_failure = false;
 
-    out->message(out, "cluster-summary", data_set, section_opts, show_opts);
+    out->message(out, "cluster-summary", data_set, pcmkd_state, section_opts,
+                 show_opts);
 
     /*** NODE LIST ***/
     if (pcmk_is_set(section_opts, pcmk_section_nodes) && unames) {
         out->message(out, "node-list", data_set->nodes, unames, resources,
                      show_opts, false);
     }
 
     /* Print resources section, if needed */
     if (pcmk_is_set(section_opts, pcmk_section_resources)) {
         out->message(out, "resource-list", data_set, show_opts, true, unames,
                      resources, false);
     }
 
     /* print Node Attributes section if requested */
     if (pcmk_is_set(section_opts, pcmk_section_attributes)) {
         out->message(out, "node-attribute-list", data_set, show_opts, false,
                      unames, resources);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (pcmk_any_flags_set(section_opts, pcmk_section_operations | pcmk_section_failcounts)) {
         out->message(out, "node-summary", data_set, unames,
                      resources, section_opts, show_opts, false);
     }
 
     /* If there were any failed actions, print them */
     if (pcmk_is_set(section_opts, pcmk_section_failures)
         && xml_has_children(data_set->failed)) {
 
         out->message(out, "failed-action-list", data_set, unames, resources,
                      show_opts, false);
     }
 
     /* Print failed stonith actions */
     if (pcmk_is_set(section_opts, pcmk_section_fence_failed) &&
         fence_history != pcmk__fence_history_none) {
         if (history_rc == 0) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_eq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 out->message(out, "failed-fencing-list", stonith_history, unames,
                              section_opts, show_opts, false);
             }
         } else {
             out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
             out->list_item(out, NULL, "Failed to get fencing history: %s",
                            crm_exit_str(history_rc));
             out->end_list(out);
         }
     }
 
     /* Print stonith history */
     if (pcmk_any_flags_set(section_opts, pcmk_section_fencing_all) &&
         fence_history != pcmk__fence_history_none) {
         if (history_rc != 0) {
             if (!already_printed_failure) {
                 out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
                 out->list_item(out, NULL, "Failed to get fencing history: %s",
                                crm_exit_str(history_rc));
                 out->end_list(out);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_worked)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_neq,
                                                                   GINT_TO_POINTER(st_failed));
 
             if (hp) {
                 out->message(out, "fencing-list", hp, unames, section_opts,
                              show_opts, false);
             }
         } else if (pcmk_is_set(section_opts, pcmk_section_fence_pending)) {
             stonith_history_t *hp = stonith__first_matching_event(stonith_history, stonith__event_state_pending, NULL);
 
             if (hp) {
                 out->message(out, "pending-fencing-list", hp, unames,
                              section_opts, show_opts, false);
             }
         }
     }
 
     /* Print tickets if requested */
     if (pcmk_is_set(section_opts, pcmk_section_tickets)) {
         out->message(out, "ticket-list", data_set, false);
     }
 
     /* Print negative location constraints if requested */
     if (pcmk_is_set(section_opts, pcmk_section_bans)) {
         out->message(out, "ban-list", data_set, prefix, resources, show_opts,
                      false);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("attribute", "const char *", "const char *", "const char *",
                   "const char *", "const char *")
 static int
 attribute_default(pcmk__output_t *out, va_list args)
 {
     const char *scope = va_arg(args, const char *);
     const char *instance = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     const char *host = va_arg(args, const char *);
 
     GString *s = g_string_sized_new(50);
 
     if (!pcmk__str_empty(scope)) {
         pcmk__g_strcat(s, "scope=\"", scope, "\" ", NULL);
     }
 
     if (!pcmk__str_empty(instance)) {
         pcmk__g_strcat(s, "id=\"", instance, "\" ", NULL);
     }
 
     pcmk__g_strcat(s, "name=\"", pcmk__s(name, ""), "\" ", NULL);
 
     if (!pcmk__str_empty(host)) {
         pcmk__g_strcat(s, "host=\"", host, "\" ", NULL);
     }
 
     pcmk__g_strcat(s, "value=\"", pcmk__s(value, ""), "\"", NULL);
 
     out->info(out, "%s", s->str);
     g_string_free(s, TRUE);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("attribute", "const char *", "const char *", "const char *",
                   "const char *", "const char *")
 static int
 attribute_xml(pcmk__output_t *out, va_list args)
 {
     const char *scope = va_arg(args, const char *);
     const char *instance = va_arg(args, const char *);
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     const char *host = va_arg(args, const char *);
 
     xmlNodePtr node = NULL;
 
     node = pcmk__output_create_xml_node(out, "attribute",
                                         "name", name,
                                         "value", value ? value : "",
                                         NULL);
 
     if (!pcmk__str_empty(scope)) {
         crm_xml_add(node, "scope", scope);
     }
 
     if (!pcmk__str_empty(instance)) {
         crm_xml_add(node, "id", instance);
     }
 
     if (!pcmk__str_empty(host)) {
         crm_xml_add(node, "host", host);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("rule-check", "const char *", "int", "const char *")
 static int
 rule_check_default(pcmk__output_t *out, va_list args)
 {
     const char *rule_id = va_arg(args, const char *);
     int result = va_arg(args, int);
     const char *error = va_arg(args, const char *);
 
     switch (result) {
         case pcmk_rc_within_range:
             return out->info(out, "Rule %s is still in effect", rule_id);
         case pcmk_rc_ok:
             return out->info(out, "Rule %s satisfies conditions", rule_id);
         case pcmk_rc_after_range:
             return out->info(out, "Rule %s is expired", rule_id);
         case pcmk_rc_before_range:
             return out->info(out, "Rule %s has not yet taken effect", rule_id);
         case pcmk_rc_op_unsatisfied:
             return out->info(out, "Rule %s does not satisfy conditions",
                              rule_id);
         default:
             out->err(out,
                      "Could not determine whether rule %s is in effect: %s",
                      rule_id, ((error != NULL)? error : "unexpected error"));
             return pcmk_rc_ok;
     }
 }
 
 PCMK__OUTPUT_ARGS("rule-check", "const char *", "int", "const char *")
 static int
 rule_check_xml(pcmk__output_t *out, va_list args)
 {
     const char *rule_id = va_arg(args, const char *);
     int result = va_arg(args, int);
     const char *error = va_arg(args, const char *);
 
     char *rc_str = pcmk__itoa(pcmk_rc2exitc(result));
 
     pcmk__output_create_xml_node(out, "rule-check",
                                  "rule-id", rule_id,
                                  "rc", rc_str,
                                  NULL);
     free(rc_str);
 
     switch (result) {
         case pcmk_rc_within_range:
         case pcmk_rc_ok:
         case pcmk_rc_after_range:
         case pcmk_rc_before_range:
         case pcmk_rc_op_unsatisfied:
             return pcmk_rc_ok;
         default:
             out->err(out,
                     "Could not determine whether rule %s is in effect: %s",
                     rule_id, ((error != NULL)? error : "unexpected error"));
             return pcmk_rc_ok;
     }
 }
 
 PCMK__OUTPUT_ARGS("result-code", "int", "const char *", "const char *")
 static int
 result_code_none(pcmk__output_t *out, va_list args)
 {
     return pcmk_rc_no_output;
 }
 
 PCMK__OUTPUT_ARGS("result-code", "int", "const char *", "const char *")
 static int
 result_code_text(pcmk__output_t *out, va_list args)
 {
     int code = va_arg(args, int);
     const char *name = va_arg(args, const char *);
     const char *desc = va_arg(args, const char *);
 
     static int code_width = 0;
 
     if (out->is_quiet(out)) {
         /* If out->is_quiet(), don't print the code. Print name and/or desc in a
          * compact format for text output, or print nothing at all for none-type
          * output.
          */
         if ((name != NULL) && (desc != NULL)) {
             pcmk__formatted_printf(out, "%s - %s\n", name, desc);
 
         } else if ((name != NULL) || (desc != NULL)) {
             pcmk__formatted_printf(out, "%s\n", ((name != NULL)? name : desc));
         }
         return pcmk_rc_ok;
     }
 
     /* Get length of longest (most negative) standard Pacemaker return code
      * This should be longer than all the values of any other type of return
      * code.
      */
     if (code_width == 0) {
         long long most_negative = pcmk_rc_error - (long long) pcmk__n_rc + 1;
         code_width = (int) snprintf(NULL, 0, "%lld", most_negative);
     }
 
     if ((name != NULL) && (desc != NULL)) {
         static int name_width = 0;
 
         if (name_width == 0) {
             // Get length of longest standard Pacemaker return code name
             for (int lpc = 0; lpc < pcmk__n_rc; lpc++) {
                 int len = (int) strlen(pcmk_rc_name(pcmk_rc_error - lpc));
                 name_width = QB_MAX(name_width, len);
             }
         }
         return out->info(out, "% *d: %-*s  %s", code_width, code, name_width,
                          name, desc);
     }
 
     if ((name != NULL) || (desc != NULL)) {
         return out->info(out, "% *d: %s", code_width, code,
                          ((name != NULL)? name : desc));
     }
 
     return out->info(out, "% *d", code_width, code);
 }
 
 PCMK__OUTPUT_ARGS("result-code", "int", "const char *", "const char *")
 static int
 result_code_xml(pcmk__output_t *out, va_list args)
 {
     int code = va_arg(args, int);
     const char *name = va_arg(args, const char *);
     const char *desc = va_arg(args, const char *);
 
     char *code_str = pcmk__itoa(code);
 
     pcmk__output_create_xml_node(out, "result-code",
                                  "code", code_str,
                                  XML_ATTR_NAME, name,
                                  XML_ATTR_DESC, desc,
                                  NULL);
     free(code_str);
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "attribute", "default", attribute_default },
     { "attribute", "xml", attribute_xml },
     { "cluster-status", "default", pcmk__cluster_status_text },
     { "cluster-status", "html", cluster_status_html },
     { "cluster-status", "xml", cluster_status_xml },
     { "crmadmin-node", "default", crmadmin_node },
     { "crmadmin-node", "text", crmadmin_node_text },
     { "crmadmin-node", "xml", crmadmin_node_xml },
     { "dc", "default", dc },
     { "dc", "text", dc_text },
     { "dc", "xml", dc_xml },
     { "digests", "default", digests_text },
     { "digests", "xml", digests_xml },
     { "health", "default", health },
     { "health", "text", health_text },
     { "health", "xml", health_xml },
     { "inject-attr", "default", inject_attr },
     { "inject-attr", "xml", inject_attr_xml },
     { "inject-cluster-action", "default", inject_cluster_action },
     { "inject-cluster-action", "xml", inject_cluster_action_xml },
     { "inject-fencing-action", "default", inject_fencing_action },
     { "inject-fencing-action", "xml", inject_fencing_action_xml },
     { "inject-modify-config", "default", inject_modify_config },
     { "inject-modify-config", "xml", inject_modify_config_xml },
     { "inject-modify-node", "default", inject_modify_node },
     { "inject-modify-node", "xml", inject_modify_node_xml },
     { "inject-modify-ticket", "default", inject_modify_ticket },
     { "inject-modify-ticket", "xml", inject_modify_ticket_xml },
     { "inject-pseudo-action", "default", inject_pseudo_action },
     { "inject-pseudo-action", "xml", inject_pseudo_action_xml },
     { "inject-rsc-action", "default", inject_rsc_action },
     { "inject-rsc-action", "xml", inject_rsc_action_xml },
     { "inject-spec", "default", inject_spec },
     { "inject-spec", "xml", inject_spec_xml },
     { "locations-list", "default", locations_list },
     { "locations-list", "xml", locations_list_xml },
     { "node-action", "default", node_action },
     { "node-action", "xml", node_action_xml },
     { "pacemakerd-health", "default", pacemakerd_health },
     { "pacemakerd-health", "html", pacemakerd_health_html },
     { "pacemakerd-health", "text", pacemakerd_health_text },
     { "pacemakerd-health", "xml", pacemakerd_health_xml },
     { "profile", "default", profile_default, },
     { "profile", "xml", profile_xml },
     { "result-code", "none", result_code_none },
     { "result-code", "text", result_code_text },
     { "result-code", "xml", result_code_xml },
     { "rsc-action", "default", rsc_action_default },
     { "rsc-action-item", "default", rsc_action_item },
     { "rsc-action-item", "xml", rsc_action_item_xml },
     { "rsc-is-colocated-with-list", "default", rsc_is_colocated_with_list },
     { "rsc-is-colocated-with-list", "xml", rsc_is_colocated_with_list_xml },
     { "rscs-colocated-with-list", "default", rscs_colocated_with_list },
     { "rscs-colocated-with-list", "xml", rscs_colocated_with_list_xml },
     { "rule-check", "default", rule_check_default },
     { "rule-check", "xml", rule_check_xml },
     { "stacks-constraints", "default", stacks_and_constraints },
     { "stacks-constraints", "xml", stacks_and_constraints_xml },
 
     { NULL, NULL, NULL }
 };
 
 void
 pcmk__register_lib_messages(pcmk__output_t *out) {
     pcmk__register_messages(out, fmt_functions);
 }
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 894b619dc4..c21c4c94d6 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,984 +1,985 @@
 /*
  * Copyright 2021-2022 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 <crm/cib/internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 #include <crm/pengine/pe_types.h>
 #include <pacemaker-internal.h>
 #include <pacemaker.h>
 
 #include <stdint.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include "libpacemaker_private.h"
 
 static pcmk__output_t *out = NULL;
 static cib_t *fake_cib = NULL;
 static GList *fake_resource_list = NULL;
 static GList *fake_op_fail_list = NULL;
 
 static void set_effective_date(pe_working_set_t *data_set, bool print_original,
                                char *use_date);
 
 /*!
  * \internal
  * \brief Create an action name for use in a dot graph
  *
  * \param[in] action   Action to create name for
  * \param[in] verbose  If true, add action ID to name
  *
  * \return Newly allocated string with action name
  * \note It is the caller's responsibility to free the result.
  */
 static char *
 create_action_name(pe_action_t *action, bool verbose)
 {
     char *action_name = NULL;
     const char *prefix = "";
     const char *action_host = NULL;
     const char *clone_name = NULL;
     const char *task = action->task;
 
     if (action->node != NULL) {
         action_host = action->node->details->uname;
     } else if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
         action_host = "<none>";
     }
 
     if (pcmk__str_eq(action->task, RSC_CANCEL, pcmk__str_none)) {
         prefix = "Cancel ";
         task = action->cancel_task;
     }
 
     if (action->rsc != NULL) {
         clone_name = action->rsc->clone_name;
     }
 
     if (clone_name != NULL) {
         char *key = NULL;
         guint interval_ms = 0;
 
         if (pcmk__guint_from_hash(action->meta,
                                   XML_LRM_ATTR_INTERVAL_MS, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
 
         if (pcmk__strcase_any_of(action->task, RSC_NOTIFY, RSC_NOTIFIED,
                                  NULL)) {
             const char *n_type = g_hash_table_lookup(action->meta,
                                                      "notify_key_type");
             const char *n_task = g_hash_table_lookup(action->meta,
                                                      "notify_key_operation");
 
             CRM_ASSERT(n_type != NULL);
             CRM_ASSERT(n_task != NULL);
             key = pcmk__notify_key(clone_name, n_type, n_task);
         } else {
             key = pcmk__op_key(clone_name, task, interval_ms);
         }
 
         if (action_host != NULL) {
             action_name = crm_strdup_printf("%s%s %s",
                                             prefix, key, action_host);
         } else {
             action_name = crm_strdup_printf("%s%s", prefix, key);
         }
         free(key);
 
     } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
         const char *op = g_hash_table_lookup(action->meta, "stonith_action");
 
         action_name = crm_strdup_printf("%s%s '%s' %s",
                                         prefix, action->task, op, action_host);
 
     } else if (action->rsc && action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->uuid, action_host);
 
     } else if (action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->task, action_host);
 
     } else {
         action_name = crm_strdup_printf("%s", action->uuid);
     }
 
     if (verbose) {
         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 
         free(action_name);
         action_name = with_id;
     }
     return action_name;
 }
 
 /*!
  * \internal
  * \brief Display the status of a cluster
  *
  * \param[in] data_set      Cluster working set
  * \param[in] show_opts     How to modify display (as pcmk_show_opt_e flags)
  * \param[in] section_opts  Sections to display (as pcmk_section_e flags)
  * \param[in] title         What to use as list title
  * \param[in] print_spacer  Whether to display a spacer first
  */
 static void
 print_cluster_status(pe_working_set_t *data_set, uint32_t show_opts,
                      uint32_t section_opts, const char *title, bool print_spacer)
 {
     pcmk__output_t *out = data_set->priv;
     GList *all = NULL;
     crm_exit_t stonith_rc = 0;
 
     section_opts |= pcmk_section_nodes | pcmk_section_resources;
     show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
 
     all = g_list_prepend(all, (gpointer) "*");
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "%s", title);
-    out->message(out, "cluster-status", data_set, stonith_rc, NULL, false,
-                 section_opts, show_opts, NULL, all, all);
+    out->message(out, "cluster-status",
+                 data_set, pcmk_pacemakerd_state_invalid, stonith_rc, NULL,
+                 false, section_opts, show_opts, NULL, all, all);
     out->end_list(out);
 
     g_list_free(all);
 }
 
 /*!
  * \internal
  * \brief Display a summary of all actions scheduled in a transition
  *
  * \param[in] data_set      Cluster working set (fully scheduled)
  * \param[in] print_spacer  Whether to display a spacer first
  */
 static void
 print_transition_summary(pe_working_set_t *data_set, bool print_spacer)
 {
     pcmk__output_t *out = data_set->priv;
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "Transition Summary");
     pcmk__output_actions(data_set);
     out->end_list(out);
 }
 
 /*!
  * \internal
  * \brief Reset a cluster working set's input, output, date, and flags
  *
  * \param[in] data_set  Cluster working set
  * \param[in] input     What to set as cluster input
  * \param[in] out       What to set as cluster output object
  * \param[in] use_date  What to set as cluster's current timestamp
  * \param[in] flags     Cluster flags to add (pe_flag_*)
  */
 static void
 reset(pe_working_set_t *data_set, xmlNodePtr input, pcmk__output_t *out,
       char *use_date, unsigned int flags)
 {
     data_set->input = input;
     data_set->priv = out;
     set_effective_date(data_set, true, use_date);
     if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
         pe__set_working_set_flags(data_set, pe_flag_sanitized);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pe__set_working_set_flags(data_set, pe_flag_show_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
     }
 }
 
 /*!
  * \brief Write out a file in dot(1) format describing the actions that will
  *        be taken by the scheduler in response to an input CIB file.
  *
  * \param[in] data_set     Working set for the cluster
  * \param[in] dot_file     The filename to write
  * \param[in] all_actions  Write all actions, even those that are optional or
  *                         are on unmanaged resources
  * \param[in] verbose      Add extra information, such as action IDs, to the
  *                         output
  *
  * \return Standard Pacemaker return code
  */
 static int
 write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
                   bool all_actions, bool verbose)
 {
     GList *gIter = NULL;
     FILE *dot_strm = fopen(dot_file, "w");
 
     if (dot_strm == NULL) {
         return errno;
     }
 
     fprintf(dot_strm, " digraph \"g\" {\n");
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
         const char *style = "dashed";
         const char *font = "black";
         const char *color = "black";
         char *action_name = create_action_name(action, verbose);
 
         if (pcmk_is_set(action->flags, pe_action_pseudo)) {
             font = "orange";
         }
 
         if (pcmk_is_set(action->flags, pe_action_dumped)) {
             style = "bold";
             color = "green";
 
         } else if ((action->rsc != NULL)
                    && !pcmk_is_set(action->rsc->flags, pe_rsc_managed)) {
             color = "red";
             font = "purple";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else if (pcmk_is_set(action->flags, pe_action_optional)) {
             color = "blue";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else {
             color = "red";
             CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pe_action_runnable));
         }
 
         pe__set_action_flags(action, pe_action_dumped);
         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
                 action_name, style, color, font);
   do_not_write:
         free(action_name);
     }
 
     for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
         pe_action_t *action = (pe_action_t *) gIter->data;
 
         GList *gIter2 = NULL;
 
         for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
             pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data;
 
             char *before_name = NULL;
             char *after_name = NULL;
             const char *style = "dashed";
             bool optional = true;
 
             if (before->state == pe_link_dumped) {
                 optional = false;
                 style = "bold";
             } else if (before->type == pe_order_none) {
                 continue;
             } else if (pcmk_is_set(before->action->flags, pe_action_dumped)
                        && pcmk_is_set(action->flags, pe_action_dumped)
                        && before->type != pe_order_load) {
                 optional = false;
             }
 
             if (all_actions || !optional) {
                 before_name = create_action_name(before->action, verbose);
                 after_name = create_action_name(action, verbose);
                 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
                         before_name, after_name, style);
                 free(before_name);
                 free(after_name);
             }
         }
     }
 
     fprintf(dot_strm, "}\n");
     fflush(dot_strm);
     fclose(dot_strm);
     return pcmk_rc_ok;
 }
 
 /*!
  * \brief Profile the configuration updates and scheduler actions in a single
  *        CIB file, printing the profiling timings.
  *
  * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in] xml_file  The CIB file to profile
  * \param[in] repeat    Number of times to run
  * \param[in] data_set  Working set for the cluster
  * \param[in] use_date  The date to set the cluster's time to (may be NULL)
  */
 static void
 profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set,
              char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     xmlNode *cib_object = NULL;
     clock_t start = 0;
     clock_t end;
     unsigned long long data_set_flags = pe_flag_no_compat;
 
     CRM_ASSERT(out != NULL);
 
     cib_object = filename2xml(xml_file);
     start = clock();
 
     if (pcmk_find_cib_element(cib_object, XML_CIB_TAG_STATUS) == NULL) {
         create_xml_node(cib_object, XML_CIB_TAG_STATUS);
     }
 
     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
         free_xml(cib_object);
         return;
     }
 
     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
         free_xml(cib_object);
         return;
     }
 
     if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
         data_set_flags |= pe_flag_show_scores;
     }
     if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
         data_set_flags |= pe_flag_show_utilization;
     }
 
     for (int i = 0; i < repeat; ++i) {
         xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
 
         data_set->input = input;
         set_effective_date(data_set, false, use_date);
         pcmk__schedule_actions(input, data_set_flags, data_set);
         pe_reset_working_set(data_set);
     }
 
     end = clock();
     out->message(out, "profile", xml_file, start, end);
 }
 
 void
 pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     struct dirent **namelist;
 
     int file_num = scandir(dir, &namelist, 0, alphasort);
 
     CRM_ASSERT(out != NULL);
 
     if (file_num > 0) {
         struct stat prop;
         char buffer[FILENAME_MAX];
 
         out->begin_list(out, NULL, NULL, "Timings");
 
         while (file_num--) {
             if ('.' == namelist[file_num]->d_name[0]) {
                 free(namelist[file_num]);
                 continue;
 
             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
                                             ".xml")) {
                 free(namelist[file_num]);
                 continue;
             }
             snprintf(buffer, sizeof(buffer), "%s/%s", dir, namelist[file_num]->d_name);
             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
                 profile_file(buffer, repeat, data_set, use_date);
             }
             free(namelist[file_num]);
         }
         free(namelist);
 
         out->end_list(out);
     }
 }
 
 /*!
  * \brief Set the date of the cluster, either to the value given by
  *        \p use_date, or to the "execution-date" value in the CIB.
  *
  * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in,out] data_set        Working set for the cluster
  * \param[in]     print_original  If \p true, the "execution-date" should
  *                                also be printed
  * \param[in]     use_date        The date to set the cluster's time to
  *                                (may be NULL)
  */
 static void
 set_effective_date(pe_working_set_t *data_set, bool print_original,
                    char *use_date)
 {
     pcmk__output_t *out = data_set->priv;
     time_t original_date = 0;
 
     CRM_ASSERT(out != NULL);
 
     crm_element_value_epoch(data_set->input, "execution-date", &original_date);
 
     if (use_date) {
         data_set->now = crm_time_new(use_date);
         out->info(out, "Setting effective cluster time: %s", use_date);
         crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now,
                      crm_time_log_date | crm_time_log_timeofday);
 
     } else if (original_date != 0) {
         data_set->now = pcmk__copy_timet(original_date);
 
         if (print_original) {
             char *when = crm_time_as_string(data_set->now,
                             crm_time_log_date|crm_time_log_timeofday);
 
             out->info(out, "Using the original execution date of: %s", when);
             free(when);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a pseudo-action in a graph
  *
  * \param[in] graph   Graph to update with pseudo-action result
  * \param[in] action  Pseudo-action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
     const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-pseudo-action", node, task);
 
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate executing a resource action in a graph
  *
  * \param[in] graph   Graph to update with resource action result
  * \param[in] action  Resource action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     int rc;
     lrmd_event_data_t *op = NULL;
     int target_outcome = PCMK_OCF_OK;
 
     const char *rtype = NULL;
     const char *rclass = NULL;
     const char *resource = NULL;
     const char *rprovider = NULL;
     const char *resource_config_name = NULL;
     const char *operation = crm_element_value(action->xml, "operation");
     const char *target_rc_s = crm_meta_value(action->params,
                                              XML_ATTR_TE_TARGET_RC);
 
     xmlNode *cib_node = NULL;
     xmlNode *cib_resource = NULL;
     xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
 
     char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
     char *uuid = NULL;
     const char *router_node = crm_element_value(action->xml,
                                                 XML_LRM_ATTR_ROUTER_NODE);
 
     // Certain actions don't need to be displayed or history entries
     if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
         crm_debug("No history injection for %s op on %s", operation, node);
         goto done; // Confirm action and update graph
     }
 
     if (action_rsc == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "Bad");
         free(node);
         return EPROTO;
     }
 
     /* A resource might be known by different names in the configuration and in
      * the action (for example, a clone instance). Grab the configuration name
      * (which is preferred when writing history), and if necessary, the instance
      * name.
      */
     resource_config_name = crm_element_value(action_rsc, XML_ATTR_ID);
     if (resource_config_name == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "No ID");
         free(node);
         return EPROTO;
     }
     resource = resource_config_name;
     if (pe_find_resource(fake_resource_list, resource) == NULL) {
         const char *longname = crm_element_value(action_rsc, XML_ATTR_ID_LONG);
 
         if ((longname != NULL)
             && (pe_find_resource(fake_resource_list, longname) != NULL)) {
             resource = longname;
         }
     }
 
     // Certain actions need to be displayed but don't need history entries
     if (pcmk__strcase_any_of(operation, "delete", RSC_METADATA, NULL)) {
         out->message(out, "inject-rsc-action", resource, operation, node,
                      (guint) 0);
         goto done; // Confirm action and update graph
     }
 
     rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS);
     rtype = crm_element_value(action_rsc, XML_ATTR_TYPE);
     rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER);
 
     pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
 
     CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL,
                                      cib_sync_call|cib_scope_local) == pcmk_ok);
 
     // Ensure the action node is in the CIB
     uuid = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET_UUID);
     cib_node = pcmk__inject_node(fake_cib, node,
                                  ((router_node == NULL)? uuid: node));
     free(uuid);
     CRM_ASSERT(cib_node != NULL);
 
     // Add a history entry for the action
     cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
                                                  resource_config_name,
                                                  rclass, rtype, rprovider);
     if (cib_resource == NULL) {
         crm_err("Could not simulate action %d history for resource %s",
                 action->id, resource);
         free(node);
         free_xml(cib_node);
         return EINVAL;
     }
 
     // Simulate and display an executor event for the action result
     op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
                                        target_outcome, "User-injected result");
     out->message(out, "inject-rsc-action", resource, op->op_type, node,
                  op->interval_ms);
 
     // Check whether action is in a list of desired simulated failures
     for (GList *iter = fake_op_fail_list; iter != NULL; iter = iter->next) {
         char *spec = (char *) iter->data;
         char *key = NULL;
         const char *match_name = NULL;
 
         // Allow user to specify anonymous clone with or without instance number
         key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
                                 op->interval_ms, node);
         if (strncasecmp(key, spec, strlen(key)) == 0) {
             match_name = resource;
         }
         free(key);
 
         // If not found, try the resource's name in the configuration
         if ((match_name == NULL)
             && (strcmp(resource, resource_config_name) != 0)) {
 
             key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
                                     op->op_type, op->interval_ms, node);
             if (strncasecmp(key, spec, strlen(key)) == 0) {
                 match_name = resource_config_name;
             }
             free(key);
         }
 
         if (match_name == NULL) {
             continue; // This failed action entry doesn't match
         }
 
         // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
         rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
         if (rc != 1) {
             out->err(out, "Invalid failed operation '%s' "
                           "(result code must be integer)", spec);
             continue; // Keep checking other list entries
         }
 
         out->info(out, "Pretending action %d failed with rc=%d",
                   action->id, op->rc);
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
         graph->abort_priority = INFINITY;
         pcmk__inject_failcount(out, cib_node, match_name, op->op_type,
                                op->interval_ms, op->rc);
         break;
     }
 
     pcmk__inject_action_result(cib_resource, op, target_outcome);
     lrmd_free_event(op);
     rc = fake_cib->cmds->modify(fake_cib, XML_CIB_TAG_STATUS, cib_node,
                                 cib_sync_call|cib_scope_local);
     CRM_ASSERT(rc == pcmk_ok);
 
   done:
     free(node);
     free_xml(cib_node);
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a cluster action
  *
  * \param[in] graph   Graph to update with action result
  * \param[in] action  Cluster action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
     const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
     xmlNode *rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-cluster-action", node, task, rsc);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a fencing action
  *
  * \param[in] graph   Graph to update with action result
  * \param[in] action  Fencing action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *op = crm_meta_value(action->params, "stonith_action");
     char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
 
     out->message(out, "inject-fencing-action", target, op);
 
     if (!pcmk__str_eq(op, "on", pcmk__str_casei)) {
         int rc = pcmk_ok;
         GString *xpath = g_string_sized_new(512);
 
         // Set node state to offline
         xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
                                                            false);
 
         CRM_ASSERT(cib_node != NULL);
         crm_xml_add(cib_node, XML_ATTR_ORIGIN, __func__);
         rc = fake_cib->cmds->replace(fake_cib, XML_CIB_TAG_STATUS, cib_node,
                                      cib_sync_call|cib_scope_local);
         CRM_ASSERT(rc == pcmk_ok);
 
         // Simulate controller clearing node's resource history and attributes
         pcmk__g_strcat(xpath,
                        "//" XML_CIB_TAG_STATE
                        "[@" XML_ATTR_UNAME "='", target, "']/" XML_CIB_TAG_LRM,
                        NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call|cib_scope_local);
 
         g_string_truncate(xpath, 0);
         pcmk__g_strcat(xpath,
                        "//" XML_CIB_TAG_STATE
                        "[@" XML_ATTR_UNAME "='", target, "']"
                        "/" XML_TAG_TRANSIENT_NODEATTRS, NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call|cib_scope_local);
 
         free_xml(cib_node);
         g_string_free(xpath, TRUE);
     }
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     free(target);
     return pcmk_rc_ok;
 }
 
 enum pcmk__graph_status
 pcmk__simulate_transition(pe_working_set_t *data_set, cib_t *cib,
                           GList *op_fail_list)
 {
     pcmk__graph_t *transition = NULL;
     enum pcmk__graph_status graph_rc;
 
     pcmk__graph_functions_t simulation_fns = {
         simulate_pseudo_action,
         simulate_resource_action,
         simulate_cluster_action,
         simulate_fencing_action,
     };
 
     out = data_set->priv;
 
     fake_cib = cib;
     fake_op_fail_list = op_fail_list;
 
     if (!out->is_quiet(out)) {
         out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
     }
 
     pcmk__set_graph_functions(&simulation_fns);
     transition = pcmk__unpack_graph(data_set->graph, crm_system_name);
     pcmk__log_graph(LOG_DEBUG, transition);
 
     fake_resource_list = data_set->resources;
     do {
         graph_rc = pcmk__execute_graph(transition);
     } while (graph_rc == pcmk__graph_active);
     fake_resource_list = NULL;
 
     if (graph_rc != pcmk__graph_complete) {
         out->err(out, "Transition failed: %s",
                  pcmk__graph_status2text(graph_rc));
         pcmk__log_graph(LOG_ERR, transition);
         out->err(out, "An invalid transition was produced");
     }
     pcmk__free_graph(transition);
 
     if (!out->is_quiet(out)) {
         // If not quiet, we'll need the resulting CIB for later display
         xmlNode *cib_object = NULL;
         int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
                                        cib_sync_call|cib_scope_local);
 
         CRM_ASSERT(rc == pcmk_ok);
         pe_reset_working_set(data_set);
         data_set->input = cib_object;
         out->end_list(out);
     }
     return graph_rc;
 }
 
 int
 pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out,
                pcmk_injections_t *injections, unsigned int flags,
                uint32_t section_opts, char *use_date, char *input_file,
                char *graph_file, char *dot_file)
 {
     int printed = pcmk_rc_no_output;
     int rc = pcmk_rc_ok;
     xmlNodePtr input = NULL;
     cib_t *cib = NULL;
 
     rc = cib__signon_query(out, &cib, &input);
     if (rc != pcmk_rc_ok) {
         goto simulate_done;
     }
 
     reset(data_set, input, out, use_date, flags);
     cluster_status(data_set);
 
     if (!out->is_quiet(out)) {
         if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
             printed = out->message(out, "maint-mode", data_set->flags);
         }
 
         if (data_set->disabled_resources || data_set->blocked_resources) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             printed = out->info(out,
                                 "%d of %d resource instances DISABLED and "
                                 "%d BLOCKED from further action due to failure",
                                 data_set->disabled_resources,
                                 data_set->ninstances,
                                 data_set->blocked_resources);
         }
 
         /* Most formatted output headers use caps for each word, but this one
          * only has the first word capitalized for compatibility with pcs.
          */
         print_cluster_status(data_set,
                              pcmk_is_set(flags, pcmk_sim_show_pending)? pcmk_show_pending : 0,
                              section_opts, "Current cluster status",
                              (printed == pcmk_rc_ok));
         printed = pcmk_rc_ok;
     }
 
     // If the user requested any injections, handle them
     if ((injections->node_down != NULL)
         || (injections->node_fail != NULL)
         || (injections->node_up != NULL)
         || (injections->op_inject != NULL)
         || (injections->ticket_activate != NULL)
         || (injections->ticket_grant != NULL)
         || (injections->ticket_revoke != NULL)
         || (injections->ticket_standby != NULL)
         || (injections->watchdog != NULL)) {
 
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         pcmk__inject_scheduler_input(data_set, cib, injections);
         printed = pcmk_rc_ok;
 
         rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
         if (rc != pcmk_rc_ok) {
             rc = pcmk_legacy2rc(rc);
             goto simulate_done;
         }
 
         cleanup_calculations(data_set);
         reset(data_set, input, out, use_date, flags);
         cluster_status(data_set);
     }
 
     if (input_file != NULL) {
         rc = write_xml_file(input, input_file, FALSE);
         if (rc < 0) {
             rc = pcmk_legacy2rc(rc);
             goto simulate_done;
         }
     }
 
     if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
         pcmk__output_t *logger_out = NULL;
         unsigned long long data_set_flags = pe_flag_no_compat;
 
         if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
             data_set_flags |= pe_flag_show_scores;
         }
         if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
             data_set_flags |= pe_flag_show_utilization;
         }
 
         if (pcmk_all_flags_set(data_set->flags,
                                pe_flag_show_scores|pe_flag_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL,
                             "Allocation Scores and Utilization Information");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(data_set->flags, pe_flag_show_scores)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Allocation Scores");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(data_set->flags, pe_flag_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Utilization Information");
             printed = pcmk_rc_ok;
 
         } else {
             rc = pcmk__log_output_new(&logger_out);
             if (rc != pcmk_rc_ok) {
                 goto simulate_done;
             }
             pe__register_messages(logger_out);
             pcmk__register_lib_messages(logger_out);
             data_set->priv = logger_out;
         }
 
         pcmk__schedule_actions(input, data_set_flags, data_set);
 
         if (logger_out == NULL) {
             out->end_list(out);
         } else {
             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
             pcmk__output_free(logger_out);
             data_set->priv = out;
         }
 
         input = NULL;           /* Don't try and free it twice */
 
         if (graph_file != NULL) {
             rc = write_xml_file(data_set->graph, graph_file, FALSE);
             if (rc < 0) {
                 rc = pcmk_rc_graph_error;
                 goto simulate_done;
             }
         }
 
         if (dot_file != NULL) {
             rc = write_sim_dotfile(data_set, dot_file,
                                    pcmk_is_set(flags, pcmk_sim_all_actions),
                                    pcmk_is_set(flags, pcmk_sim_verbose));
             if (rc != pcmk_rc_ok) {
                 rc = pcmk_rc_dot_error;
                 goto simulate_done;
             }
         }
 
         if (!out->is_quiet(out)) {
             print_transition_summary(data_set, printed == pcmk_rc_ok);
         }
     }
 
     rc = pcmk_rc_ok;
 
     if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
         goto simulate_done;
     }
 
     PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
     if (pcmk__simulate_transition(data_set, cib,
                                   injections->op_fail) != pcmk__graph_complete) {
         rc = pcmk_rc_invalid_transition;
     }
 
     if (out->is_quiet(out)) {
         goto simulate_done;
     }
 
     set_effective_date(data_set, true, use_date);
 
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pe__set_working_set_flags(data_set, pe_flag_show_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pe__set_working_set_flags(data_set, pe_flag_show_utilization);
     }
 
     cluster_status(data_set);
     print_cluster_status(data_set, 0, section_opts, "Revised Cluster Status",
                          true);
 
 simulate_done:
     cib__clean_up_connection(&cib);
     return rc;
 }
 
 int
 pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set,
               pcmk_injections_t *injections, unsigned int flags,
               unsigned int section_opts, char *use_date, char *input_file,
               char *graph_file, char *dot_file)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     pe__register_messages(out);
     pcmk__register_lib_messages(out);
 
     rc = pcmk__simulate(data_set, out, injections, flags, section_opts,
                         use_date, input_file, graph_file, dot_file);
     pcmk__xml_output_finish(out, xml);
     return rc;
 }
diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c
index 16ca11a484..0105382f92 100644
--- a/lib/pacemaker/pcmk_status.c
+++ b/lib/pacemaker/pcmk_status.c
@@ -1,352 +1,359 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
 #include <crm/cib/internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 #include <crm/fencing/internal.h>
 #include <crm/stonith-ng.h>
 #include <pacemaker.h>
 #include <pacemaker-internal.h>
 
 static stonith_t *
 fencing_connect(void)
 {
     stonith_t *st = stonith_api_new();
     int rc = pcmk_rc_ok;
 
     if (st == NULL) {
         return NULL;
     }
 
     rc = st->cmds->connect(st, crm_system_name, NULL);
     if (rc == pcmk_rc_ok) {
         return st;
     } else {
         stonith_api_delete(st);
         return NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Output the cluster status given a fencer and CIB connection
  *
  * \param[in,out] out                  Output object
  * \param[in,out] stonith              Fencer connection
  * \param[in,out] cib                  CIB connection
  * \param[in]     current_cib          Current CIB XML
+ * \param[in]     pcmkd_state          \p pacemakerd state
  * \param[in]     fence_history        How much of the fencing history to output
  * \param[in]     show                 Group of \p pcmk_section_e flags
  * \param[in]     show_opts            Group of \p pcmk_show_opt_e flags
  * \param[in]     only_node            If a node name or tag, include only the
  *                                     matching node(s) (if any) in the output.
  *                                     If \p "*" or \p NULL, include all nodes
  *                                     in the output.
  * \param[in]     only_rsc             If a resource ID or tag, include only the
  *                                     matching resource(s) (if any) in the
  *                                     output. If \p "*" or \p NULL, include all
  *                                     resources in the output.
  * \param[in]     neg_location_prefix  Prefix denoting a ban in a constraint ID
  * \param[in]     simple_output        Whether to use a simple output format.
  *                                     Note: This is for use by \p crm_mon only
  *                                     and is planned to be deprecated.
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib,
-                            xmlNode *current_cib, enum pcmk__fence_history fence_history,
+                            xmlNode *current_cib,
+                            enum pcmk_pacemakerd_state pcmkd_state,
+                            enum pcmk__fence_history fence_history,
                             uint32_t show, uint32_t show_opts,
                             const char *only_node, const char *only_rsc,
                             const char *neg_location_prefix, bool simple_output)
 {
     xmlNode *cib_copy = copy_xml(current_cib);
     stonith_history_t *stonith_history = NULL;
     int history_rc = 0;
     pe_working_set_t *data_set = NULL;
     GList *unames = NULL;
     GList *resources = NULL;
 
     int rc = pcmk_rc_ok;
 
     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
         cib__clean_up_connection(&cib);
         free_xml(cib_copy);
         rc = pcmk_rc_schema_validation;
         out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc));
         return rc;
     }
 
     /* get the stonith-history if there is evidence we need it */
     if (fence_history != pcmk__fence_history_none) {
         history_rc = pcmk__get_fencing_history(stonith, &stonith_history,
                                                fence_history);
     }
 
     data_set = pe_new_working_set();
     CRM_ASSERT(data_set != NULL);
     pe__set_working_set_flags(data_set, pe_flag_no_compat);
 
     data_set->input = cib_copy;
     data_set->priv = out;
     cluster_status(data_set);
 
     /* Unpack constraints if any section will need them
      * (tickets may be referenced in constraints but not granted yet,
      * and bans need negative location constraints) */
     if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) {
         pcmk__unpack_constraints(data_set);
     }
 
     unames = pe__build_node_name_list(data_set, only_node);
     resources = pe__build_rsc_list(data_set, only_rsc);
 
     /* Always print DC if NULL. */
     if (data_set->dc_node == NULL) {
         show |= pcmk_section_dc;
     }
 
     if (simple_output) {
         rc = pcmk__output_simple_status(out, data_set);
     } else {
-        out->message(out, "cluster-status", data_set, pcmk_rc2exitc(history_rc),
+        out->message(out, "cluster-status",
+                     data_set, pcmkd_state, pcmk_rc2exitc(history_rc),
                      stonith_history, fence_history, show, show_opts,
                      neg_location_prefix, unames, resources);
     }
 
     g_list_free_full(unames, free);
     g_list_free_full(resources, free);
 
     stonith_history_free(stonith_history);
     stonith_history = NULL;
     pe_free_working_set(data_set);
     return rc;
 }
 
 int
 pcmk_status(xmlNodePtr *xml)
 {
     cib_t *cib = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     uint32_t show_opts = pcmk_show_pending | pcmk_show_inactive_rscs | pcmk_show_timing;
 
     cib = cib_new();
 
     if (cib == NULL) {
         return pcmk_rc_cib_corrupt;
     }
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         cib_delete(cib);
         return rc;
     }
 
     pcmk__register_lib_messages(out);
     pe__register_messages(out);
     stonith__register_messages(out);
 
     rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all,
                       show_opts, NULL, NULL, NULL, false, 0);
     pcmk__xml_output_finish(out, xml);
 
     cib_delete(cib);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Query and output the cluster status
  *
  * The operation is considered a success if we're able to get the \p pacemakerd
  * state. If possible, we'll also try to connect to the fencer and CIB and
  * output their respective status information.
  *
  * \param[in,out] out                  Output object
  * \param[in,out] cib                  CIB connection
  * \param[in]     fence_history        How much of the fencing history to output
  * \param[in]     show                 Group of \p pcmk_section_e flags
  * \param[in]     show_opts            Group of \p pcmk_show_opt_e flags
  * \param[in]     only_node            If a node name or tag, include only the
  *                                     matching node(s) (if any) in the output.
  *                                     If \p "*" or \p NULL, include all nodes
  *                                     in the output.
  * \param[in]     only_rsc             If a resource ID or tag, include only the
  *                                     matching resource(s) (if any) in the
  *                                     output. If \p "*" or \p NULL, include all
  *                                     resources in the output.
  * \param[in]     neg_location_prefix  Prefix denoting a ban in a constraint ID
  * \param[in]     simple_output        Whether to use a simple output format.
  *                                     Note: This is for use by \p crm_mon only
  *                                     and is planned to be deprecated.
  * \param[in]     timeout_ms           How long to wait for a reply from the
  *                                     \p pacemakerd API. If 0,
  *                                     \p pcmk_ipc_dispatch_sync will be used.
  *                                     If positive, \p pcmk_ipc_dispatch_main
  *                                     will be used, and a new mainloop will be
  *                                     created for this purpose (freed before
  *                                     return).
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__status(pcmk__output_t *out, cib_t *cib,
              enum pcmk__fence_history fence_history, uint32_t show,
              uint32_t show_opts, const char *only_node, const char *only_rsc,
              const char *neg_location_prefix, bool simple_output,
              unsigned int timeout_ms)
 {
     xmlNode *current_cib = NULL;
     int rc = pcmk_rc_ok;
     stonith_t *stonith = NULL;
-    enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
+    enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
+    time_t last_updated = 0;
 
     if (cib == NULL) {
         return ENOTCONN;
     }
 
     if (cib->variant == cib_native) {
         rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, false,
-                                     &state);
+                                     &pcmkd_state);
         if (rc != pcmk_rc_ok) {
             return rc;
         }
 
-        switch (state) {
+        last_updated = time(NULL);
+
+        switch (pcmkd_state) {
             case pcmk_pacemakerd_state_running:
             case pcmk_pacemakerd_state_shutting_down:
             case pcmk_pacemakerd_state_remote:
                 /* Fencer and CIB may still be available while shutting down or
                  * running on a Pacemaker Remote node
                  */
                 break;
             default:
                 // Fencer and CIB are definitely unavailable
                 out->message(out, "pacemakerd-health",
-                             NULL, state, NULL, time(NULL));
+                             NULL, pcmkd_state, NULL, last_updated);
                 return rc;
         }
 
         if (fence_history != pcmk__fence_history_none) {
             stonith = fencing_connect();
         }
     }
 
     rc = cib__signon_query(out, &cib, &current_cib);
     if (rc != pcmk_rc_ok) {
-        if (state != pcmk_pacemakerd_state_invalid) {
-            // If we got this far, invalid means we didn't query the pcmkd state
+        if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
+            // Invalid at this point means we didn't query the pcmkd state
             out->message(out, "pacemakerd-health",
-                         NULL, state, NULL, time(NULL));
+                         NULL, pcmkd_state, NULL, last_updated);
         }
         goto done;
     }
 
     rc = pcmk__output_cluster_status(out, stonith, cib, current_cib,
-                                     fence_history, show, show_opts, only_node,
-                                     only_rsc, neg_location_prefix,
-                                     simple_output);
+                                     pcmkd_state, fence_history, show,
+                                     show_opts, only_node, only_rsc,
+                                     neg_location_prefix, simple_output);
     if (rc != pcmk_rc_ok) {
         out->err(out, "Error outputting status info from the fencer or CIB");
     }
 
 done:
     stonith_api_delete(stonith);
     free_xml(current_cib);
     return pcmk_rc_ok;
 }
 
 /* This is an internal-only function that is planned to be deprecated and removed.
  * It should only ever be called from crm_mon.
  */
 int
 pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set)
 {
     int nodes_online = 0;
     int nodes_standby = 0;
     int nodes_maintenance = 0;
     GString *offline_nodes = NULL;
     bool no_dc = false;
     bool offline = false;
     bool has_warnings = false;
 
     if (data_set->dc_node == NULL) {
         has_warnings = true;
         no_dc = true;
     }
 
     for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
         pe_node_t *node = (pe_node_t *) iter->data;
 
         if (node->details->standby && node->details->online) {
             nodes_standby++;
         } else if (node->details->maintenance && node->details->online) {
             nodes_maintenance++;
         } else if (node->details->online) {
             nodes_online++;
         } else {
             pcmk__add_word(&offline_nodes, 1024, "offline node:");
             pcmk__add_word(&offline_nodes, 0, pe__node_name(node));
             has_warnings = true;
             offline = true;
         }
     }
 
     if (has_warnings) {
         out->info(out, "CLUSTER WARN: %s%s%s",
                   no_dc ? "No DC" : "",
                   no_dc && offline ? ", " : "",
                   (offline? (const char *) offline_nodes->str : ""));
 
         if (offline_nodes != NULL) {
             g_string_free(offline_nodes, TRUE);
         }
 
     } else {
         char *nodes_standby_s = NULL;
         char *nodes_maint_s = NULL;
 
         if (nodes_standby > 0) {
             nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
                                                 pcmk__plural_s(nodes_standby));
         }
 
         if (nodes_maintenance > 0) {
             nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
                                               nodes_maintenance,
                                               pcmk__plural_s(nodes_maintenance));
         }
 
         out->info(out, "CLUSTER OK: %d node%s online%s%s, "
                        "%d resource instance%s configured",
                   nodes_online, pcmk__plural_s(nodes_online),
                   nodes_standby_s != NULL ? nodes_standby_s : "",
                   nodes_maint_s != NULL ? nodes_maint_s : "",
                   data_set->ninstances, pcmk__plural_s(data_set->ninstances));
 
         free(nodes_standby_s);
         free(nodes_maint_s);
     }
 
     if (has_warnings) {
         return pcmk_rc_error;
     } else {
         return pcmk_rc_ok;
     }
     /* coverity[leaked_storage] False positive */
 }
diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c
index 20bf9fbdc7..4966cab90a 100644
--- a/lib/pengine/pe_output.c
+++ b/lib/pengine/pe_output.c
@@ -1,3028 +1,3059 @@
 /*
  * Copyright 2019-2022 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 <stdint.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/output.h>
 #include <crm/cib/util.h>
 #include <crm/msg_xml.h>
 #include <crm/pengine/internal.h>
 
 /* Never display node attributes whose name starts with one of these prefixes */
 #define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX,   \
                      "shutdown", "terminate", "standby", "#", NULL }
 
 static int
 compare_attribute(gconstpointer a, gconstpointer b)
 {
     int rc;
 
     rc = strcmp((const char *)a, (const char *)b);
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Determine whether extended information about an attribute should be added.
  *
  * \param[in]  node           Node that ran this resource.
  * \param[in]  rsc_list       The list of resources for this node.
  * \param[in]  attrname       The attribute to find.
  * \param[out] expected_score The expected value for this attribute.
  *
  * \return true if extended information should be printed, false otherwise
  * \note Currently, extended information is only supported for ping/pingd
  *       resources, for which a message will be printed if connectivity is lost
  *       or degraded.
  */
 static bool
 add_extra_info(pe_node_t *node, GList *rsc_list, pe_working_set_t *data_set,
                const char *attrname, int *expected_score)
 {
     GList *gIter = NULL;
 
     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
         const char *type = g_hash_table_lookup(rsc->meta, "type");
         const char *name = NULL;
         GHashTable *params = NULL;
 
         if (rsc->children != NULL) {
             if (add_extra_info(node, rsc->children, data_set, attrname,
                                expected_score)) {
                 return true;
             }
         }
 
         if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) {
             continue;
         }
 
         params = pe_rsc_params(rsc, node, data_set);
         name = g_hash_table_lookup(params, "name");
 
         if (name == NULL) {
             name = "pingd";
         }
 
         /* To identify the resource with the attribute name. */
         if (pcmk__str_eq(name, attrname, pcmk__str_casei)) {
             int host_list_num = 0;
             const char *hosts = g_hash_table_lookup(params, "host_list");
             const char *multiplier = g_hash_table_lookup(params, "multiplier");
             int multiplier_i;
 
             if (hosts) {
                 char **host_list = g_strsplit(hosts, " ", 0);
                 host_list_num = g_strv_length(host_list);
                 g_strfreev(host_list);
             }
 
             if ((multiplier == NULL)
                 || (pcmk__scan_min_int(multiplier, &multiplier_i,
                                        INT_MIN) != pcmk_rc_ok)) {
                 /* The ocf:pacemaker:ping resource agent defaults multiplier to
                  * 1. The agent currently does not handle invalid text, but it
                  * should, and this would be a reasonable choice ...
                  */
                 multiplier_i = 1;
             }
             *expected_score = host_list_num * multiplier_i;
 
             return true;
         }
     }
     return false;
 }
 
 static GList *
 filter_attr_list(GList *attr_list, char *name)
 {
     int i;
     const char *filt_str[] = FILTER_STR;
 
     CRM_CHECK(name != NULL, return attr_list);
 
     /* filtering automatic attributes */
     for (i = 0; filt_str[i] != NULL; i++) {
         if (g_str_has_prefix(name, filt_str[i])) {
             return attr_list;
         }
     }
 
     return g_list_insert_sorted(attr_list, name, compare_attribute);
 }
 
 static GList *
 get_operation_list(xmlNode *rsc_entry) {
     GList *op_list = NULL;
     xmlNode *rsc_op = NULL;
 
     for (rsc_op = pcmk__xe_first_child(rsc_entry); rsc_op != NULL;
          rsc_op = pcmk__xe_next(rsc_op)) {
         const char *task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
         const char *interval_ms_s = crm_element_value(rsc_op,
                                                       XML_LRM_ATTR_INTERVAL_MS);
         const char *op_rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC);
         int op_rc_i;
 
         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
 
         /* Display 0-interval monitors as "probe" */
         if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)
             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
             task = "probe";
         }
 
         /* Ignore notifies and some probes */
         if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_casei) || (pcmk__str_eq(task, "probe", pcmk__str_casei) && (op_rc_i == 7))) {
             continue;
         }
 
         if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, pcmk__str_none)) {
             op_list = g_list_append(op_list, rsc_op);
         }
     }
 
     op_list = g_list_sort(op_list, sort_op_by_callid);
     return op_list;
 }
 
 static void
 add_dump_node(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNodePtr node = user_data;
     pcmk_create_xml_text_node(node, (const char *) key, (const char *) value);
 }
 
 static void
 append_dump_text(gpointer key, gpointer value, gpointer user_data)
 {
     char **dump_text = user_data;
     char *new_text = crm_strdup_printf("%s %s=%s",
                                        *dump_text, (char *)key, (char *)value);
 
     free(*dump_text);
     *dump_text = new_text;
 }
 
 static const char *
 get_cluster_stack(pe_working_set_t *data_set)
 {
     xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
                                       data_set->input, LOG_DEBUG);
     return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
 }
 
 static char *
 last_changed_string(const char *last_written, const char *user,
                     const char *client, const char *origin) {
     if (last_written != NULL || user != NULL || client != NULL || origin != NULL) {
         return crm_strdup_printf("%s%s%s%s%s%s%s",
                                  last_written ? last_written : "",
                                  user ? " by " : "",
                                  user ? user : "",
                                  client ? " via " : "",
                                  client ? client : "",
                                  origin ? " on " : "",
                                  origin ? origin : "");
     } else {
         return strdup("");
     }
 }
 
 static char *
 op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s,
                   int rc, bool print_timing) {
     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
     char *interval_str = NULL;
     char *buf = NULL;
 
     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
         char *pair = pcmk__format_nvpair("interval", interval_ms_s, "ms");
         interval_str = crm_strdup_printf(" %s", pair);
         free(pair);
     }
 
     if (print_timing) {
         char *last_change_str = NULL;
         char *exec_str = NULL;
         char *queue_str = NULL;
 
         const char *value = NULL;
 
         time_t epoch = 0;
 
         if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &epoch) == pcmk_ok)
             && (epoch > 0)) {
             char *time = pcmk__format_named_time(XML_RSC_OP_LAST_CHANGE, epoch);
 
             last_change_str = crm_strdup_printf(" %s", time);
             free(time);
         }
 
         value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
         if (value) {
             char *pair = pcmk__format_nvpair(XML_RSC_OP_T_EXEC, value, "ms");
             exec_str = crm_strdup_printf(" %s", pair);
             free(pair);
         }
 
         value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
         if (value) {
             char *pair = pcmk__format_nvpair(XML_RSC_OP_T_QUEUE, value, "ms");
             queue_str = crm_strdup_printf(" %s", pair);
             free(pair);
         }
 
         buf = crm_strdup_printf("(%s) %s:%s%s%s%s rc=%d (%s)", call, task,
                                 interval_str ? interval_str : "",
                                 last_change_str ? last_change_str : "",
                                 exec_str ? exec_str : "",
                                 queue_str ? queue_str : "",
                                 rc, services_ocf_exitcode_str(rc));
 
         if (last_change_str) {
             free(last_change_str);
         }
 
         if (exec_str) {
             free(exec_str);
         }
 
         if (queue_str) {
             free(queue_str);
         }
     } else {
         buf = crm_strdup_printf("(%s) %s%s%s", call, task,
                                 interval_str ? ":" : "",
                                 interval_str ? interval_str : "");
     }
 
     if (interval_str) {
         free(interval_str);
     }
 
     return buf;
 }
 
 static char *
 resource_history_string(pe_resource_t *rsc, const char *rsc_id, bool all,
                         int failcount, time_t last_failure) {
     char *buf = NULL;
 
     if (rsc == NULL) {
         buf = crm_strdup_printf("%s: orphan", rsc_id);
     } else if (all || failcount || last_failure > 0) {
         char *failcount_s = NULL;
         char *lastfail_s = NULL;
 
         if (failcount > 0) {
             failcount_s = crm_strdup_printf(" %s=%d", PCMK__FAIL_COUNT_PREFIX,
                                             failcount);
         } else {
             failcount_s = strdup("");
         }
         if (last_failure > 0) {
             buf = pcmk__epoch2str(&last_failure, 0);
             lastfail_s = crm_strdup_printf(" %s='%s'",
                                            PCMK__LAST_FAILURE_PREFIX, buf);
             free(buf);
         }
 
         buf = crm_strdup_printf("%s: migration-threshold=%d%s%s",
                                 rsc_id, rsc->migration_threshold, failcount_s,
                                 lastfail_s? lastfail_s : "");
         free(failcount_s);
         free(lastfail_s);
     } else {
         buf = crm_strdup_printf("%s:", rsc_id);
     }
 
     return buf;
 }
 
 static const char *
 get_node_feature_set(pe_node_t *node) {
     const char *feature_set = NULL;
 
     if (node->details->online && !pe__is_guest_or_remote_node(node)) {
         feature_set = g_hash_table_lookup(node->details->attrs,
                                           CRM_ATTR_FEATURE_SET);
         /* The feature set attribute is present since 3.15.1. If it is missing
          * then the node must be running an earlier version. */
         if (feature_set == NULL) {
             feature_set = "<3.15.1";
         }
     }
     return feature_set;
 }
 
 static bool
 is_mixed_version(pe_working_set_t *data_set) {
     const char *feature_set = NULL;
     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = gIter->data;
         const char *node_feature_set = get_node_feature_set(node);
         if (node_feature_set != NULL) {
             if (feature_set == NULL) {
                 feature_set = node_feature_set;
             } else if (strcmp(feature_set, node_feature_set) != 0) {
                 return true;
             }
         }
     }
     return false;
 }
 
 static char *
 formatted_xml_buf(pe_resource_t *rsc, bool raw)
 {
     if (raw) {
         return dump_xml_formatted(rsc->orig_xml ? rsc->orig_xml : rsc->xml);
     } else {
         return dump_xml_formatted(rsc->xml);
     }
 }
 
-PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", "uint32_t", "uint32_t")
+PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 static int
 cluster_summary(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     int rc = pcmk_rc_no_output;
     const char *stack_s = get_cluster_stack(data_set);
 
     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
-        out->message(out, "cluster-stack", stack_s);
+        out->message(out, "cluster-stack", stack_s, pcmkd_state);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_dc)) {
         xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
                                                data_set->input, LOG_DEBUG);
         const char *dc_version_s = dc_version?
                                    crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
                                    : NULL;
         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
         char *dc_name = data_set->dc_node ? pe__node_display_name(data_set->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
         bool mixed_version = is_mixed_version(data_set);
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-dc", data_set->dc_node, quorum,
                      dc_version_s, dc_name, mixed_version);
         free(dc_name);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_times)) {
         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-times", last_written, user, client, origin);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-counts", g_list_length(data_set->nodes),
                      data_set->ninstances, data_set->disabled_resources,
                      data_set->blocked_resources);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_options)) {
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-options", data_set);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 
     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
         if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) {
             rc = pcmk_rc_ok;
         }
     }
 
     return rc;
 }
 
-PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", "uint32_t", "uint32_t")
+PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 static int
 cluster_summary_html(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     int rc = pcmk_rc_no_output;
     const char *stack_s = get_cluster_stack(data_set);
 
     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
-        out->message(out, "cluster-stack", stack_s);
+        out->message(out, "cluster-stack", stack_s, pcmkd_state);
     }
 
     /* Always print DC if none, even if not requested */
     if (data_set->dc_node == NULL || pcmk_is_set(section_opts, pcmk_section_dc)) {
         xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
                                                data_set->input, LOG_DEBUG);
         const char *dc_version_s = dc_version?
                                    crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
                                    : NULL;
         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
         char *dc_name = data_set->dc_node ? pe__node_display_name(data_set->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL;
         bool mixed_version = is_mixed_version(data_set);
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-dc", data_set->dc_node, quorum,
                      dc_version_s, dc_name, mixed_version);
         free(dc_name);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_times)) {
         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-times", last_written, user, client, origin);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
         out->message(out, "cluster-counts", g_list_length(data_set->nodes),
                      data_set->ninstances, data_set->disabled_resources,
                      data_set->blocked_resources);
     }
 
     if (pcmk_is_set(section_opts, pcmk_section_options)) {
         /* Kind of a hack - close the list we may have opened earlier in this
          * function so we can put all the options into their own list.  We
          * only want to do this on HTML output, though.
          */
         PCMK__OUTPUT_LIST_FOOTER(out, rc);
 
         out->begin_list(out, NULL, NULL, "Config Options");
         out->message(out, "cluster-options", data_set);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 
     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
         if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) {
             rc = pcmk_rc_ok;
         }
     }
 
     return rc;
 }
 
 char *
 pe__node_display_name(pe_node_t *node, bool print_detail)
 {
     char *node_name;
     const char *node_host = NULL;
     const char *node_id = NULL;
     int name_len;
 
     CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
 
     /* Host is displayed only if this is a guest node and detail is requested */
     if (print_detail && pe__is_guest_node(node)) {
         const pe_resource_t *container = node->details->remote_rsc->container;
         const pe_node_t *host_node = pe__current_node(container);
 
         if (host_node && host_node->details) {
             node_host = host_node->details->uname;
         }
         if (node_host == NULL) {
             node_host = ""; /* so we at least get "uname@" to indicate guest */
         }
     }
 
     /* Node ID is displayed if different from uname and detail is requested */
     if (print_detail && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) {
         node_id = node->details->id;
     }
 
     /* Determine name length */
     name_len = strlen(node->details->uname) + 1;
     if (node_host) {
         name_len += strlen(node_host) + 1; /* "@node_host" */
     }
     if (node_id) {
         name_len += strlen(node_id) + 3; /* + " (node_id)" */
     }
 
     /* Allocate and populate display name */
     node_name = malloc(name_len);
     CRM_ASSERT(node_name != NULL);
     strcpy(node_name, node->details->uname);
     if (node_host) {
         strcat(node_name, "@");
         strcat(node_name, node_host);
     }
     if (node_id) {
         strcat(node_name, " (");
         strcat(node_name, node_id);
         strcat(node_name, ")");
     }
     return node_name;
 }
 
 int
 pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
                          , size_t pairs_count, ...)
 {
     xmlNodePtr xml_node = NULL;
     va_list args;
 
     CRM_ASSERT(tag_name != NULL);
 
     xml_node = pcmk__output_xml_peek_parent(out);
     CRM_ASSERT(xml_node != NULL);
     xml_node = is_list
         ? create_xml_node(xml_node, tag_name)
         : xmlNewChild(xml_node, NULL, (pcmkXmlStr) tag_name, NULL);
 
     va_start(args, pairs_count);
     while(pairs_count--) {
         const char *param_name = va_arg(args, const char *);
         const char *param_value = va_arg(args, const char *);
         if (param_name && param_value) {
             crm_xml_add(xml_node, param_name, param_value);
         }
     };
     va_end(args);
 
     if (is_list) {
         pcmk__output_xml_push_parent(out, xml_node);
     }
     return pcmk_rc_ok;
 }
 
 static const char *
 role_desc(enum rsc_role_e role)
 {
     if (role == RSC_ROLE_PROMOTED) {
 #ifdef PCMK__COMPAT_2_0
         return "as " RSC_ROLE_PROMOTED_LEGACY_S " ";
 #else
         return "in " RSC_ROLE_PROMOTED_S " role ";
 #endif
     }
     return "";
 }
 
 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
 static int
 ban_html(pcmk__output_t *out, va_list args) {
     pe_node_t *pe_node = va_arg(args, pe_node_t *);
     pe__location_t *location = va_arg(args, pe__location_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     char *node_name = pe__node_display_name(pe_node,
                                             pcmk_is_set(show_opts, pcmk_show_node_id));
     char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s",
                                   location->id, location->rsc_lh->id,
                                   role_desc(location->role_filter), node_name);
 
     pcmk__output_create_html_node(out, "li", NULL, NULL, buf);
 
     free(node_name);
     free(buf);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
 static int
 ban_text(pcmk__output_t *out, va_list args) {
     pe_node_t *pe_node = va_arg(args, pe_node_t *);
     pe__location_t *location = va_arg(args, pe__location_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     char *node_name = pe__node_display_name(pe_node,
                                             pcmk_is_set(show_opts, pcmk_show_node_id));
     out->list_item(out, NULL, "%s\tprevents %s from running %son %s",
                    location->id, location->rsc_lh->id,
                    role_desc(location->role_filter), node_name);
 
     free(node_name);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
 static int
 ban_xml(pcmk__output_t *out, va_list args) {
     pe_node_t *pe_node = va_arg(args, pe_node_t *);
     pe__location_t *location = va_arg(args, pe__location_t *);
     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
 
     const char *promoted_only = pcmk__btoa(location->role_filter == RSC_ROLE_PROMOTED);
     char *weight_s = pcmk__itoa(pe_node->weight);
 
     pcmk__output_create_xml_node(out, "ban",
                                  "id", location->id,
                                  "resource", location->rsc_lh->id,
                                  "node", pe_node->details->uname,
                                  "weight", weight_s,
                                  "promoted-only", promoted_only,
                                  /* This is a deprecated alias for
                                   * promoted_only. Removing it will break
                                   * backward compatibility of the API schema,
                                   * which will require an API schema major
                                   * version bump.
                                   */
                                  "master_only", promoted_only,
                                  NULL);
 
     free(weight_s);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ban-list", "pe_working_set_t *", "const char *", "GList *",
                   "uint32_t", "bool")
 static int
 ban_list(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     const char *prefix = va_arg(args, const char *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer = va_arg(args, int);
 
     GList *gIter, *gIter2;
     int rc = pcmk_rc_no_output;
 
     /* Print each ban */
     for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
         pe__location_t *location = gIter->data;
 
         if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) {
             continue;
         }
 
         if (!pcmk__str_in_list(rsc_printable_id(location->rsc_lh), only_rsc, pcmk__str_star_matches) &&
             !pcmk__str_in_list(rsc_printable_id(uber_parent(location->rsc_lh)), only_rsc, pcmk__str_star_matches)) {
             continue;
         }
 
         for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
             pe_node_t *node = (pe_node_t *) gIter2->data;
 
             if (node->weight < 0) {
                 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints");
                 out->message(out, "ban", node, location, show_opts);
             }
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
 static int
 cluster_counts_html(pcmk__output_t *out, va_list args) {
     unsigned int nnodes = va_arg(args, unsigned int);
     int nresources = va_arg(args, int);
     int ndisabled = va_arg(args, int);
     int nblocked = va_arg(args, int);
 
     xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL);
     xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL);
 
     char *nnodes_str = crm_strdup_printf("%d node%s configured",
                                          nnodes, pcmk__plural_s(nnodes));
 
     pcmk_create_html_node(nodes_node, "span", NULL, NULL, nnodes_str);
     free(nnodes_str);
 
     if (ndisabled && nblocked) {
         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
                                     nresources, pcmk__plural_s(nresources),
                                     ndisabled);
         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
         free(s);
 
         pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
 
         s = crm_strdup_printf(", %d ", nblocked);
         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
         free(s);
 
         pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
         pcmk_create_html_node(resources_node, "span", NULL, NULL,
                               " from further action due to failure)");
     } else if (ndisabled && !nblocked) {
         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
                                     nresources, pcmk__plural_s(nresources),
                                     ndisabled);
         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
         free(s);
 
         pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
         pcmk_create_html_node(resources_node, "span", NULL, NULL, ")");
     } else if (!ndisabled && nblocked) {
         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
                                     nresources, pcmk__plural_s(nresources),
                                     nblocked);
         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
         free(s);
 
         pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
         pcmk_create_html_node(resources_node, "span", NULL, NULL,
                               " from further action due to failure)");
     } else {
         char *s = crm_strdup_printf("%d resource instance%s configured",
                                     nresources, pcmk__plural_s(nresources));
         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
         free(s);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
 static int
 cluster_counts_text(pcmk__output_t *out, va_list args) {
     unsigned int nnodes = va_arg(args, unsigned int);
     int nresources = va_arg(args, int);
     int ndisabled = va_arg(args, int);
     int nblocked = va_arg(args, int);
 
     out->list_item(out, NULL, "%d node%s configured",
                    nnodes, pcmk__plural_s(nnodes));
 
     if (ndisabled && nblocked) {
         out->list_item(out, NULL, "%d resource instance%s configured "
                                   "(%d DISABLED, %d BLOCKED from "
                                   "further action due to failure)",
                        nresources, pcmk__plural_s(nresources), ndisabled,
                        nblocked);
     } else if (ndisabled && !nblocked) {
         out->list_item(out, NULL, "%d resource instance%s configured "
                                   "(%d DISABLED)",
                        nresources, pcmk__plural_s(nresources), ndisabled);
     } else if (!ndisabled && nblocked) {
         out->list_item(out, NULL, "%d resource instance%s configured "
                                   "(%d BLOCKED from further action "
                                   "due to failure)",
                        nresources, pcmk__plural_s(nresources), nblocked);
     } else {
         out->list_item(out, NULL, "%d resource instance%s configured",
                        nresources, pcmk__plural_s(nresources));
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
 static int
 cluster_counts_xml(pcmk__output_t *out, va_list args) {
     unsigned int nnodes = va_arg(args, unsigned int);
     int nresources = va_arg(args, int);
     int ndisabled = va_arg(args, int);
     int nblocked = va_arg(args, int);
 
     xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured", NULL);
     xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured", NULL);
 
     char *s = pcmk__itoa(nnodes);
     crm_xml_add(nodes_node, "number", s);
     free(s);
 
     s = pcmk__itoa(nresources);
     crm_xml_add(resources_node, "number", s);
     free(s);
 
     s = pcmk__itoa(ndisabled);
     crm_xml_add(resources_node, "disabled", s);
     free(s);
 
     s = pcmk__itoa(nblocked);
     crm_xml_add(resources_node, "blocked", s);
     free(s);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
                   "char *", "int")
 static int
 cluster_dc_html(pcmk__output_t *out, va_list args) {
     pe_node_t *dc = va_arg(args, pe_node_t *);
     const char *quorum = va_arg(args, const char *);
     const char *dc_version_s = va_arg(args, const char *);
     char *dc_name = va_arg(args, char *);
     bool mixed_version = va_arg(args, int);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 
     pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: ");
 
     if (dc) {
         char *buf = crm_strdup_printf("%s (version %s) -", dc_name,
                                       dc_version_s ? dc_version_s : "unknown");
         pcmk_create_html_node(node, "span", NULL, NULL, buf);
         free(buf);
 
         if (mixed_version) {
             pcmk_create_html_node(node, "span", NULL, "warning",
                                   " MIXED-VERSION");
         }
         pcmk_create_html_node(node, "span", NULL, NULL, " partition");
         if (crm_is_true(quorum)) {
             pcmk_create_html_node(node, "span", NULL, NULL, " with");
         } else {
             pcmk_create_html_node(node, "span", NULL, "warning", " WITHOUT");
         }
         pcmk_create_html_node(node, "span", NULL, NULL, " quorum");
     } else {
         pcmk_create_html_node(node, "span", NULL, "warning", "NONE");
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
                   "char *", "int")
 static int
 cluster_dc_text(pcmk__output_t *out, va_list args) {
     pe_node_t *dc = va_arg(args, pe_node_t *);
     const char *quorum = va_arg(args, const char *);
     const char *dc_version_s = va_arg(args, const char *);
     char *dc_name = va_arg(args, char *);
     bool mixed_version = va_arg(args, int);
 
     if (dc) {
         out->list_item(out, "Current DC",
                        "%s (version %s) - %spartition %s quorum",
                        dc_name, dc_version_s ? dc_version_s : "unknown",
                        mixed_version ? "MIXED-VERSION " : "",
                        crm_is_true(quorum) ? "with" : "WITHOUT");
     } else {
         out->list_item(out, "Current DC", "NONE");
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
                   "char *", "int")
 static int
 cluster_dc_xml(pcmk__output_t *out, va_list args) {
     pe_node_t *dc = va_arg(args, pe_node_t *);
     const char *quorum = va_arg(args, const char *);
     const char *dc_version_s = va_arg(args, const char *);
     char *dc_name G_GNUC_UNUSED = va_arg(args, char *);
     bool mixed_version = va_arg(args, int);
 
     if (dc) {
         pcmk__output_create_xml_node(out, "current_dc",
                                      "present", "true",
                                      "version", dc_version_s ? dc_version_s : "",
                                      "name", dc->details->uname,
                                      "id", dc->details->id,
                                      "with_quorum", pcmk__btoa(crm_is_true(quorum)),
                                      "mixed_version", pcmk__btoa(mixed_version),
                                      NULL);
     } else {
         pcmk__output_create_xml_node(out, "current_dc",
                                      "present", "false",
                                      NULL);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int")
 static int
 cluster_maint_mode_text(pcmk__output_t *out, va_list args) {
     unsigned long long flags = va_arg(args, unsigned long long);
 
     if (pcmk_is_set(flags, pe_flag_maintenance_mode)) {
         pcmk__formatted_printf(out, "\n              *** Resource management is DISABLED ***\n");
         pcmk__formatted_printf(out, "  The cluster will not attempt to start, stop or recover services\n");
         return pcmk_rc_ok;
     } else if (pcmk_is_set(flags, pe_flag_stop_everything)) {
         pcmk__formatted_printf(out, "\n    *** Resource management is DISABLED ***\n");
         pcmk__formatted_printf(out, "  The cluster will keep all resources stopped\n");
         return pcmk_rc_ok;
     } else {
         return pcmk_rc_no_output;
     }
 }
 
 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
 static int
 cluster_options_html(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 
     out->list_item(out, NULL, "STONITH of failed nodes %s",
                    pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled");
 
     out->list_item(out, NULL, "Cluster is %s",
                    pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric");
 
     switch (data_set->no_quorum_policy) {
         case no_quorum_freeze:
             out->list_item(out, NULL, "No quorum policy: Freeze resources");
             break;
 
         case no_quorum_stop:
             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
             break;
 
         case no_quorum_demote:
             out->list_item(out, NULL, "No quorum policy: Demote promotable "
                            "resources and stop all other resources");
             break;
 
         case no_quorum_ignore:
             out->list_item(out, NULL, "No quorum policy: Ignore");
             break;
 
         case no_quorum_suicide:
             out->list_item(out, NULL, "No quorum policy: Suicide");
             break;
     }
 
     if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 
         pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
         pcmk_create_html_node(node, "span", NULL, "bold", "DISABLED");
         pcmk_create_html_node(node, "span", NULL, NULL,
                               " (the cluster will not attempt to start, stop, or recover services)");
     } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) {
         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 
         pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
         pcmk_create_html_node(node, "span", NULL, "bold", "STOPPED");
         pcmk_create_html_node(node, "span", NULL, NULL,
                               " (the cluster will keep all resources stopped)");
     } else {
         out->list_item(out, NULL, "Resource management: enabled");
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
 static int
 cluster_options_log(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 
     if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
         return out->info(out, "Resource management is DISABLED.  The cluster will not attempt to start, stop or recover services.");
     } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) {
         return out->info(out, "Resource management is DISABLED.  The cluster has stopped all resources.");
     } else {
         return pcmk_rc_no_output;
     }
 }
 
 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
 static int
 cluster_options_text(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 
     out->list_item(out, NULL, "STONITH of failed nodes %s",
                    pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled");
 
     out->list_item(out, NULL, "Cluster is %s",
                    pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric");
 
     switch (data_set->no_quorum_policy) {
         case no_quorum_freeze:
             out->list_item(out, NULL, "No quorum policy: Freeze resources");
             break;
 
         case no_quorum_stop:
             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
             break;
 
         case no_quorum_demote:
             out->list_item(out, NULL, "No quorum policy: Demote promotable "
                            "resources and stop all other resources");
             break;
 
         case no_quorum_ignore:
             out->list_item(out, NULL, "No quorum policy: Ignore");
             break;
 
         case no_quorum_suicide:
             out->list_item(out, NULL, "No quorum policy: Suicide");
             break;
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
 static int
 cluster_options_xml(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 
     const char *no_quorum_policy = NULL;
     char *stonith_timeout_str = pcmk__itoa(data_set->stonith_timeout);
     char *priority_fencing_delay_str = pcmk__itoa(data_set->priority_fencing_delay * 1000);
 
     switch (data_set->no_quorum_policy) {
         case no_quorum_freeze:
             no_quorum_policy = "freeze";
             break;
 
         case no_quorum_stop:
             no_quorum_policy = "stop";
             break;
 
         case no_quorum_demote:
             no_quorum_policy = "demote";
             break;
 
         case no_quorum_ignore:
             no_quorum_policy = "ignore";
             break;
 
         case no_quorum_suicide:
             no_quorum_policy = "suicide";
             break;
     }
 
     pcmk__output_create_xml_node(out, "cluster_options",
                                  "stonith-enabled", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)),
                                  "symmetric-cluster", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)),
                                  "no-quorum-policy", no_quorum_policy,
                                  "maintenance-mode", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)),
                                  "stop-all-resources", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stop_everything)),
                                  "stonith-timeout-ms", stonith_timeout_str,
                                  "priority-fencing-delay-ms", priority_fencing_delay_str,
                                  NULL);
     free(stonith_timeout_str);
     free(priority_fencing_delay_str);
 
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("cluster-stack", "const char *")
+PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
 static int
 cluster_stack_html(pcmk__output_t *out, va_list args) {
     const char *stack_s = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 
     pcmk_create_html_node(node, "span", NULL, "bold", "Stack: ");
     pcmk_create_html_node(node, "span", NULL, NULL, stack_s);
 
+    if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
+        pcmk_create_html_node(node, "span", NULL, NULL, " (");
+        pcmk_create_html_node(node, "span", NULL, NULL,
+                              pcmk__pcmkd_state_enum2friendly(pcmkd_state));
+        pcmk_create_html_node(node, "span", NULL, NULL, ")");
+    }
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("cluster-stack", "const char *")
+PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
 static int
 cluster_stack_text(pcmk__output_t *out, va_list args) {
     const char *stack_s = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
+
+    if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
+        out->list_item(out, "Stack", "%s (%s)",
+                       stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state));
+    } else {
+        out->list_item(out, "Stack", "%s", stack_s);
+    }
 
-    out->list_item(out, "Stack", "%s", stack_s);
     return pcmk_rc_ok;
 }
 
-PCMK__OUTPUT_ARGS("cluster-stack", "const char *")
+PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
 static int
 cluster_stack_xml(pcmk__output_t *out, va_list args) {
     const char *stack_s = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state pcmkd_state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
+
+    const char *state_s = NULL;
+
+    if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
+        state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state);
+    }
 
     pcmk__output_create_xml_node(out, "stack",
                                  "type", stack_s,
+                                 "pacemakerd-state", state_s,
                                  NULL);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *")
 static int
 cluster_times_html(pcmk__output_t *out, va_list args) {
     const char *last_written = va_arg(args, const char *);
     const char *user = va_arg(args, const char *);
     const char *client = va_arg(args, const char *);
     const char *origin = va_arg(args, const char *);
 
     xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL);
     xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL);
 
     char *buf = pcmk__epoch2str(NULL, 0);
 
     pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: ");
     pcmk_create_html_node(updated_node, "span", NULL, NULL, buf);
 
     free(buf);
     buf = last_changed_string(last_written, user, client, origin);
 
     pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: ");
     pcmk_create_html_node(changed_node, "span", NULL, NULL, buf);
 
     free(buf);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *")
 static int
 cluster_times_xml(pcmk__output_t *out, va_list args) {
     const char *last_written = va_arg(args, const char *);
     const char *user = va_arg(args, const char *);
     const char *client = va_arg(args, const char *);
     const char *origin = va_arg(args, const char *);
 
     char *buf = pcmk__epoch2str(NULL, 0);
 
     pcmk__output_create_xml_node(out, "last_update",
                                  "time", buf,
                                  NULL);
 
     pcmk__output_create_xml_node(out, "last_change",
                                  "time", last_written ? last_written : "",
                                  "user", user ? user : "",
                                  "client", client ? client : "",
                                  "origin", origin ? origin : "",
                                  NULL);
 
     free(buf);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *")
 static int
 cluster_times_text(pcmk__output_t *out, va_list args) {
     const char *last_written = va_arg(args, const char *);
     const char *user = va_arg(args, const char *);
     const char *client = va_arg(args, const char *);
     const char *origin = va_arg(args, const char *);
 
     char *buf = pcmk__epoch2str(NULL, 0);
 
     out->list_item(out, "Last updated", "%s", buf);
 
     free(buf);
     buf = last_changed_string(last_written, user, client, origin);
 
     out->list_item(out, "Last change", " %s", buf);
 
     free(buf);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Display a failed action in less-technical natural language
  */
 static void
 failed_action_friendly(pcmk__output_t *out, xmlNodePtr xml_op,
                        const char *op_key, const char *node_name, int rc,
                        int status, const char *exit_reason,
                        const char *exec_time)
 {
     char *rsc_id = NULL;
     char *task = NULL;
     guint interval_ms = 0;
     time_t last_change_epoch = 0;
     GString *str = NULL;
 
     if (pcmk__str_empty(op_key)
         || !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) {
         rsc_id = strdup("unknown resource");
         task = strdup("unknown action");
         interval_ms = 0;
     }
     CRM_ASSERT((rsc_id != NULL) && (task != NULL));
 
     str = g_string_sized_new(256); // Should be sufficient for most messages
 
     pcmk__g_strcat(str, rsc_id, " ", NULL);
 
     if (interval_ms != 0) {
         pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ",
                        NULL);
     }
     pcmk__g_strcat(str, crm_action_str(task, interval_ms), " on ", node_name,
                    NULL);
 
     if (status == PCMK_EXEC_DONE) {
         pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'",
                        NULL);
         if (!pcmk__str_empty(exit_reason)) {
             pcmk__g_strcat(str, " (", exit_reason, ")", NULL);
         }
 
     } else {
         pcmk__g_strcat(str, " could not be executed (",
                        pcmk_exec_status_str(status), NULL);
         if (!pcmk__str_empty(exit_reason)) {
             pcmk__g_strcat(str, ": ", exit_reason, NULL);
         }
         g_string_append_c(str, ')');
     }
 
 
     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                 &last_change_epoch) == pcmk_ok) {
         char *s = pcmk__epoch2str(&last_change_epoch, 0);
 
         pcmk__g_strcat(str, " at ", s, NULL);
         free(s);
     }
     if (!pcmk__str_empty(exec_time)) {
         int exec_time_ms = 0;
 
         if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok)
             && (exec_time_ms > 0)) {
 
             pcmk__g_strcat(str, " after ",
                            pcmk__readable_interval(exec_time_ms), NULL);
         }
     }
 
     out->list_item(out, NULL, "%s", str->str);
     g_string_free(str, TRUE);
     free(rsc_id);
     free(task);
 }
 
 /*!
  * \internal
  * \brief Display a failed action with technical details
  */
 static void
 failed_action_technical(pcmk__output_t *out, xmlNodePtr xml_op,
                         const char *op_key, const char *node_name, int rc,
                         int status, const char *exit_reason,
                         const char *exec_time)
 {
     const char *call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
     const char *queue_time = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
     const char *exit_status = services_ocf_exitcode_str(rc);
     const char *lrm_status = pcmk_exec_status_str(status);
     time_t last_change_epoch = 0;
     GString *str = NULL;
 
     if (pcmk__str_empty(op_key)) {
         op_key = "unknown operation";
     }
     if (pcmk__str_empty(exit_status)) {
         exit_status = "unknown exit status";
     }
     if (pcmk__str_empty(call_id)) {
         call_id = "unknown";
     }
 
     str = g_string_sized_new(256);
 
     g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'",
                            op_key, node_name, exit_status, rc, call_id,
                            lrm_status);
 
     if (!pcmk__str_empty(exit_reason)) {
         pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL);
     }
 
     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                 &last_change_epoch) == pcmk_ok) {
         char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0);
 
         pcmk__g_strcat(str,
                        ", " XML_RSC_OP_LAST_CHANGE "="
                        "'", last_change_str, "'", NULL);
         free(last_change_str);
     }
     if (!pcmk__str_empty(queue_time)) {
         pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL);
     }
     if (!pcmk__str_empty(exec_time)) {
         pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL);
     }
 
     out->list_item(out, NULL, "%s", str->str);
     g_string_free(str, TRUE);
 }
 
 PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
 static int
 failed_action_default(pcmk__output_t *out, va_list args)
 {
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     const char *node_name = crm_element_value(xml_op, XML_ATTR_UNAME);
     const char *exit_reason = crm_element_value(xml_op,
                                                 XML_LRM_ATTR_EXIT_REASON);
     const char *exec_time = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
 
     int rc;
     int status;
 
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
 
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
                        &status, 0);
 
     if (pcmk__str_empty(op_key)) {
         op_key = ID(xml_op);
     }
     if (pcmk__str_empty(node_name)) {
         node_name = "unknown node";
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
         failed_action_technical(out, xml_op, op_key, node_name, rc, status,
                                 exit_reason, exec_time);
     } else {
         failed_action_friendly(out, xml_op, op_key, node_name, rc, status,
                                exit_reason, exec_time);
     }
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
 static int
 failed_action_xml(pcmk__output_t *out, va_list args) {
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
 
     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     int rc;
     int status;
     const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
 
     time_t epoch = 0;
     char *rc_s = NULL;
     char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none");
     xmlNodePtr node = NULL;
 
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
                        &status, 0);
 
     rc_s = pcmk__itoa(rc);
     node = pcmk__output_create_xml_node(out, "failure",
                                         (op_key == NULL)? "id" : "op_key",
                                         (op_key == NULL)? ID(xml_op) : op_key,
                                         "node", crm_element_value(xml_op, XML_ATTR_UNAME),
                                         "exitstatus", services_ocf_exitcode_str(rc),
                                         "exitreason", pcmk__s(reason_s, ""),
                                         "exitcode", rc_s,
                                         "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
                                         "status", pcmk_exec_status_str(status),
                                         NULL);
     free(rc_s);
 
     if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                  &epoch) == pcmk_ok) && (epoch > 0)) {
         guint interval_ms = 0;
         char *s = NULL;
         crm_time_t *crm_when = pcmk__copy_timet(epoch);
         char *rc_change = NULL;
 
         crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
         s = pcmk__itoa(interval_ms);
 
         rc_change = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
 
         pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE, rc_change,
                            "queued", crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
                            "exec", crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
                            "interval", s,
                            "task", crm_element_value(xml_op, XML_LRM_ATTR_TASK),
                            NULL);
 
         free(s);
         free(rc_change);
         crm_time_free(crm_when);
     }
 
     free(reason_s);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("failed-action-list", "pe_working_set_t *", "GList *",
                   "GList *", "uint32_t", "bool")
 static int
 failed_action_list(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer = va_arg(args, int);
 
     xmlNode *xml_op = NULL;
     int rc = pcmk_rc_no_output;
 
     const char *id = NULL;
 
     if (xmlChildElementCount(data_set->failed) == 0) {
         return rc;
     }
 
     for (xml_op = pcmk__xml_first_child(data_set->failed); xml_op != NULL;
          xml_op = pcmk__xml_next(xml_op)) {
         char *rsc = NULL;
 
         if (!pcmk__str_in_list(crm_element_value(xml_op, XML_ATTR_UNAME), only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         if (pcmk_xe_mask_probe_failure(xml_op)) {
             continue;
         }
 
         id = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
         if (!parse_op_key(id ? id : ID(xml_op), &rsc, NULL, NULL)) {
             continue;
         }
 
         if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) {
             free(rsc);
             continue;
         }
 
         free(rsc);
 
         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions");
         out->message(out, "failed-action", xml_op, show_opts);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 static void
 status_node(pe_node_t *node, xmlNodePtr parent, uint32_t show_opts)
 {
     int health = pe__node_health(node);
 
     // Cluster membership
     if (node->details->online) {
         pcmk_create_html_node(parent, "span", NULL, "online", " online");
     } else {
         pcmk_create_html_node(parent, "span", NULL, "offline", " OFFLINE");
     }
 
     // Standby mode
     if (node->details->standby_onfail && (node->details->running_rsc != NULL)) {
         pcmk_create_html_node(parent, "span", NULL, "standby",
                               " (in standby due to on-fail,"
                               " with active resources)");
     } else if (node->details->standby_onfail) {
         pcmk_create_html_node(parent, "span", NULL, "standby",
                               " (in standby due to on-fail)");
     } else if (node->details->standby && (node->details->running_rsc != NULL)) {
         pcmk_create_html_node(parent, "span", NULL, "standby",
                               " (in standby, with active resources)");
     } else if (node->details->standby) {
         pcmk_create_html_node(parent, "span", NULL, "standby", " (in standby)");
     }
 
     // Maintenance mode
     if (node->details->maintenance) {
         pcmk_create_html_node(parent, "span", NULL, "maint",
                               " (in maintenance mode)");
     }
 
     // Node health
     if (health < 0) {
         pcmk_create_html_node(parent, "span", NULL, "health_red",
                               " (health is RED)");
     } else if (health == 0) {
         pcmk_create_html_node(parent, "span", NULL, "health_yellow",
                               " (health is YELLOW)");
     }
 
     // Feature set
     if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
         const char *feature_set = get_node_feature_set(node);
         if (feature_set != NULL) {
             char *buf = crm_strdup_printf(", feature set %s", feature_set);
             pcmk_create_html_node(parent, "span", NULL, NULL, buf);
             free(buf);
         }
     }
 }
 
 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool",
                   "GList *", "GList *")
 static int
 node_html(pcmk__output_t *out, va_list args) {
     pe_node_t *node = va_arg(args, pe_node_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool full = va_arg(args, int);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
 
     if (full) {
         xmlNodePtr item_node;
 
         if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) {
             GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
 
             out->begin_list(out, NULL, NULL, "%s:", node_name);
             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
             pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
             status_node(node, item_node, show_opts);
 
             if (rscs != NULL) {
                 uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
                 out->begin_list(out, NULL, NULL, "Resources");
                 pe__rscs_brief_output(out, rscs, new_show_opts);
                 out->end_list(out);
             }
 
             pcmk__output_xml_pop_parent(out);
             out->end_list(out);
 
         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
             GList *lpc2 = NULL;
             int rc = pcmk_rc_no_output;
 
             out->begin_list(out, NULL, NULL, "%s:", node_name);
             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
             pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
             status_node(node, item_node, show_opts);
 
             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
                 pe_resource_t *rsc = (pe_resource_t *) lpc2->data;
                 PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources");
 
                 show_opts |= pcmk_show_rsc_only;
                 out->message(out, crm_map_element_name(rsc->xml), show_opts,
                              rsc, only_node, only_rsc);
             }
 
             PCMK__OUTPUT_LIST_FOOTER(out, rc);
             pcmk__output_xml_pop_parent(out);
             out->end_list(out);
 
         } else {
             char *buf = crm_strdup_printf("%s:", node_name);
 
             item_node = pcmk__output_create_xml_node(out, "li", NULL);
             pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
             status_node(node, item_node, show_opts);
 
             free(buf);
         }
     } else {
         out->begin_list(out, NULL, NULL, "%s:", node_name);
     }
 
     free(node_name);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Get a human-friendly textual description of a node's status
  *
  * \param[in] node  Node to check
  *
  * \return String representation of node's status
  */
 static const char *
 node_text_status(pe_node_t *node)
 {
     if (node->details->unclean) {
         if (node->details->online) {
             return "UNCLEAN (online)";
 
         } else if (node->details->pending) {
             return "UNCLEAN (pending)";
 
         } else {
             return "UNCLEAN (offline)";
         }
 
     } else if (node->details->pending) {
         return "pending";
 
     } else if (node->details->standby_onfail && node->details->online) {
         return "standby (on-fail)";
 
     } else if (node->details->standby) {
         if (node->details->online) {
             if (node->details->running_rsc) {
                 return "standby (with active resources)";
             } else {
                 return "standby";
             }
         } else {
             return "OFFLINE (standby)";
         }
 
     } else if (node->details->maintenance) {
         if (node->details->online) {
             return "maintenance";
         } else {
             return "OFFLINE (maintenance)";
         }
 
     } else if (node->details->online) {
         return "online";
     }
 
     return "OFFLINE";
 }
 
 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *")
 static int
 node_text(pcmk__output_t *out, va_list args) {
     pe_node_t *node = va_arg(args, pe_node_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool full = va_arg(args, int);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     if (full) {
         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
         GString *str = g_string_sized_new(64);
         int health = pe__node_health(node);
 
         // Create a summary line with node type, name, and status
         if (pe__is_guest_node(node)) {
             g_string_append(str, "GuestNode");
         } else if (pe__is_remote_node(node)) {
             g_string_append(str, "RemoteNode");
         } else {
             g_string_append(str, "Node");
         }
         pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL);
 
         if (health < 0) {
             g_string_append(str, " (health is RED)");
         } else if (health == 0) {
             g_string_append(str, " (health is YELLOW)");
         }
         if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
             const char *feature_set = get_node_feature_set(node);
             if (feature_set != NULL) {
                 pcmk__g_strcat(str, ", feature set ", feature_set, NULL);
             }
         }
 
         /* If we're grouping by node, print its resources */
         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
             if (pcmk_is_set(show_opts, pcmk_show_brief)) {
                 GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
 
                 if (rscs != NULL) {
                     uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
                     out->begin_list(out, NULL, NULL, "%s", str->str);
                     out->begin_list(out, NULL, NULL, "Resources");
 
                     pe__rscs_brief_output(out, rscs, new_show_opts);
 
                     out->end_list(out);
                     out->end_list(out);
 
                     g_list_free(rscs);
                 }
 
             } else {
                 GList *gIter2 = NULL;
 
                 out->begin_list(out, NULL, NULL, "%s", str->str);
                 out->begin_list(out, NULL, NULL, "Resources");
 
                 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
                     pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
 
                     show_opts |= pcmk_show_rsc_only;
                     out->message(out, crm_map_element_name(rsc->xml), show_opts,
                                  rsc, only_node, only_rsc);
                 }
 
                 out->end_list(out);
                 out->end_list(out);
             }
         } else {
             out->list_item(out, NULL, "%s", str->str);
         }
 
         g_string_free(str, TRUE);
         free(node_name);
     } else {
         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
         out->begin_list(out, NULL, NULL, "Node: %s", node_name);
         free(node_name);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *")
 static int
 node_xml(pcmk__output_t *out, va_list args) {
     pe_node_t *node = va_arg(args, pe_node_t *);
     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
     bool full = va_arg(args, int);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     if (full) {
         const char *node_type = "unknown";
         char *length_s = pcmk__itoa(g_list_length(node->details->running_rsc));
         int health = pe__node_health(node);
         const char *health_s = NULL;
         const char *feature_set;
 
         switch (node->details->type) {
             case node_member:
                 node_type = "member";
                 break;
             case node_remote:
                 node_type = "remote";
                 break;
             case node_ping:
                 node_type = "ping";
                 break;
         }
 
         if (health < 0) {
             health_s = "red";
         } else if (health == 0) {
             health_s = "yellow";
         } else {
             health_s = "green";
         }
 
         feature_set = get_node_feature_set(node);
 
         pe__name_and_nvpairs_xml(out, true, "node", 15,
                                  "name", node->details->uname,
                                  "id", node->details->id,
                                  "online", pcmk__btoa(node->details->online),
                                  "standby", pcmk__btoa(node->details->standby),
                                  "standby_onfail", pcmk__btoa(node->details->standby_onfail),
                                  "maintenance", pcmk__btoa(node->details->maintenance),
                                  "pending", pcmk__btoa(node->details->pending),
                                  "unclean", pcmk__btoa(node->details->unclean),
                                  "health", health_s,
                                  "feature_set", feature_set,
                                  "shutdown", pcmk__btoa(node->details->shutdown),
                                  "expected_up", pcmk__btoa(node->details->expected_up),
                                  "is_dc", pcmk__btoa(node->details->is_dc),
                                  "resources_running", length_s,
                                  "type", node_type);
 
         if (pe__is_guest_node(node)) {
             xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out);
             crm_xml_add(xml_node, "id_as_resource", node->details->remote_rsc->container->id);
         }
 
         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
             GList *lpc = NULL;
 
             for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) {
                 pe_resource_t *rsc = (pe_resource_t *) lpc->data;
 
                 show_opts |= pcmk_show_rsc_only;
                 out->message(out, crm_map_element_name(rsc->xml), show_opts,
                              rsc, only_node, only_rsc);
             }
         }
 
         free(length_s);
 
         out->end_list(out);
     } else {
         pcmk__output_xml_create_parent(out, "node",
                                        "name", node->details->uname,
                                        NULL);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
 static int
 node_attribute_text(pcmk__output_t *out, va_list args) {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     bool add_extra = va_arg(args, int);
     int expected_score = va_arg(args, int);
 
     if (add_extra) {
         int v;
 
         if (value == NULL) {
             v = 0;
         } else {
             pcmk__scan_min_int(value, &v, INT_MIN);
         }
         if (v <= 0) {
             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value);
         } else if (v < expected_score) {
             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score);
         } else {
             out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
         }
     } else {
         out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
 static int
 node_attribute_html(pcmk__output_t *out, va_list args) {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     bool add_extra = va_arg(args, int);
     int expected_score = va_arg(args, int);
 
     if (add_extra) {
         int v;
         char *s = crm_strdup_printf("%s: %s", name, value);
         xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL);
 
         if (value == NULL) {
             v = 0;
         } else {
             pcmk__scan_min_int(value, &v, INT_MIN);
         }
 
         pcmk_create_html_node(item_node, "span", NULL, NULL, s);
         free(s);
 
         if (v <= 0) {
             pcmk_create_html_node(item_node, "span", NULL, "bold", "(connectivity is lost)");
         } else if (v < expected_score) {
             char *buf = crm_strdup_printf("(connectivity is degraded -- expected %d", expected_score);
             pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
             free(buf);
         }
     } else {
         out->list_item(out, NULL, "%s: %s", name, value);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr")
 static int
 node_and_op(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
 
     pe_resource_t *rsc = NULL;
     gchar *node_str = NULL;
     char *last_change_str = NULL;
 
     const char *op_rsc = crm_element_value(xml_op, "resource");
     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     int status;
     time_t last_change = 0;
 
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
                        &status, PCMK_EXEC_UNKNOWN);
 
     rsc = pe_find_resource(data_set->resources, op_rsc);
 
     if (rsc) {
         pe_node_t *node = pe__current_node(rsc);
         const char *target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE);
         uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending;
 
         if (node == NULL) {
             node = rsc->pending_node;
         }
 
         node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node,
                                               show_opts, target_role, false);
     } else {
         node_str = crm_strdup_printf("Unknown resource %s", op_rsc);
     }
 
     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                 &last_change) == pcmk_ok) {
         last_change_str = crm_strdup_printf(", %s=%s, exec=%sms",
                                             XML_RSC_OP_LAST_CHANGE,
                                             pcmk__trim(ctime(&last_change)),
                                             crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
     }
 
     out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s",
                    node_str, op_key ? op_key : ID(xml_op),
                    crm_element_value(xml_op, XML_ATTR_UNAME),
                    crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
                    crm_element_value(xml_op, XML_LRM_ATTR_RC),
                    last_change_str ? last_change_str : "",
                    pcmk_exec_status_str(status));
 
     g_free(node_str);
     free(last_change_str);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr")
 static int
 node_and_op_xml(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
 
     pe_resource_t *rsc = NULL;
     const char *op_rsc = crm_element_value(xml_op, "resource");
     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     int status;
     time_t last_change = 0;
     xmlNode *node = NULL;
 
     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
                        &status, PCMK_EXEC_UNKNOWN);
     node = pcmk__output_create_xml_node(out, "operation",
                                         "op", op_key ? op_key : ID(xml_op),
                                         "node", crm_element_value(xml_op, XML_ATTR_UNAME),
                                         "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
                                         "rc", crm_element_value(xml_op, XML_LRM_ATTR_RC),
                                         "status", pcmk_exec_status_str(status),
                                         NULL);
 
     rsc = pe_find_resource(data_set->resources, op_rsc);
 
     if (rsc) {
         const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
         const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE);
         char *agent_tuple = NULL;
 
         agent_tuple = crm_strdup_printf("%s:%s:%s", class,
                                         pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider) ? crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER) : "",
                                         kind);
 
         pcmk__xe_set_props(node, "rsc", rsc_printable_id(rsc),
                            "agent", agent_tuple,
                            NULL);
         free(agent_tuple);
     }
 
     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                 &last_change) == pcmk_ok) {
         pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE,
                            pcmk__trim(ctime(&last_change)),
                            XML_RSC_OP_T_EXEC, crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
                            NULL);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
 static int
 node_attribute_xml(pcmk__output_t *out, va_list args) {
     const char *name = va_arg(args, const char *);
     const char *value = va_arg(args, const char *);
     bool add_extra = va_arg(args, int);
     int expected_score = va_arg(args, int);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute",
                                                    "name", name,
                                                    "value", value,
                                                    NULL);
 
     if (add_extra) {
         char *buf = pcmk__itoa(expected_score);
         crm_xml_add(node, "expected", buf);
         free(buf);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-attribute-list", "pe_working_set_t *", "uint32_t",
                   "bool", "GList *", "GList *")
 static int
 node_attribute_list(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer = va_arg(args, int);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     int rc = pcmk_rc_no_output;
 
     /* Display each node's attributes */
     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = gIter->data;
 
         GList *attr_list = NULL;
         GHashTableIter iter;
         gpointer key;
 
         if (!node || !node->details || !node->details->online) {
             continue;
         }
 
         g_hash_table_iter_init(&iter, node->details->attrs);
         while (g_hash_table_iter_next (&iter, &key, NULL)) {
             attr_list = filter_attr_list(attr_list, key);
         }
 
         if (attr_list == NULL) {
             continue;
         }
 
         if (!pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
             g_list_free(attr_list);
             continue;
         }
 
         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes");
 
         out->message(out, "node", node, show_opts, false, only_node, only_rsc);
 
         for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) {
             const char *name = aIter->data;
             const char *value = NULL;
             int expected_score = 0;
             bool add_extra = false;
 
             value = pe_node_attribute_raw(node, name);
 
             add_extra = add_extra_info(node, node->details->running_rsc,
                                        data_set, name, &expected_score);
 
             /* Print attribute name and value */
             out->message(out, "node-attribute", name, value, add_extra,
                          expected_score);
         }
 
         g_list_free(attr_list);
         out->end_list(out);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-capacity", "pe_node_t *", "const char *")
 static int
 node_capacity(pcmk__output_t *out, va_list args)
 {
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *comment = va_arg(args, const char *);
 
     char *dump_text = crm_strdup_printf("%s: %s capacity:",
                                         comment, pe__node_name(node));
 
     g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text);
     out->list_item(out, NULL, "%s", dump_text);
     free(dump_text);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-capacity", "pe_node_t *", "const char *")
 static int
 node_capacity_xml(pcmk__output_t *out, va_list args)
 {
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *comment = va_arg(args, const char *);
 
     xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "capacity",
                                                        "node", node->details->uname,
                                                        "comment", comment,
                                                        NULL);
     g_hash_table_foreach(node->details->utilization, add_dump_node, xml_node);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-history-list", "pe_working_set_t *", "pe_node_t *", "xmlNodePtr",
                   "GList *", "GList *", "uint32_t", "uint32_t")
 static int
 node_history_list(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     xmlNode *node_state = va_arg(args, xmlNode *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     xmlNode *lrm_rsc = NULL;
     xmlNode *rsc_entry = NULL;
     int rc = pcmk_rc_no_output;
 
     lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
     lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
 
     /* Print history of each of the node's resources */
     for (rsc_entry = first_named_child(lrm_rsc, XML_LRM_TAG_RESOURCE);
          rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
         const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
         pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
 
         /* We can't use is_filtered here to filter group resources.  For is_filtered,
          * we have to decide whether to check the parent or not.  If we check the
          * parent, all elements of a group will always be printed because that's how
          * is_filtered works for groups.  If we do not check the parent, sometimes
          * this will filter everything out.
          *
          * For other resource types, is_filtered is okay.
          */
         if (uber_parent(rsc)->variant == pe_group) {
             if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) &&
                 !pcmk__str_in_list(rsc_printable_id(uber_parent(rsc)), only_rsc, pcmk__str_star_matches)) {
                 continue;
             }
         } else {
             if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
                 continue;
             }
         }
 
         if (!pcmk_is_set(section_opts, pcmk_section_operations)) {
             time_t last_failure = 0;
             int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
                                              NULL, data_set);
 
             if (failcount <= 0) {
                 continue;
             }
 
             if (rc == pcmk_rc_no_output) {
                 rc = pcmk_rc_ok;
                 out->message(out, "node", node, show_opts, false, only_node,
                              only_rsc);
             }
 
             out->message(out, "resource-history", rsc, rsc_id, false,
                          failcount, last_failure, false);
         } else {
             GList *op_list = get_operation_list(rsc_entry);
             pe_resource_t *rsc = pe_find_resource(data_set->resources,
                                                   crm_element_value(rsc_entry, XML_ATTR_ID));
 
             if (op_list == NULL) {
                 continue;
             }
 
             if (rc == pcmk_rc_no_output) {
                 rc = pcmk_rc_ok;
                 out->message(out, "node", node, show_opts, false, only_node,
                              only_rsc);
             }
 
             out->message(out, "resource-operation-list", data_set, rsc, node,
                          op_list, show_opts);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
 static int
 node_list_html(pcmk__output_t *out, va_list args) {
     GList *nodes = va_arg(args, GList *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
 
     int rc = pcmk_rc_no_output;
 
     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (!pcmk__str_in_list(node->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List");
 
         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
 static int
 node_list_text(pcmk__output_t *out, va_list args) {
     GList *nodes = va_arg(args, GList *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer = va_arg(args, int);
 
     /* space-separated lists of node names */
     GString *online_nodes = NULL;
     GString *online_remote_nodes = NULL;
     GString *online_guest_nodes = NULL;
     GString *offline_nodes = NULL;
     GString *offline_remote_nodes = NULL;
 
     int rc = pcmk_rc_no_output;
 
     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
 
         if (!pcmk__str_in_list(node->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             free(node_name);
             continue;
         }
 
         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List");
 
         // Determine whether to display node individually or in a list
         if (node->details->unclean || node->details->pending
             || (node->details->standby_onfail && node->details->online)
             || node->details->standby || node->details->maintenance
             || pcmk_is_set(show_opts, pcmk_show_rscs_by_node)
             || pcmk_is_set(show_opts, pcmk_show_feature_set)
             || (pe__node_health(node) <= 0)) {
             // Display node individually
 
         } else if (node->details->online) {
             // Display online node in a list
             if (pe__is_guest_node(node)) {
                 pcmk__add_word(&online_guest_nodes, 1024, node_name);
 
             } else if (pe__is_remote_node(node)) {
                 pcmk__add_word(&online_remote_nodes, 1024, node_name);
 
             } else {
                 pcmk__add_word(&online_nodes, 1024, node_name);
             }
             free(node_name);
             continue;
 
         } else {
             // Display offline node in a list
             if (pe__is_remote_node(node)) {
                 pcmk__add_word(&offline_remote_nodes, 1024, node_name);
 
             } else if (pe__is_guest_node(node)) {
                 /* ignore offline guest nodes */
 
             } else {
                 pcmk__add_word(&offline_nodes, 1024, node_name);
             }
             free(node_name);
             continue;
         }
 
         /* If we get here, node is in bad state, or we're grouping by node */
         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
         free(node_name);
     }
 
     /* If we're not grouping by node, summarize nodes by status */
     if (online_nodes != NULL) {
         out->list_item(out, "Online", "[ %s ]",
                        (const char *) online_nodes->str);
         g_string_free(online_nodes, TRUE);
     }
     if (offline_nodes != NULL) {
         out->list_item(out, "OFFLINE", "[ %s ]",
                        (const char *) offline_nodes->str);
         g_string_free(offline_nodes, TRUE);
     }
     if (online_remote_nodes) {
         out->list_item(out, "RemoteOnline", "[ %s ]",
                        (const char *) online_remote_nodes->str);
         g_string_free(online_remote_nodes, TRUE);
     }
     if (offline_remote_nodes) {
         out->list_item(out, "RemoteOFFLINE", "[ %s ]",
                        (const char *) offline_remote_nodes->str);
         g_string_free(offline_remote_nodes, TRUE);
     }
     if (online_guest_nodes != NULL) {
         out->list_item(out, "GuestOnline", "[ %s ]",
                        (const char *) online_guest_nodes->str);
         g_string_free(online_guest_nodes, TRUE);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
 static int
 node_list_xml(pcmk__output_t *out, va_list args) {
     GList *nodes = va_arg(args, GList *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
 
     out->begin_list(out, NULL, NULL, "nodes");
     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_node_t *) gIter->data;
 
         if (!pcmk__str_in_list(node->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
     }
     out->end_list(out);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-summary", "pe_working_set_t *", "GList *", "GList *",
                   "uint32_t", "uint32_t", "bool")
 static int
 node_summary(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     uint32_t section_opts = va_arg(args, uint32_t);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_spacer = va_arg(args, int);
 
     xmlNode *node_state = NULL;
     xmlNode *cib_status = pcmk_find_cib_element(data_set->input,
                                                 XML_CIB_TAG_STATUS);
     int rc = pcmk_rc_no_output;
 
     if (xmlChildElementCount(cib_status) == 0) {
         return rc;
     }
 
     for (node_state = first_named_child(cib_status, XML_CIB_TAG_STATE);
          node_state != NULL; node_state = crm_next_same_xml(node_state)) {
         pe_node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
 
         if (!node || !node->details || !node->details->online) {
             continue;
         }
 
         if (!pcmk__str_in_list(node->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc,
                                  pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary");
 
         out->message(out, "node-history-list", data_set, node, node_state,
                      only_node, only_rsc, section_opts, show_opts);
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("node-weight", "pe_resource_t *", "const char *", "const char *", "const char *")
 static int
 node_weight(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     const char *prefix = va_arg(args, const char *);
     const char *uname = va_arg(args, const char *);
     const char *score = va_arg(args, const char *);
 
     if (rsc) {
         out->list_item(out, NULL, "%s: %s allocation score on %s: %s",
                        prefix, rsc->id, uname, score);
     } else {
         out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("node-weight", "pe_resource_t *", "const char *", "const char *", "const char *")
 static int
 node_weight_xml(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     const char *prefix = va_arg(args, const char *);
     const char *uname = va_arg(args, const char *);
     const char *score = va_arg(args, const char *);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "node_weight",
                                                    "function", prefix,
                                                    "node", uname,
                                                    "score", score,
                                                    NULL);
 
     if (rsc) {
         crm_xml_add(node, "id", rsc->id);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
 static int
 op_history_text(pcmk__output_t *out, va_list args) {
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
     const char *task = va_arg(args, const char *);
     const char *interval_ms_s = va_arg(args, const char *);
     int rc = va_arg(args, int);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     char *buf = op_history_string(xml_op, task, interval_ms_s, rc,
                                   pcmk_is_set(show_opts, pcmk_show_timing));
 
     out->list_item(out, NULL, "%s", buf);
 
     free(buf);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
 static int
 op_history_xml(pcmk__output_t *out, va_list args) {
     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
     const char *task = va_arg(args, const char *);
     const char *interval_ms_s = va_arg(args, const char *);
     int rc = va_arg(args, int);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     char *rc_s = pcmk__itoa(rc);
     xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history",
                                                    "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
                                                    "task", task,
                                                    "rc", rc_s,
                                                    "rc_text", services_ocf_exitcode_str(rc),
                                                    NULL);
     free(rc_s);
 
     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
         char *s = crm_strdup_printf("%sms", interval_ms_s);
         crm_xml_add(node, "interval", s);
         free(s);
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_timing)) {
         const char *value = NULL;
         time_t epoch = 0;
 
         if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
                                      &epoch) == pcmk_ok) && (epoch > 0)) {
             char *s = pcmk__epoch2str(&epoch, 0);
             crm_xml_add(node, XML_RSC_OP_LAST_CHANGE, s);
             free(s);
         }
 
         value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
         if (value) {
             char *s = crm_strdup_printf("%sms", value);
             crm_xml_add(node, XML_RSC_OP_T_EXEC, s);
             free(s);
         }
         value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
         if (value) {
             char *s = crm_strdup_printf("%sms", value);
             crm_xml_add(node, XML_RSC_OP_T_QUEUE, s);
             free(s);
         }
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *")
 static int
 promotion_score(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *child_rsc = va_arg(args, pe_resource_t *);
     pe_node_t *chosen = va_arg(args, pe_node_t *);
     const char *score = va_arg(args, const char *);
 
     out->list_item(out, NULL, "%s promotion score on %s: %s",
                    child_rsc->id,
                    chosen? chosen->details->uname : "none",
                    score);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *")
 static int
 promotion_score_xml(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *child_rsc = va_arg(args, pe_resource_t *);
     pe_node_t *chosen = va_arg(args, pe_node_t *);
     const char *score = va_arg(args, const char *);
 
     xmlNodePtr node = pcmk__output_create_xml_node(out, "promotion_score",
                                                    "id", child_rsc->id,
                                                    "score", score,
                                                    NULL);
 
     if (chosen) {
         crm_xml_add(node, "node", chosen->details->uname);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool")
 static int
 resource_config(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool raw = va_arg(args, int);
 
     char *rsc_xml = formatted_xml_buf(rsc, raw);
 
     out->output_xml(out, "xml", rsc_xml);
 
     free(rsc_xml);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool")
 static int
 resource_config_text(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     bool raw = va_arg(args, int);
 
     char *rsc_xml = formatted_xml_buf(rsc, raw);
 
     pcmk__formatted_printf(out, "Resource XML:\n");
     out->output_xml(out, "xml", rsc_xml);
 
     free(rsc_xml);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool")
 static int
 resource_history_text(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     const char *rsc_id = va_arg(args, const char *);
     bool all = va_arg(args, int);
     int failcount = va_arg(args, int);
     time_t last_failure = va_arg(args, time_t);
     bool as_header = va_arg(args, int);
 
     char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure);
 
     if (as_header) {
         out->begin_list(out, NULL, NULL, "%s", buf);
     } else {
         out->list_item(out, NULL, "%s", buf);
     }
 
     free(buf);
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool")
 static int
 resource_history_xml(pcmk__output_t *out, va_list args) {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     const char *rsc_id = va_arg(args, const char *);
     bool all = va_arg(args, int);
     int failcount = va_arg(args, int);
     time_t last_failure = va_arg(args, time_t);
     bool as_header = va_arg(args, int);
 
     xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history",
                                                      "id", rsc_id,
                                                      NULL);
 
     if (rsc == NULL) {
         pcmk__xe_set_bool_attr(node, "orphan", true);
     } else if (all || failcount || last_failure > 0) {
         char *migration_s = pcmk__itoa(rsc->migration_threshold);
 
         pcmk__xe_set_props(node, "orphan", "false",
                            "migration-threshold", migration_s,
                            NULL);
         free(migration_s);
 
         if (failcount > 0) {
             char *s = pcmk__itoa(failcount);
 
             crm_xml_add(node, PCMK__FAIL_COUNT_PREFIX, s);
             free(s);
         }
 
         if (last_failure > 0) {
             char *s = pcmk__epoch2str(&last_failure, 0);
 
             crm_xml_add(node, PCMK__LAST_FAILURE_PREFIX, s);
             free(s);
         }
     }
 
     if (!as_header) {
         pcmk__output_xml_pop_parent(out);
     }
 
     return pcmk_rc_ok;
 }
 
 static void
 print_resource_header(pcmk__output_t *out, uint32_t show_opts)
 {
     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
         /* Active resources have already been printed by node */
         out->begin_list(out, NULL, NULL, "Inactive Resources");
     } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
         out->begin_list(out, NULL, NULL, "Full List of Resources");
     } else {
         out->begin_list(out, NULL, NULL, "Active Resources");
     }
 }
 
 
 PCMK__OUTPUT_ARGS("resource-list", "pe_working_set_t *", "uint32_t", "bool",
                   "GList *", "GList *", "bool")
 static int
 resource_list(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     uint32_t show_opts = va_arg(args, uint32_t);
     bool print_summary = va_arg(args, int);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
     bool print_spacer = va_arg(args, int);
 
     GList *rsc_iter;
     int rc = pcmk_rc_no_output;
     bool printed_header = false;
 
     /* If we already showed active resources by node, and
      * we're not showing inactive resources, we have nothing to do
      */
     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) &&
         !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
         return rc;
     }
 
     /* If we haven't already printed resources grouped by node,
      * and brief output was requested, print resource summary */
     if (pcmk_is_set(show_opts, pcmk_show_brief) && !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
         GList *rscs = pe__filter_rsc_list(data_set->resources, only_rsc);
 
         PCMK__OUTPUT_SPACER_IF(out, print_spacer);
         print_resource_header(out, show_opts);
         printed_header = true;
 
         rc = pe__rscs_brief_output(out, rscs, show_opts);
         g_list_free(rscs);
     }
 
     /* For each resource, display it if appropriate */
     for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
         pe_resource_t *rsc = (pe_resource_t *) rsc_iter->data;
         int x;
 
         /* Complex resources may have some sub-resources active and some inactive */
         gboolean is_active = rsc->fns->active(rsc, TRUE);
         gboolean partially_active = rsc->fns->active(rsc, FALSE);
 
         /* Skip inactive orphans (deleted but still in CIB) */
         if (pcmk_is_set(rsc->flags, pe_rsc_orphan) && !is_active) {
             continue;
 
         /* Skip active resources if we already displayed them by node */
         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
             if (is_active) {
                 continue;
             }
 
         /* Skip primitives already counted in a brief summary */
         } else if (pcmk_is_set(show_opts, pcmk_show_brief) && (rsc->variant == pe_native)) {
             continue;
 
         /* Skip resources that aren't at least partially active,
          * unless we're displaying inactive resources
          */
         } else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
             continue;
 
         } else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) {
             continue;
         }
 
         if (!printed_header) {
             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
             print_resource_header(out, show_opts);
             printed_header = true;
         }
 
         /* Print this resource */
         x = out->message(out, crm_map_element_name(rsc->xml), show_opts, rsc,
                          only_node, only_rsc);
         if (x == pcmk_rc_ok) {
             rc = pcmk_rc_ok;
         }
     }
 
     if (print_summary && rc != pcmk_rc_ok) {
         if (!printed_header) {
             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
             print_resource_header(out, show_opts);
             printed_header = true;
         }
 
         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
             out->list_item(out, NULL, "No inactive resources");
         } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
             out->list_item(out, NULL, "No resources");
         } else {
             out->list_item(out, NULL, "No active resources");
         }
     }
 
     if (printed_header) {
         out->end_list(out);
     }
 
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("resource-operation-list", "pe_working_set_t *", "pe_resource_t *",
                   "pe_node_t *", "GList *", "uint32_t")
 static int
 resource_operation_list(pcmk__output_t *out, va_list args)
 {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     GList *op_list = va_arg(args, GList *);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     GList *gIter = NULL;
     int rc = pcmk_rc_no_output;
 
     /* Print each operation */
     for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *xml_op = (xmlNode *) gIter->data;
         const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
         const char *interval_ms_s = crm_element_value(xml_op,
                                                       XML_LRM_ATTR_INTERVAL_MS);
         const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
         int op_rc_i;
 
         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
 
         /* Display 0-interval monitors as "probe" */
         if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)
             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
             task = "probe";
         }
 
         /* If this is the first printed operation, print heading for resource */
         if (rc == pcmk_rc_no_output) {
             time_t last_failure = 0;
             int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
                                              NULL, data_set);
 
             out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true,
                          failcount, last_failure, true);
             rc = pcmk_rc_ok;
         }
 
         /* Print the operation */
         out->message(out, "op-history", xml_op, task, interval_ms_s,
                      op_rc_i, show_opts);
     }
 
     /* Free the list we created (no need to free the individual items) */
     g_list_free(op_list);
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *")
 static int
 resource_util(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *fn = va_arg(args, const char *);
 
     char *dump_text = crm_strdup_printf("%s: %s utilization on %s:",
                                         fn, rsc->id, pe__node_name(node));
 
     g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text);
     out->list_item(out, NULL, "%s", dump_text);
     free(dump_text);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *")
 static int
 resource_util_xml(pcmk__output_t *out, va_list args)
 {
     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
     pe_node_t *node = va_arg(args, pe_node_t *);
     const char *fn = va_arg(args, const char *);
 
     xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "utilization",
                                                        "resource", rsc->id,
                                                        "node", node->details->uname,
                                                        "function", fn,
                                                        NULL);
     g_hash_table_foreach(rsc->utilization, add_dump_node, xml_node);
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
 static int
 ticket_html(pcmk__output_t *out, va_list args) {
     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
 
     if (ticket->last_granted > -1) {
         char *time = pcmk__format_named_time("last-granted",
                                              ticket->last_granted);
 
         out->list_item(out, NULL, "%s:\t%s%s %s", ticket->id,
                        ticket->granted ? "granted" : "revoked",
                        ticket->standby ? " [standby]" : "",
                        time);
         free(time);
     } else {
         out->list_item(out, NULL, "%s:\t%s%s", ticket->id,
                        ticket->granted ? "granted" : "revoked",
                        ticket->standby ? " [standby]" : "");
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
 static int
 ticket_text(pcmk__output_t *out, va_list args) {
     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
 
     if (ticket->last_granted > -1) {
         char *time = pcmk__format_named_time("last-granted",
                                              ticket->last_granted);
 
         out->list_item(out, ticket->id, "%s%s %s",
                        ticket->granted ? "granted" : "revoked",
                        ticket->standby ? " [standby]" : "",
                        time);
         free(time);
     } else {
         out->list_item(out, ticket->id, "%s%s",
                        ticket->granted ? "granted" : "revoked",
                        ticket->standby ? " [standby]" : "");
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
 static int
 ticket_xml(pcmk__output_t *out, va_list args) {
     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
 
     xmlNodePtr node = NULL;
 
     node = pcmk__output_create_xml_node(out, "ticket",
                                         "id", ticket->id,
                                         "status", ticket->granted ? "granted" : "revoked",
                                         "standby", pcmk__btoa(ticket->standby),
                                         NULL);
 
     if (ticket->last_granted > -1) {
         char *buf = pcmk__epoch2str(&ticket->last_granted, 0);
 
         crm_xml_add(node, "last-granted", buf);
         free(buf);
     }
 
     return pcmk_rc_ok;
 }
 
 PCMK__OUTPUT_ARGS("ticket-list", "pe_working_set_t *", "bool")
 static int
 ticket_list(pcmk__output_t *out, va_list args) {
     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
     bool print_spacer = va_arg(args, int);
 
     GHashTableIter iter;
     gpointer key, value;
 
     if (g_hash_table_size(data_set->tickets) == 0) {
         return pcmk_rc_no_output;
     }
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
 
     /* Print section heading */
     out->begin_list(out, NULL, NULL, "Tickets");
 
     /* Print each ticket */
     g_hash_table_iter_init(&iter, data_set->tickets);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         pe_ticket_t *ticket = (pe_ticket_t *) value;
         out->message(out, "ticket", ticket);
     }
 
     /* Close section */
     out->end_list(out);
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "ban", "default", ban_text },
     { "ban", "html", ban_html },
     { "ban", "xml", ban_xml },
     { "ban-list", "default", ban_list },
     { "bundle", "default", pe__bundle_text },
     { "bundle", "xml",  pe__bundle_xml },
     { "bundle", "html",  pe__bundle_html },
     { "clone", "default", pe__clone_default },
     { "clone", "xml",  pe__clone_xml },
     { "cluster-counts", "default", cluster_counts_text },
     { "cluster-counts", "html", cluster_counts_html },
     { "cluster-counts", "xml", cluster_counts_xml },
     { "cluster-dc", "default", cluster_dc_text },
     { "cluster-dc", "html", cluster_dc_html },
     { "cluster-dc", "xml", cluster_dc_xml },
     { "cluster-options", "default", cluster_options_text },
     { "cluster-options", "html", cluster_options_html },
     { "cluster-options", "log", cluster_options_log },
     { "cluster-options", "xml", cluster_options_xml },
     { "cluster-summary", "default", cluster_summary },
     { "cluster-summary", "html", cluster_summary_html },
     { "cluster-stack", "default", cluster_stack_text },
     { "cluster-stack", "html", cluster_stack_html },
     { "cluster-stack", "xml", cluster_stack_xml },
     { "cluster-times", "default", cluster_times_text },
     { "cluster-times", "html", cluster_times_html },
     { "cluster-times", "xml", cluster_times_xml },
     { "failed-action", "default", failed_action_default },
     { "failed-action", "xml", failed_action_xml },
     { "failed-action-list", "default", failed_action_list },
     { "group", "default",  pe__group_default},
     { "group", "xml",  pe__group_xml },
     { "maint-mode", "text", cluster_maint_mode_text },
     { "node", "default", node_text },
     { "node", "html", node_html },
     { "node", "xml", node_xml },
     { "node-and-op", "default", node_and_op },
     { "node-and-op", "xml", node_and_op_xml },
     { "node-capacity", "default", node_capacity },
     { "node-capacity", "xml", node_capacity_xml },
     { "node-history-list", "default", node_history_list },
     { "node-list", "default", node_list_text },
     { "node-list", "html", node_list_html },
     { "node-list", "xml", node_list_xml },
     { "node-weight", "default", node_weight },
     { "node-weight", "xml", node_weight_xml },
     { "node-attribute", "default", node_attribute_text },
     { "node-attribute", "html", node_attribute_html },
     { "node-attribute", "xml", node_attribute_xml },
     { "node-attribute-list", "default", node_attribute_list },
     { "node-summary", "default", node_summary },
     { "op-history", "default", op_history_text },
     { "op-history", "xml", op_history_xml },
     { "primitive", "default",  pe__resource_text },
     { "primitive", "xml",  pe__resource_xml },
     { "primitive", "html",  pe__resource_html },
     { "promotion-score", "default", promotion_score },
     { "promotion-score", "xml", promotion_score_xml },
     { "resource-config", "default", resource_config },
     { "resource-config", "text", resource_config_text },
     { "resource-history", "default", resource_history_text },
     { "resource-history", "xml", resource_history_xml },
     { "resource-list", "default", resource_list },
     { "resource-operation-list", "default", resource_operation_list },
     { "resource-util", "default", resource_util },
     { "resource-util", "xml", resource_util_xml },
     { "ticket", "default", ticket_text },
     { "ticket", "html", ticket_html },
     { "ticket", "xml", ticket_xml },
     { "ticket-list", "default", ticket_list },
 
     { NULL, NULL, NULL }
 };
 
 void
 pe__register_messages(pcmk__output_t *out) {
     pcmk__register_messages(out, fmt_functions);
 }
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index eb8d7cbcae..7065dacdb5 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2166 +1,2180 @@
 /*
  * Copyright 2004-2022 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 
 #include <crm/crm.h>
 
 #include <stdint.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <signal.h>
 #include <sys/utsname.h>
 
 #include <crm/msg_xml.h>
 #include <crm/services.h>
 #include <crm/lrmd.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/internal.h>  // pcmk__ends_with_ext()
 #include <crm/common/ipc.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/output.h>
 #include <crm/common/output_internal.h>
 #include <crm/common/results.h>
 #include <crm/common/util.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <crm/cib/internal.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <pacemaker-internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 
 #include "crm_mon.h"
 
 #define SUMMARY "Provides a summary of cluster's current state.\n\n" \
                 "Outputs varying levels of detail in a number of different formats."
 
 /*
  * Definitions indicating which items to print
  */
 
 static uint32_t show;
 static uint32_t show_opts = pcmk_show_pending;
 
 /*
  * Definitions indicating how to output
  */
 
 static mon_output_format_t output_format = mon_output_unset;
 
 /* other globals */
 static GIOChannel *io_channel = NULL;
 static GMainLoop *mainloop = NULL;
 static guint reconnect_timer = 0;
 static mainloop_timer_t *refresh_timer = NULL;
 
+static enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
 static cib_t *cib = NULL;
 static stonith_t *st = NULL;
 static xmlNode *current_cib = NULL;
 
 static GError *error = NULL;
 static pcmk__common_args_t *args = NULL;
 static pcmk__output_t *out = NULL;
 static GOptionContext *context = NULL;
 static gchar **processed_args = NULL;
 
 static time_t last_refresh = 0;
 volatile crm_trigger_t *refresh_trigger = NULL;
 
 static enum pcmk__fence_history fence_history = pcmk__fence_history_none;
-static gboolean on_remote_node = FALSE;
-static gboolean use_cib_native = FALSE;
 
 int interactive_fence_level = 0;
 
 static pcmk__supported_format_t formats[] = {
 #if CURSES_ENABLED
     CRM_MON_SUPPORTED_FORMAT_CURSES,
 #endif
     PCMK__SUPPORTED_FORMAT_HTML,
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
+PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *", "int")
+static int
+crm_mon_disconnected_default(pcmk__output_t *out, va_list args)
+{
+    return pcmk_rc_no_output;
+}
+
+PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *", "int")
+static int
+crm_mon_disconnected_html(pcmk__output_t *out, va_list args)
+{
+    const char *desc = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
+
+    if (out->dest != stdout) {
+        out->reset(out);
+    }
+
+    pcmk__output_create_xml_text_node(out, "span", "Not connected to CIB");
+
+    if (desc != NULL) {
+        pcmk__output_create_xml_text_node(out, "span", ": ");
+        pcmk__output_create_xml_text_node(out, "span", desc);
+    }
+
+    if (state != pcmk_pacemakerd_state_invalid) {
+        const char *state_s = pcmk__pcmkd_state_enum2friendly(state);
+
+        pcmk__output_create_xml_text_node(out, "span", " (");
+        pcmk__output_create_xml_text_node(out, "span", state_s);
+        pcmk__output_create_xml_text_node(out, "span", ")");
+    }
+
+    out->finish(out, CRM_EX_DISCONNECT, true, NULL);
+    return pcmk_rc_ok;
+}
+
+PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *", "int")
+static int
+crm_mon_disconnected_text(pcmk__output_t *out, va_list args)
+{
+    const char *desc = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
+    int rc = pcmk_rc_ok;
+
+    if (out->dest != stdout) {
+        out->reset(out);
+    }
+
+    if (state != pcmk_pacemakerd_state_invalid) {
+        rc = out->info(out, "Not connected to CIB%s%s (%s)",
+                       (desc != NULL)? ": " : "", pcmk__s(desc, ""),
+                       pcmk__pcmkd_state_enum2friendly(state));
+    } else {
+        rc = out->info(out, "Not connected to CIB%s%s",
+                       (desc != NULL)? ": " : "", pcmk__s(desc, ""));
+    }
+
+    out->finish(out, CRM_EX_DISCONNECT, true, NULL);
+    return rc;
+}
+
+PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *", "int")
+static int
+crm_mon_disconnected_xml(pcmk__output_t *out, va_list args)
+{
+    const char *desc = va_arg(args, const char *);
+    enum pcmk_pacemakerd_state state =
+        (enum pcmk_pacemakerd_state) va_arg(args, int);
+    const char *state_s = NULL;
+
+    if (out->dest != stdout) {
+        out->reset(out);
+    }
+
+    if (state != pcmk_pacemakerd_state_invalid) {
+        state_s = pcmk_pacemakerd_api_daemon_state_enum2text(state);
+    }
+
+    pcmk__output_create_xml_node(out, "crm-mon-disconnected",
+                                 XML_ATTR_DESC, desc,
+                                 "pacemakerd-state", state_s,
+                                 NULL);
+
+    out->finish(out, CRM_EX_DISCONNECT, true, NULL);
+    return pcmk_rc_ok;
+}
+
+static pcmk__message_entry_t fmt_functions[] = {
+    { "crm-mon-disconnected", "default", crm_mon_disconnected_default },
+    { "crm-mon-disconnected", "html", crm_mon_disconnected_html },
+    { "crm-mon-disconnected", "text", crm_mon_disconnected_text },
+    { "crm-mon-disconnected", "xml", crm_mon_disconnected_xml },
+    { NULL, NULL, NULL },
+};
+
 /* Define exit codes for monitoring-compatible output
  * For nagios plugins, the possibilities are
  * OK=0, WARN=1, CRIT=2, and UNKNOWN=3
  */
 #define MON_STATUS_WARN    CRM_EX_ERROR
 #define MON_STATUS_CRIT    CRM_EX_INVALID_PARAM
 #define MON_STATUS_UNKNOWN CRM_EX_UNIMPLEMENT_FEATURE
 
 #define RECONNECT_MSECS 5000
 
 struct {
     guint reconnect_ms;
     gboolean daemonize;
     gboolean fence_connect;
     gboolean one_shot;
     gboolean print_pending;
     gboolean show_bans;
     gboolean watch_fencing;
     char *pid_file;
     char *external_agent;
     char *external_recipient;
     char *neg_location_prefix;
     char *only_node;
     char *only_rsc;
     GSList *user_includes_excludes;
     GSList *includes_excludes;
 } options = {
     .fence_connect = TRUE,
     .reconnect_ms = RECONNECT_MSECS
 };
 
 static crm_exit_t clean_up(crm_exit_t exit_code);
 static void crm_diff_update(const char *event, xmlNode * msg);
 static void clean_up_on_connection_failure(int rc);
 static int mon_refresh_display(gpointer user_data);
 static int setup_cib_connection(void);
-static int fencing_connect(void);
-static int pacemakerd_status(void);
+static int setup_fencer_connection(void);
+static int setup_api_connections(void);
 static void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
 static void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
 static void refresh_after_event(gboolean data_updated, gboolean enforce);
 
 static uint32_t
 all_includes(mon_output_format_t fmt) {
     if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
         return ~pcmk_section_options;
     } else {
         return pcmk_section_all;
     }
 }
 
 static uint32_t
 default_includes(mon_output_format_t fmt) {
     switch (fmt) {
         case mon_output_monitor:
         case mon_output_plain:
         case mon_output_console:
         case mon_output_html:
         case mon_output_cgi:
             return pcmk_section_summary
                    |pcmk_section_nodes
                    |pcmk_section_resources
                    |pcmk_section_failures;
 
         case mon_output_xml:
         case mon_output_legacy_xml:
             return all_includes(fmt);
 
         default:
             return 0;
     }
 }
 
 struct {
     const char *name;
     uint32_t bit;
 } sections[] = {
     { "attributes", pcmk_section_attributes },
     { "bans", pcmk_section_bans },
     { "counts", pcmk_section_counts },
     { "dc", pcmk_section_dc },
     { "failcounts", pcmk_section_failcounts },
     { "failures", pcmk_section_failures },
     { PCMK__VALUE_FENCING, pcmk_section_fencing_all },
     { "fencing-failed", pcmk_section_fence_failed },
     { "fencing-pending", pcmk_section_fence_pending },
     { "fencing-succeeded", pcmk_section_fence_worked },
     { "maint-mode", pcmk_section_maint_mode },
     { "nodes", pcmk_section_nodes },
     { "operations", pcmk_section_operations },
     { "options", pcmk_section_options },
     { "resources", pcmk_section_resources },
     { "stack", pcmk_section_stack },
     { "summary", pcmk_section_summary },
     { "tickets", pcmk_section_tickets },
     { "times", pcmk_section_times },
     { NULL }
 };
 
 static uint32_t
 find_section_bit(const char *name) {
     for (int i = 0; sections[i].name != NULL; i++) {
         if (pcmk__str_eq(sections[i].name, name, pcmk__str_casei)) {
             return sections[i].bit;
         }
     }
 
     return 0;
 }
 
 static gboolean
 apply_exclude(const gchar *excludes, GError **error) {
     char **parts = NULL;
     gboolean result = TRUE;
 
     parts = g_strsplit(excludes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         uint32_t bit = find_section_bit(*s);
 
         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
             show = 0;
         } else if (pcmk__str_eq(*s, PCMK__VALUE_NONE, pcmk__str_none)) {
             show = all_includes(output_format);
         } else if (bit != 0) {
             show &= ~bit;
         } else {
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         "--exclude options: all, attributes, bans, counts, dc, "
                         "failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, maint-mode, nodes, "
                         PCMK__VALUE_NONE ", operations, options, resources, "
                         "stack, summary, tickets, times");
             result = FALSE;
             break;
         }
     }
     g_strfreev(parts);
     return result;
 }
 
 static gboolean
 apply_include(const gchar *includes, GError **error) {
     char **parts = NULL;
     gboolean result = TRUE;
 
     parts = g_strsplit(includes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         uint32_t bit = find_section_bit(*s);
 
         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
             show = all_includes(output_format);
         } else if (pcmk__starts_with(*s, "bans")) {
             show |= pcmk_section_bans;
             if (options.neg_location_prefix != NULL) {
                 free(options.neg_location_prefix);
                 options.neg_location_prefix = NULL;
             }
 
             if (strlen(*s) > 4 && (*s)[4] == ':') {
                 options.neg_location_prefix = strdup(*s+5);
             }
         } else if (pcmk__str_any_of(*s, "default", "defaults", NULL)) {
             show |= default_includes(output_format);
         } else if (pcmk__str_eq(*s, PCMK__VALUE_NONE, pcmk__str_none)) {
             show = 0;
         } else if (bit != 0) {
             show |= bit;
         } else {
             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         "--include options: all, attributes, bans[:PREFIX], counts, dc, "
                         "default, failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, maint-mode, nodes, "
                         PCMK__VALUE_NONE ", operations, options, resources, "
                         "stack, summary, tickets, times");
             result = FALSE;
             break;
         }
     }
     g_strfreev(parts);
     return result;
 }
 
 static gboolean
 apply_include_exclude(GSList *lst, GError **error) {
     gboolean rc = TRUE;
     GSList *node = lst;
 
     while (node != NULL) {
         char *s = node->data;
 
         if (pcmk__starts_with(s, "--include=")) {
             rc = apply_include(s+10, error);
         } else if (pcmk__starts_with(s, "-I=")) {
             rc = apply_include(s+3, error);
         } else if (pcmk__starts_with(s, "--exclude=")) {
             rc = apply_exclude(s+10, error);
         } else if (pcmk__starts_with(s, "-U=")) {
             rc = apply_exclude(s+3, error);
         }
 
         if (rc != TRUE) {
             break;
         }
 
         node = node->next;
     }
 
     return rc;
 }
 
 static gboolean
 user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.includes_excludes = g_slist_append(options.includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     pcmk__str_update(&args->output_ty, "html");
     output_format = mon_output_cgi;
     options.one_shot = TRUE;
     return TRUE;
 }
 
 static gboolean
 as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     pcmk__str_update(&args->output_dest, optarg);
     pcmk__str_update(&args->output_ty, "html");
     output_format = mon_output_html;
     umask(S_IWGRP | S_IWOTH);
     return TRUE;
 }
 
 static gboolean
 as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     pcmk__str_update(&args->output_ty, "text");
     output_format = mon_output_monitor;
     options.one_shot = TRUE;
     return TRUE;
 }
 
 static gboolean
 as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     pcmk__str_update(&args->output_ty, "xml");
     output_format = mon_output_legacy_xml;
     return TRUE;
 }
 
 static gboolean
 fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (optarg == NULL) {
         interactive_fence_level = 2;
     } else {
         pcmk__scan_min_int(optarg, &interactive_fence_level, 0);
     }
 
     switch (interactive_fence_level) {
         case 3:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             return include_exclude_cb("--include", PCMK__VALUE_FENCING, data,
                                       err);
 
         case 2:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             return include_exclude_cb("--include", PCMK__VALUE_FENCING, data,
                                       err);
 
         case 1:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
 
         case 0:
             options.fence_connect = FALSE;
             fence_history = pcmk__fence_history_none;
             return include_exclude_cb("--exclude", PCMK__VALUE_FENCING, data,
                                       err);
 
         default:
             g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
             return FALSE;
     }
 }
 
 static gboolean
 group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_rscs_by_node;
     return TRUE;
 }
 
 static gboolean
 hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return user_include_exclude_cb("--exclude", "summary", data, err);
 }
 
 static gboolean
 inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_inactive_rscs;
     return TRUE;
 }
 
 static gboolean
 no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     output_format = mon_output_plain;
     return TRUE;
 }
 
 static gboolean
 print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_brief;
     return TRUE;
 }
 
 static gboolean
 print_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_details;
     return TRUE;
 }
 
 static gboolean
 print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     show_opts |= pcmk_show_timing;
     return user_include_exclude_cb("--include", "operations", data, err);
 }
 
 static gboolean
 reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     int rc = crm_get_msec(optarg);
 
     if (rc == -1) {
         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
         return FALSE;
     } else {
         options.reconnect_ms = crm_parse_interval_spec(optarg);
     }
 
     return TRUE;
 }
 
 static gboolean
 show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return user_include_exclude_cb("--include", "attributes", data, err);
 }
 
 static gboolean
 show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (optarg != NULL) {
         char *s = crm_strdup_printf("bans:%s", optarg);
         gboolean rc = user_include_exclude_cb("--include", s, data, err);
         free(s);
         return rc;
     } else {
         return user_include_exclude_cb("--include", "bans", data, err);
     }
 }
 
 static gboolean
 show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return user_include_exclude_cb("--include", "failcounts", data, err);
 }
 
 static gboolean
 show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return user_include_exclude_cb("--include", "failcounts,operations", data, err);
 }
 
 static gboolean
 show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return user_include_exclude_cb("--include", "tickets", data, err);
 }
 
 static gboolean
 use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     setenv("CIB_file", optarg, 1);
     options.one_shot = TRUE;
     return TRUE;
 }
 
 #define INDENT "                                    "
 
 /* *INDENT-OFF* */
 static GOptionEntry addl_entries[] = {
     { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb,
       "Update frequency (default is 5 seconds)",
       "TIMESPEC" },
 
     { "one-shot", '1', 0, G_OPTION_ARG_NONE, &options.one_shot,
       "Display the cluster status once on the console and exit",
       NULL },
 
     { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
       "Run in the background as a daemon.\n"
       INDENT "Requires at least one of --output-to and --external-agent.",
       NULL },
 
     { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file,
       "(Advanced) Daemon pid file location",
       "FILE" },
 
     { "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent,
       "A program to run when resource operations take place",
       "FILE" },
 
     { "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient,
       "A recipient for your program (assuming you want the program to send something to someone).",
       "RCPT" },
 
     { "watch-fencing", 'W', 0, G_OPTION_ARG_NONE, &options.watch_fencing,
       "Listen for fencing events. For use with --external-agent.",
       NULL },
 
     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
       NULL,
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry display_entries[] = {
     { "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to include in the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to exclude from the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "node", 0, 0, G_OPTION_ARG_STRING, &options.only_node,
       "When displaying information about nodes, show only what's related to the given\n"
       INDENT "node, or to all nodes tagged with the given tag",
       "NODE" },
 
     { "resource", 0, 0, G_OPTION_ARG_STRING, &options.only_rsc,
       "When displaying information about resources, show only what's related to the given\n"
       INDENT "resource, or to all resources tagged with the given tag",
       "RSC" },
 
     { "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb,
       "Group resources by node",
       NULL },
 
     { "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb,
       "Display inactive resources",
       NULL },
 
     { "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb,
       "Display resource fail counts",
       NULL },
 
     { "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb,
       "Display resource operation history",
       NULL },
 
     { "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb,
       "Display resource operation history with timing details",
       NULL },
 
     { "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb,
       "Display cluster tickets",
       NULL },
 
     { "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb,
       "Show fence history:\n"
       INDENT "0=off, 1=failures and pending (default without option),\n"
       INDENT "2=add successes (default without value for option),\n"
       INDENT "3=show full history without reduction to most recent of each flavor",
       "LEVEL" },
 
     { "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb,
       "Display negative location constraints [optionally filtered by id prefix]",
       NULL },
 
     { "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb,
       "Display node attributes",
       NULL },
 
     { "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb,
       "Hide all headers",
       NULL },
 
     { "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_detail_cb,
       "Show more details (node IDs, individual clone instances)",
       NULL },
 
     { "brief", 'b', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_brief_cb,
       "Brief output",
       NULL },
 
     { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending,
       "Display pending state if 'record-pending' is enabled",
       NULL },
 
     { "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_simple_cb,
       "Display the cluster status once as a simple one line output (suitable for nagios)",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry deprecated_entries[] = {
     { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
       "Write cluster status to the named HTML file.\n"
       INDENT "Use --output-as=html --output-to=FILE instead.",
       "FILE" },
 
     { "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb,
       "Write cluster status as XML to stdout. This will enable one-shot mode.\n"
       INDENT "Use --output-as=xml instead.",
       NULL },
 
     { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
       "Disable the use of ncurses.\n"
       INDENT "Use --output-as=text instead.",
       NULL },
 
     { "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb,
       "Web mode with output suitable for CGI (preselected when run as *.cgi).\n"
       INDENT "Use --output-as=html --html-cgi instead.",
       NULL },
 
     { NULL }
 };
 /* *INDENT-ON* */
 
 /* Reconnect to the CIB and fencing agent after reconnect_ms has passed.  This sounds
  * like it would be more broadly useful, but only ever happens after a disconnect via
  * mon_cib_connection_destroy.
  */
 static gboolean
 reconnect_after_timeout(gpointer data)
 {
 #if CURSES_ENABLED
     if (output_format == mon_output_console) {
         clear();
         refresh();
     }
 #endif
 
-    out->info(out, "Reconnecting...");
-    if (pacemakerd_status() == pcmk_rc_ok) {
-        fencing_connect();
-        if (setup_cib_connection() == pcmk_rc_ok) {
-            /* trigger redrawing the screen (needs reconnect_timer == 0) */
-            reconnect_timer = 0;
-            refresh_after_event(FALSE, TRUE);
-            return G_SOURCE_REMOVE;
-        }
+    out->transient(out, "Reconnecting...");
+    if (setup_api_connections() == pcmk_rc_ok) {
+        // Trigger redrawing the screen (needs reconnect_timer == 0)
+        reconnect_timer = 0;
+        refresh_after_event(FALSE, TRUE);
+        return G_SOURCE_REMOVE;
     }
 
+    out->message(out, "crm-mon-disconnected",
+                 "Latest connection attempt failed", pcmkd_state);
+
     reconnect_timer = g_timeout_add(options.reconnect_ms,
                                     reconnect_after_timeout, NULL);
     return G_SOURCE_REMOVE;
 }
 
 /* Called from various places when we are disconnected from the CIB or from the
  * fencing agent.  If the CIB connection is still valid, this function will also
  * attempt to sign off and reconnect.
  */
 static void
 mon_cib_connection_destroy(gpointer user_data)
 {
-    out->info(out, "Connection to the cluster-daemons terminated");
+    const char *msg = "Connection to the cluster lost";
+
+    pcmkd_state = pcmk_pacemakerd_state_invalid;
+
+    /* No crm-mon-disconnected message for console; a working implementation
+     * is not currently worth the effort
+     */
+    out->transient(out, "%s", msg);
+
+    out->message(out, "crm-mon-disconnected", msg, pcmkd_state);
 
     if (refresh_timer != NULL) {
         /* we'll trigger a refresh after reconnect */
         mainloop_timer_stop(refresh_timer);
     }
     if (reconnect_timer) {
         /* we'll trigger a new reconnect-timeout at the end */
         g_source_remove(reconnect_timer);
         reconnect_timer = 0;
     }
 
     /* the client API won't properly reconnect notifications if they are still
      * in the table - so remove them
      */
     stonith_api_delete(st);
     st = NULL;
 
     if (cib) {
         cib->cmds->signoff(cib);
         reconnect_timer = g_timeout_add(options.reconnect_ms,
                                         reconnect_after_timeout, NULL);
     }
-    return;
 }
 
 /* Signal handler installed into the mainloop for normal program shutdown */
 static void
 mon_shutdown(int nsig)
 {
     clean_up(CRM_EX_OK);
 }
 
 #if CURSES_ENABLED
 static volatile sighandler_t ncurses_winch_handler;
 
 /* Signal handler installed the regular way (not into the main loop) for when
  * the screen is resized.  Commonly, this happens when running in an xterm and
  * the user changes its size.
  */
 static void
 mon_winresize(int nsig)
 {
     static int not_done;
     int lines = 0, cols = 0;
 
     if (!not_done++) {
         if (ncurses_winch_handler)
             /* the original ncurses WINCH signal handler does the
              * magic of retrieving the new window size;
              * otherwise, we'd have to use ioctl or tgetent */
             (*ncurses_winch_handler) (SIGWINCH);
         getmaxyx(stdscr, lines, cols);
         resizeterm(lines, cols);
         /* Alert the mainloop code we'd like the refresh_trigger to run next
          * time the mainloop gets around to checking.
          */
         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
     }
     not_done--;
 }
 #endif
 
 static int
-fencing_connect(void)
+setup_fencer_connection(void)
 {
     int rc = pcmk_ok;
 
     if (options.fence_connect && st == NULL) {
         st = stonith_api_new();
     }
 
     if (!options.fence_connect || st == NULL || st->state != stonith_disconnected) {
         return rc;
     }
 
     rc = st->cmds->connect(st, crm_system_name, NULL);
     if (rc == pcmk_ok) {
         crm_trace("Setting up stonith callbacks");
         if (options.watch_fencing) {
             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
                                             mon_st_callback_event);
             st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
         } else {
             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
                                             mon_st_callback_display);
             st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
         }
     } else {
         stonith_api_delete(st);
         st = NULL;
     }
 
     return rc;
 }
 
 static int
 setup_cib_connection(void)
 {
     int rc = pcmk_rc_ok;
 
     CRM_CHECK(cib != NULL, return EINVAL);
 
     if (cib->state != cib_disconnected) {
         // Already connected with notifications registered for CIB updates
         return rc;
     }
 
     rc = cib__signon_query(out, &cib, &current_cib);
 
     if (rc == pcmk_rc_ok) {
         rc = pcmk_legacy2rc(cib->cmds->set_connection_dnotify(cib,
             mon_cib_connection_destroy));
         if (rc == EPROTONOSUPPORT) {
             out->err(out,
-                     "Notification setup not supported, won't be "
-                     "able to reconnect after failure");
-            if (output_format == mon_output_console) {
-                sleep(2);
-            }
+                     "CIB client does not support connection loss "
+                     "notifications; crm_mon will be unable to reconnect after "
+                     "connection loss");
             rc = pcmk_rc_ok;
         }
 
         if (rc == pcmk_rc_ok) {
             cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY,
                                            crm_diff_update);
             rc = pcmk_legacy2rc(cib->cmds->add_notify_callback(cib,
                                     T_CIB_DIFF_NOTIFY, crm_diff_update));
         }
 
         if (rc != pcmk_rc_ok) {
-            out->err(out, "Notification setup failed, could not monitor CIB actions");
+            if (rc == EPROTONOSUPPORT) {
+                out->err(out,
+                         "CIB client does not support CIB diff "
+                         "notifications");
+            } else {
+                out->err(out, "CIB diff notification setup failed");
+            }
+
+            out->err(out, "Cannot monitor CIB changes; exiting");
             cib__clean_up_connection(&cib);
             stonith_api_delete(st);
             st = NULL;
         }
     }
     return rc;
 }
 
 /* This is used to set up the fencing options after the interactive UI has been stared.
  * fence_history_cb can't be used because it builds up a list of includes/excludes that
  * then have to be processed with apply_include_exclude and that could affect other
  * things.
  */
 static void
 set_fencing_options(int level)
 {
     switch (level) {
         case 3:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             show |= pcmk_section_fencing_all;
             break;
 
         case 2:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             show |= pcmk_section_fencing_all;
             break;
 
         case 1:
             options.fence_connect = TRUE;
             fence_history = pcmk__fence_history_full;
             show |= pcmk_section_fence_failed | pcmk_section_fence_pending;
             break;
 
         default:
             interactive_fence_level = 0;
             options.fence_connect = FALSE;
             fence_history = pcmk__fence_history_none;
             show &= ~pcmk_section_fencing_all;
             break;
     }
 }
 
-/* Before trying to connect to fencer or cib check for state of
-   pacemakerd - just no sense in trying till pacemakerd has
-   taken care of starting all the sub-processes
-
-   Only noteworthy thing to show here is when pacemakerd is
-   waiting for startup-trigger from SBD.
- */
-static void
-pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api,
-                    enum pcmk_ipc_event event_type, crm_exit_t status,
-                    void *event_data, void *user_data)
+static int
+setup_api_connections(void)
 {
-    pcmk_pacemakerd_api_reply_t *reply = event_data;
-    enum pcmk_pacemakerd_state *state =
-        (enum pcmk_pacemakerd_state *) user_data;
-
-    /* we are just interested in the latest reply */
-    *state = pcmk_pacemakerd_state_invalid;
-
-    switch (event_type) {
-        case pcmk_ipc_event_reply:
-            break;
+    int rc = pcmk_rc_ok;
 
-        default:
-            return;
-    }
+    CRM_CHECK(cib != NULL, return EINVAL);
 
-    if (status != CRM_EX_OK) {
-        out->err(out, "Bad reply from pacemakerd: %s",
-                 crm_exit_str(status));
-        return;
+    if (cib->state != cib_disconnected) {
+        return rc;
     }
 
-    if (reply->reply_type != pcmk_pacemakerd_reply_ping) {
-        out->err(out, "Unknown reply type %d from pacemakerd",
-                 reply->reply_type);
-    } else {
-        if ((reply->data.ping.last_good != (time_t) 0) &&
-            (reply->data.ping.status == pcmk_rc_ok)) {
-            *state = reply->data.ping.state;
+    if (cib->variant == cib_native) {
+        rc = pcmk__pacemakerd_status(out, crm_system_name,
+                                     options.reconnect_ms / 2, false,
+                                     &pcmkd_state);
+        if (rc != pcmk_rc_ok) {
+            return rc;
         }
-    }
-}
 
-static int
-pacemakerd_status(void)
-{
-    int rc = pcmk_rc_ok;
-    pcmk_ipc_api_t *pacemakerd_api = NULL;
-    enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
+        switch (pcmkd_state) {
+            case pcmk_pacemakerd_state_running:
+            case pcmk_pacemakerd_state_remote:
+            case pcmk_pacemakerd_state_shutting_down:
+                /* Fencer and CIB may still be available while shutting down or
+                 * running on a Pacemaker Remote node
+                 */
+                break;
+            default:
+                // Fencer and CIB are definitely unavailable
+                return ENOTCONN;
+        }
 
-    if (!use_cib_native) {
-        /* we don't need fully functional pacemakerd otherwise */
-        return rc;
-    }
-    if (cib != NULL &&
-        (cib->state == cib_connected_query ||
-         cib->state == cib_connected_command)) {
-        /* As long as we have a cib-connection let's go with
-         * that to fetch further cluster-status and avoid
-         * unnecessary pings to pacemakerd.
-         * If cluster is going down and fencer is down already
-         * this will lead to a silently failing fencer reconnect.
-         * On cluster startup we shouldn't see this situation
-         * as first we do is wait for pacemakerd to report all
-         * daemons running.
-         */
-        return rc;
-    }
-    rc = pcmk_new_ipc_api(&pacemakerd_api, pcmk_ipc_pacemakerd);
-    if (pacemakerd_api == NULL) {
-        out->err(out, "Could not connect to pacemakerd: %s",
-                 pcmk_rc_str(rc));
-        /* this is unrecoverable so return with rc we have */
-        return rc;
+        setup_fencer_connection();
     }
-    pcmk_register_ipc_callback(pacemakerd_api, pacemakerd_event_cb, (void *) &state);
-    rc = pcmk_connect_ipc(pacemakerd_api, pcmk_ipc_dispatch_poll);
-    switch (rc) {
-        case pcmk_rc_ok:
-            rc = pcmk_pacemakerd_api_ping(pacemakerd_api, crm_system_name);
-            if (rc == pcmk_rc_ok) {
-                rc = pcmk_poll_ipc(pacemakerd_api, options.reconnect_ms/2);
-                if (rc == pcmk_rc_ok) {
-                    pcmk_dispatch_ipc(pacemakerd_api);
-                    rc = ENOTCONN;
-                    if ((output_format == mon_output_console) ||
-                        (output_format == mon_output_plain)) {
-
-                        const char *state_str = NULL;
-                        state_str = pcmk__pcmkd_state_enum2friendly(state);
-                        switch (state) {
-                            case pcmk_pacemakerd_state_running:
-                                rc = pcmk_rc_ok;
-                                break;
-                            case pcmk_pacemakerd_state_starting_daemons:
-                                out->info(out, "%s", state_str);
-                                break;
-                            case pcmk_pacemakerd_state_wait_for_ping:
-                                out->info(out, "%s", state_str);
-                                break;
-                            case pcmk_pacemakerd_state_shutting_down:
-                                out->info(out, "%s", state_str);
-                                /* try our luck maybe CIB is still accessible */
-                                rc = pcmk_rc_ok;
-                                break;
-                            case pcmk_pacemakerd_state_shutdown_complete:
-                                out->info(out, "%s", state_str);
-                                break;
-                            default:
-                                break;
-                        }
-                    } else {
-                        switch (state) {
-                            case pcmk_pacemakerd_state_running:
-                                rc = pcmk_rc_ok;
-                                break;
-                            case pcmk_pacemakerd_state_shutting_down:
-                                /* try our luck maybe CIB is still accessible */
-                                rc = pcmk_rc_ok;
-                                break;
-                            default:
-                                break;
-                        }
-                    }
-                }
-            }
-            break;
-        case EREMOTEIO:
-            rc = pcmk_rc_ok;
-            on_remote_node = TRUE;
-            /* just show this if refresh is gonna remove all traces */
-            if (output_format == mon_output_console) {
-                out->info(out,
-                    "Running on remote-node waiting to be connected by cluster ...");
-            }
-            break;
-        default:
-            break;
-    }
-    pcmk_free_ipc_api(pacemakerd_api);
-    /* returning with ENOTCONN triggers a retry */
-    return (rc == pcmk_rc_ok)?rc:ENOTCONN;
+
+    rc = setup_cib_connection();
+    return rc;
 }
 
 #if CURSES_ENABLED
 static const char *
 get_option_desc(char c)
 {
     const char *desc = "No help available";
 
     for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
         if (entry->short_name == c) {
             desc = entry->description;
             break;
         }
     }
     return desc;
 }
 
 #define print_option_help(out, option, condition) \
     curses_formatted_printf(out, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
 
 /* This function is called from the main loop when there is something to be read
  * on stdin, like an interactive user's keystroke.  All it does is read the keystroke,
  * set flags (or show the page showing which keystrokes are valid), and redraw the
  * screen.  It does not do anything with connections to the CIB or fencing agent
  * agent what would happen in mon_refresh_display.
  */
 static gboolean
 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data)
 {
     int c;
     gboolean config_mode = FALSE;
 
     while (1) {
 
         /* Get user input */
         c = getchar();
 
         switch (c) {
             case 'm':
                 interactive_fence_level++;
                 if (interactive_fence_level > 3) {
                     interactive_fence_level = 0;
                 }
 
                 set_fencing_options(interactive_fence_level);
                 break;
             case 'c':
                 show ^= pcmk_section_tickets;
                 break;
             case 'f':
                 show ^= pcmk_section_failcounts;
                 break;
             case 'n':
                 show_opts ^= pcmk_show_rscs_by_node;
                 break;
             case 'o':
                 show ^= pcmk_section_operations;
                 if (!pcmk_is_set(show, pcmk_section_operations)) {
                     show_opts &= ~pcmk_show_timing;
                 }
                 break;
             case 'r':
                 show_opts ^= pcmk_show_inactive_rscs;
                 break;
             case 'R':
                 show_opts ^= pcmk_show_details;
 #ifdef PCMK__COMPAT_2_0
                 // Keep failed action output the same as 2.0.x
                 show_opts |= pcmk_show_failed_detail;
 #endif
                 break;
             case 't':
                 show_opts ^= pcmk_show_timing;
                 if (pcmk_is_set(show_opts, pcmk_show_timing)) {
                     show |= pcmk_section_operations;
                 }
                 break;
             case 'A':
                 show ^= pcmk_section_attributes;
                 break;
             case 'L':
                 show ^= pcmk_section_bans;
                 break;
             case 'D':
                 /* If any header is shown, clear them all, otherwise set them all */
                 if (pcmk_any_flags_set(show, pcmk_section_summary)) {
                     show &= ~pcmk_section_summary;
                 } else {
                     show |= pcmk_section_summary;
                 }
                 /* Regardless, we don't show options in console mode. */
                 show &= ~pcmk_section_options;
                 break;
             case 'b':
                 show_opts ^= pcmk_show_brief;
                 break;
             case 'j':
                 show_opts ^= pcmk_show_pending;
                 break;
             case '?':
                 config_mode = TRUE;
                 break;
             default:
                 /* All other keys just redraw the screen. */
                 goto refresh;
         }
 
         if (!config_mode)
             goto refresh;
 
-        blank_screen();
+        clear();
+        refresh();
 
         curses_formatted_printf(out, "%s", "Display option change mode\n");
         print_option_help(out, 'c', pcmk_is_set(show, pcmk_section_tickets));
         print_option_help(out, 'f', pcmk_is_set(show, pcmk_section_failcounts));
         print_option_help(out, 'n', pcmk_is_set(show_opts, pcmk_show_rscs_by_node));
         print_option_help(out, 'o', pcmk_is_set(show, pcmk_section_operations));
         print_option_help(out, 'r', pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
         print_option_help(out, 't', pcmk_is_set(show_opts, pcmk_show_timing));
         print_option_help(out, 'A', pcmk_is_set(show, pcmk_section_attributes));
         print_option_help(out, 'L', pcmk_is_set(show, pcmk_section_bans));
         print_option_help(out, 'D', !pcmk_is_set(show, pcmk_section_summary));
 #ifdef PCMK__COMPAT_2_0
         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details & ~pcmk_show_failed_detail));
 #else
         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details));
 #endif
         print_option_help(out, 'b', pcmk_is_set(show_opts, pcmk_show_brief));
         print_option_help(out, 'j', pcmk_is_set(show_opts, pcmk_show_pending));
         curses_formatted_printf(out, "%d m: \t%s\n", interactive_fence_level, get_option_desc('m'));
         curses_formatted_printf(out, "%s", "\nToggle fields via field letter, type any other key to return\n");
     }
 
 refresh:
     refresh_after_event(FALSE, TRUE);
 
     return TRUE;
 }
-#endif
+#endif  // CURSES_ENABLED
 
 // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
 static void
 avoid_zombies(void)
 {
     struct sigaction sa;
 
     memset(&sa, 0, sizeof(struct sigaction));
     if (sigemptyset(&sa.sa_mask) < 0) {
         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
         return;
     }
     sa.sa_handler = SIG_IGN;
     sa.sa_flags = SA_RESTART|SA_NOCLDWAIT;
     if (sigaction(SIGCHLD, &sa, NULL) < 0) {
         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
     }
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     const char *description = "Notes:\n\n"
                               "If this program is called as crm_mon.cgi, --output-as=html --html-cgi will\n"
                               "automatically be added to the command line arguments.\n\n"
                               "Time Specification:\n\n"
                               "The TIMESPEC in any command line option can be specified in many different\n"
                               "formats.  It can be just an integer number of seconds, a number plus units\n"
                               "(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n"
                               "Output Control:\n\n"
                               "By default, a certain list of sections are written to the output destination.\n"
                               "The default varies based on the output format - XML includes everything, while\n"
                               "other output formats will display less.  This list can be modified with the\n"
                               "--include and --exclude command line options.  Each option may be given multiple\n"
                               "times on the command line, and each can give a comma-separated list of sections.\n"
                               "The options are applied to the default set, from left to right as seen on the\n"
                               "command line.  For a list of valid sections, pass --include=list or --exclude=list.\n\n"
                               "Interactive Use:\n\n"
                               "When run interactively, crm_mon can be told to hide and display various sections\n"
                               "of output.  To see a help screen explaining the options, hit '?'.  Any key stroke\n"
                               "aside from those listed will cause the screen to refresh.\n\n"
                               "Examples:\n\n"
                               "Display the cluster status on the console with updates as they occur:\n\n"
                               "\tcrm_mon\n\n"
                               "Display the cluster status on the console just once then exit:\n\n"
                               "\tcrm_mon -1\n\n"
                               "Display your cluster status, group resources by node, and include inactive resources in the list:\n\n"
                               "\tcrm_mon --group-by-node --inactive\n\n"
                               "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:\n\n"
                               "\tcrm_mon --daemonize --output-as html --output-to /path/to/docroot/filename.html\n\n"
                               "Start crm_mon and export the current cluster status as XML to stdout, then exit:\n\n"
                               "\tcrm_mon --output-as xml\n\n";
 
 #if CURSES_ENABLED
     context = pcmk__build_arg_context(args, "console (default), html, text, xml", group, NULL);
 #else
     context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL);
 #endif
     pcmk__add_main_args(context, extra_prog_entries);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "display", "Display Options:",
                         "Show display options", display_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
                         "Show deprecated options", deprecated_entries);
 
     return context;
 }
 
 /* If certain format options were specified, we want to set some extra
  * options.  We can just process these like they were given on the
  * command line.
  */
 static void
 add_output_args(void) {
     GError *err = NULL;
 
     if (output_format == mon_output_plain) {
         if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_cgi) {
         if (!pcmk__force_args(context, &err, "%s --html-cgi", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_xml) {
         if (!pcmk__force_args(context, &err, "%s --xml-simple-list --xml-substitute", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_legacy_xml) {
         output_format = mon_output_xml;
         if (!pcmk__force_args(context, &err, "%s --xml-legacy --xml-substitute", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     }
 }
 
 /* Which output format to use could come from two places:  The --as-xml
  * style arguments we gave in deprecated_entries above, or the formatted output
  * arguments added by pcmk__register_formats.  If the latter were used,
  * output_format will be mon_output_unset.
  *
  * Call the callbacks as if those older style arguments were provided so
  * the various things they do get done.
  */
 static void
 reconcile_output_format(pcmk__common_args_t *args) {
     gboolean retval = TRUE;
     GError *err = NULL;
 
     if (output_format != mon_output_unset) {
         return;
     }
 
     if (pcmk__str_eq(args->output_ty, "html", pcmk__str_casei)) {
         char *dest = NULL;
 
         pcmk__str_update(&dest, args->output_dest);
         retval = as_html_cb("h", dest, NULL, &err);
         free(dest);
     } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_casei)) {
         retval = no_curses_cb("N", NULL, NULL, &err);
     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_casei)) {
         pcmk__str_update(&args->output_ty, "xml");
         output_format = mon_output_xml;
     } else if (options.one_shot) {
         pcmk__str_update(&args->output_ty, "text");
         output_format = mon_output_plain;
     } else if (!options.daemonize && args->output_dest != NULL) {
         options.one_shot = TRUE;
         pcmk__str_update(&args->output_ty, "text");
         output_format = mon_output_plain;
     } else {
         /* Neither old nor new arguments were given, so set the default. */
         pcmk__str_update(&args->output_ty, "console");
         output_format = mon_output_console;
     }
 
     if (!retval) {
         g_propagate_error(&error, err);
         clean_up(CRM_EX_USAGE);
     }
 }
 
 static void
 clean_up_on_connection_failure(int rc)
 {
     if (output_format == mon_output_monitor) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
                     pcmk_rc_str(rc));
         clean_up(MON_STATUS_CRIT);
     } else if (rc == ENOTCONN) {
-        if (on_remote_node) {
+        if (pcmkd_state == pcmk_pacemakerd_state_remote) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: remote-node not connected to cluster");
         } else {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: cluster is not available on this node");
         }
     } else {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Connection to cluster failed: %s", pcmk_rc_str(rc));
     }
 
     clean_up(pcmk_rc2exitc(rc));
 }
 
 static void
 one_shot(void)
 {
     int rc = pcmk__status(out, cib, fence_history, show, show_opts,
                           options.only_node, options.only_rsc,
                           options.neg_location_prefix,
                           output_format == mon_output_monitor, 0);
 
     if (rc == pcmk_rc_ok) {
         clean_up(pcmk_rc2exitc(rc));
     } else {
         clean_up_on_connection_failure(rc);
     }
 }
 
 static void
 exit_on_invalid_cib(void)
 {
     if (cib != NULL) {
         return;
     }
 
     // Shouldn't really be possible
     g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Invalid CIB source");
     clean_up(CRM_EX_ERROR);
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_rc_ok;
     GOptionGroup *output_group = NULL;
 
     args = pcmk__new_common_args(SUMMARY);
     context = build_arg_context(args, &output_group);
     pcmk__register_formats(output_group, formats);
 
     options.pid_file = strdup("/tmp/ClusterMon.pid");
     pcmk__cli_init_logging("crm_mon", 0);
 
     // Avoid needing to wait for subprocesses forked for -E/--external-agent
     avoid_zombies();
 
     if (pcmk__ends_with_ext(argv[0], ".cgi")) {
         output_format = mon_output_cgi;
         options.one_shot = TRUE;
     }
 
     processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
 
     fence_history_cb("--fence-history", "1", NULL, NULL);
 
     /* Set an HTML title regardless of what format we will eventually use.  This can't
      * be done in add_output_args.  That function is called after command line
      * arguments are processed in the next block, which means it'll override whatever
      * title the user provides.  Doing this here means the user can give their own
      * title on the command line.
      */
     if (!pcmk__force_args(context, &error, "%s --html-title \"Cluster Status\"",
                           g_get_prgname())) {
         return clean_up(CRM_EX_USAGE);
     }
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (!args->version) {
         if (args->quiet) {
             include_exclude_cb("--exclude", "times", NULL, NULL);
         }
 
         if (options.watch_fencing) {
             fence_history_cb("--fence-history", "0", NULL, NULL);
             options.fence_connect = TRUE;
         }
 
         /* create the cib-object early to be able to do further
          * decisions based on the cib-source
          */
         cib = cib_new();
 
         exit_on_invalid_cib();
 
         switch (cib->variant) {
             case cib_native:
-                /* cib & fencing - everything available */
-                use_cib_native = TRUE;
+                // Everything (fencer, CIB, pcmkd status) should be available
                 break;
 
             case cib_file:
-                /* Don't try to connect to fencing as we
-                 * either don't have a running cluster or
-                 * the fencing-information would possibly
-                 * not match the cib data from a file.
-                 * As we don't expect cib-updates coming
-                 * in enforce one-shot. */
+                // Live fence history is not meaningful
                 fence_history_cb("--fence-history", "0", NULL, NULL);
+
+                /* Notifications are unsupported; nothing to monitor
+                 * @COMPAT: Let setup_cib_connection() handle this by exiting?
+                 */
                 options.one_shot = TRUE;
                 break;
 
             case cib_remote:
-                /* updates coming in but no fencing */
+                // We won't receive any fencing updates
                 fence_history_cb("--fence-history", "0", NULL, NULL);
                 break;
 
             case cib_undefined:
             case cib_database:
             default:
                 /* something is odd */
                 exit_on_invalid_cib();
                 break;
         }
 
         if (options.one_shot) {
             if (output_format == mon_output_console) {
                 output_format = mon_output_plain;
             }
 
         } else if (options.daemonize) {
             if (pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches|pcmk__str_casei) &&
                 !options.external_agent) {
                 g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                             "--daemonize requires at least one of --output-to and --external-agent");
                 return clean_up(CRM_EX_USAGE);
             }
 
         } else if (output_format == mon_output_console) {
 #if CURSES_ENABLED
             crm_enable_stderr(FALSE);
 #else
             options.one_shot = TRUE;
             output_format = mon_output_plain;
             printf("Defaulting to one-shot mode\n");
             printf("You need to have curses available at compile time to enable console mode\n");
 #endif
         }
     }
 
     reconcile_output_format(args);
     add_output_args();
 
     /* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
 
     if (args->version && output_format == mon_output_console) {
         /* Use the text output format here if we are in curses mode but were given
          * --version.  Displaying version information uses printf, and then we
          *  immediately exit.  We don't want to initialize curses for that.
          */
         rc = pcmk__output_new(&out, "text", args->output_dest, argv);
     } else {
         rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     }
 
     if (rc != pcmk_rc_ok) {
         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
                     args->output_ty, pcmk_rc_str(rc));
         return clean_up(CRM_EX_ERROR);
     }
 
     if (options.daemonize) {
         if (!options.external_agent && (output_format == mon_output_console ||
                                         output_format == mon_output_unset ||
                                         output_format == mon_output_none)) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                         "--daemonize requires --output-as=[html|text|xml]");
             return clean_up(CRM_EX_USAGE);
         }
 
         crm_enable_stderr(FALSE);
 
         cib_delete(cib);
         cib = NULL;
         pcmk__daemonize(crm_system_name, options.pid_file);
         cib = cib_new();
         exit_on_invalid_cib();
     }
 
     show = default_includes(output_format);
 
     /* Apply --include/--exclude flags we used internally.  There's no error reporting
      * here because this would be a programming error.
      */
     apply_include_exclude(options.includes_excludes, &error);
 
     /* And now apply any --include/--exclude flags the user gave on the command line.
      * These are done in a separate pass from the internal ones because we want to
      * make sure whatever the user specifies overrides whatever we do.
      */
     if (!apply_include_exclude(options.user_includes_excludes, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     /* Sync up the initial value of interactive_fence_level with whatever was set with
      * --include/--exclude= options.
      */
     if (pcmk_all_flags_set(show, pcmk_section_fencing_all)) {
         interactive_fence_level = 3;
     } else if (pcmk_is_set(show, pcmk_section_fence_worked)) {
         interactive_fence_level = 2;
     } else if (pcmk_any_flags_set(show, pcmk_section_fence_failed | pcmk_section_fence_pending)) {
         interactive_fence_level = 1;
     } else {
         interactive_fence_level = 0;
     }
 
     pcmk__register_lib_messages(out);
     crm_mon_register_messages(out);
     pe__register_messages(out);
     stonith__register_messages(out);
 
+    // Messages internal to this file, nothing curses-specific
+    pcmk__register_messages(out, fmt_functions);
+
     if (args->version) {
         out->version(out, false);
         return clean_up(CRM_EX_OK);
     }
 
     /* Extra sanity checks when in CGI mode */
     if (output_format == mon_output_cgi) {
         if (cib->variant == cib_file) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
             return clean_up(CRM_EX_USAGE);
         } else if (options.external_agent != NULL) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
             return clean_up(CRM_EX_USAGE);
         } else if (options.daemonize == TRUE) {
             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
             return clean_up(CRM_EX_USAGE);
         }
     }
 
     if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) {
         show_opts |= pcmk_show_inactive_rscs | pcmk_show_timing;
 
         if (!options.daemonize) {
             options.one_shot = TRUE;
         }
     }
 
     if ((output_format == mon_output_html || output_format == mon_output_cgi) &&
         out->dest != stdout) {
         pcmk__html_add_header("meta", "http-equiv", "refresh", "content",
                               pcmk__itoa(options.reconnect_ms / 1000), NULL);
     }
 
 #ifdef PCMK__COMPAT_2_0
     // Keep failed action output the same as 2.0.x
     show_opts |= pcmk_show_failed_detail;
 #endif
 
     crm_info("Starting %s", crm_system_name);
 
     cib__set_output(cib, out);
 
     if (options.one_shot) {
         one_shot();
     }
 
+    out->message(out, "crm-mon-disconnected",
+                 "Waiting for initial connection", pcmkd_state);
     do {
-        out->info(out,"Waiting until cluster is available on this node ...");
-
-        rc = pacemakerd_status();
-        if (rc == pcmk_rc_ok) {
-            fencing_connect();
-            rc = setup_cib_connection();
-        }
+        out->transient(out, "Connecting to cluster...");
+        rc = setup_api_connections();
 
         if (rc != pcmk_rc_ok) {
+            if ((rc == ENOTCONN) || (rc == ECONNREFUSED)) {
+                out->transient(out, "Connection failed. Retrying in %ums...",
+                               options.reconnect_ms);
+            }
+
+            // Give some time to view all output even if we won't retry
             pcmk__sleep_ms(options.reconnect_ms);
 #if CURSES_ENABLED
             if (output_format == mon_output_console) {
                 clear();
                 refresh();
             }
 #endif
-        } else if (output_format == mon_output_html && out->dest != stdout) {
-            printf("Writing html to %s ...\n", args->output_dest);
         }
-
-    } while (rc == ENOTCONN);
+    } while ((rc == ENOTCONN) || (rc == ECONNREFUSED));
 
     if (rc != pcmk_rc_ok) {
         clean_up_on_connection_failure(rc);
     }
 
     set_fencing_options(interactive_fence_level);
     mon_refresh_display(NULL);
 
     mainloop = g_main_loop_new(NULL, FALSE);
 
     mainloop_add_signal(SIGTERM, mon_shutdown);
     mainloop_add_signal(SIGINT, mon_shutdown);
 #if CURSES_ENABLED
     if (output_format == mon_output_console) {
         ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize);
         if (ncurses_winch_handler == SIG_DFL ||
             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
             ncurses_winch_handler = NULL;
 
         io_channel = g_io_channel_unix_new(STDIN_FILENO);
         g_io_add_watch(io_channel, G_IO_IN, detect_user_input, NULL);
     }
 #endif
 
     /* When refresh_trigger->trigger is set to TRUE, call mon_refresh_display.  In
      * this file, that is anywhere mainloop_set_trigger is called.
      */
     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
 
     g_main_loop_run(mainloop);
     g_main_loop_unref(mainloop);
 
     if (io_channel != NULL) {
         g_io_channel_shutdown(io_channel, TRUE, NULL);
     }
 
     crm_info("Exiting %s", crm_system_name);
 
     return clean_up(CRM_EX_OK);
 }
 
 static int
 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
                  int status, const char *desc)
 {
     pid_t pid;
 
     /*setenv needs chars, these are ints */
     char *rc_s = pcmk__itoa(rc);
     char *status_s = pcmk__itoa(status);
     char *target_rc_s = pcmk__itoa(target_rc);
 
     crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent);
 
     if(rsc) {
         setenv("CRM_notify_rsc", rsc, 1);
     }
     if (options.external_recipient) {
         setenv("CRM_notify_recipient", options.external_recipient, 1);
     }
     setenv("CRM_notify_node", node, 1);
     setenv("CRM_notify_task", task, 1);
     setenv("CRM_notify_desc", desc, 1);
     setenv("CRM_notify_rc", rc_s, 1);
     setenv("CRM_notify_target_rc", target_rc_s, 1);
     setenv("CRM_notify_status", status_s, 1);
 
     pid = fork();
     if (pid == -1) {
         crm_perror(LOG_ERR, "notification fork() failed.");
     }
     if (pid == 0) {
         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
         execl(options.external_agent, options.external_agent, NULL);
         exit(CRM_EX_ERROR);
     }
 
     crm_trace("Finished running custom notification program '%s'.", options.external_agent);
     free(target_rc_s);
     free(status_s);
     free(rc_s);
     return 0;
 }
 
 static int
 handle_rsc_op(xmlNode *xml, void *userdata)
 {
     const char *node_id = (const char *) userdata;
     int rc = -1;
     int status = -1;
     int target_rc = -1;
     gboolean notify = TRUE;
 
     char *rsc = NULL;
     char *task = NULL;
     const char *desc = NULL;
     const char *magic = NULL;
     const char *id = NULL;
     const char *node = NULL;
 
     xmlNode *n = xml;
     xmlNode * rsc_op = xml;
 
     if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
         pcmk__xe_foreach_child(xml, NULL, handle_rsc_op, (void *) node_id);
         return pcmk_rc_ok;
     }
 
     id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
     if (id == NULL) {
         /* Compatibility with <= 1.1.5 */
         id = ID(rsc_op);
     }
 
     magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
     if (magic == NULL) {
         /* non-change */
         return pcmk_rc_ok;
     }
 
     if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
                                  &target_rc)) {
         crm_err("Invalid event %s detected for %s", magic, id);
         return pcmk_rc_ok;
     }
 
     if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
         crm_err("Invalid event detected for %s", id);
         goto bail;
     }
 
     node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
 
     while (n != NULL && !pcmk__str_eq(XML_CIB_TAG_STATE, TYPE(n), pcmk__str_casei)) {
         n = n->parent;
     }
 
     if(node == NULL && n) {
         node = crm_element_value(n, XML_ATTR_UNAME);
     }
 
     if (node == NULL && n) {
         node = ID(n);
     }
 
     if (node == NULL) {
         node = node_id;
     }
 
     if (node == NULL) {
         crm_err("No node detected for event %s (%s)", magic, id);
         goto bail;
     }
 
     /* look up where we expected it to be? */
     desc = pcmk_rc_str(pcmk_rc_ok);
     if ((status == PCMK_EXEC_DONE) && (target_rc == rc)) {
         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
         if (rc == PCMK_OCF_NOT_RUNNING) {
             notify = FALSE;
         }
 
     } else if (status == PCMK_EXEC_DONE) {
         desc = services_ocf_exitcode_str(rc);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
 
     } else {
         desc = pcmk_exec_status_str(status);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
     }
 
     if (notify && options.external_agent) {
         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
     }
 
   bail:
     free(rsc);
     free(task);
     return pcmk_rc_ok;
 }
 
 /* This function is just a wrapper around mainloop_set_trigger so that it can be
  * called from a mainloop directly.  It's simply another way of ensuring the screen
  * gets redrawn.
  */
 static gboolean
 mon_trigger_refresh(gpointer user_data)
 {
     mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
     return FALSE;
 }
 
 static int
 handle_op_for_node(xmlNode *xml, void *userdata)
 {
     const char *node = crm_element_value(xml, XML_ATTR_UNAME);
 
     if (node == NULL) {
         node = ID(xml);
     }
 
     handle_rsc_op(xml, (void *) node);
     return pcmk_rc_ok;
 }
 
 static void
 crm_diff_update_v2(const char *event, xmlNode * msg)
 {
     xmlNode *change = NULL;
     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     for (change = pcmk__xml_first_child(diff); change != NULL;
          change = pcmk__xml_next(change)) {
         const char *name = NULL;
         const char *op = crm_element_value(change, XML_DIFF_OP);
         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
         xmlNode *match = NULL;
         const char *node = NULL;
 
         if(op == NULL) {
             continue;
 
         } else if(strcmp(op, "create") == 0) {
             match = change->children;
 
         } else if(strcmp(op, "move") == 0) {
             continue;
 
         } else if(strcmp(op, "delete") == 0) {
             continue;
 
         } else if(strcmp(op, "modify") == 0) {
             match = first_named_child(change, XML_DIFF_RESULT);
             if(match) {
                 match = match->children;
             }
         }
 
         if(match) {
             name = (const char *)match->name;
         }
 
         crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
         if(xpath == NULL) {
             /* Version field, ignore */
 
         } else if(name == NULL) {
             crm_debug("No result for %s operation to %s", op, xpath);
             CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
 
         } else if(strcmp(name, XML_TAG_CIB) == 0) {
             pcmk__xe_foreach_child(first_named_child(match, XML_CIB_TAG_STATUS),
                                    NULL, handle_op_for_node, NULL);
 
         } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
             pcmk__xe_foreach_child(match, NULL, handle_op_for_node, NULL);
 
         } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
             node = crm_element_value(match, XML_ATTR_UNAME);
             if (node == NULL) {
                 node = ID(match);
             }
             handle_rsc_op(match, (void *) node);
 
         } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
             node = ID(match);
             handle_rsc_op(match, (void *) node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else {
             crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
         }
     }
 }
 
 static void
 crm_diff_update_v1(const char *event, xmlNode * msg)
 {
     /* Process operation updates */
     xmlXPathObject *xpathObj = xpath_search(msg,
                                             "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
                                             "//" XML_LRM_TAG_RSC_OP);
     int lpc = 0, max = numXpathResults(xpathObj);
 
     for (lpc = 0; lpc < max; lpc++) {
         xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
 
         handle_rsc_op(rsc_op, NULL);
     }
     freeXpathObject(xpathObj);
 }
 
 static void
 crm_diff_update(const char *event, xmlNode * msg)
 {
     int rc = -1;
     static bool stale = FALSE;
     gboolean cib_updated = FALSE;
     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
 
     out->progress(out, false);
 
     if (current_cib != NULL) {
         rc = xml_apply_patchset(current_cib, diff, TRUE);
 
         switch (rc) {
             case -pcmk_err_diff_resync:
             case -pcmk_err_diff_failed:
                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(current_cib); current_cib = NULL;
                 break;
             case pcmk_ok:
                 cib_updated = TRUE;
                 break;
             default:
                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
                 free_xml(current_cib); current_cib = NULL;
         }
     }
 
     if (current_cib == NULL) {
         crm_trace("Re-requesting the full cib");
         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
     }
 
     if (options.external_agent) {
         int format = 0;
         crm_element_value_int(diff, "format", &format);
         switch(format) {
             case 1:
                 crm_diff_update_v1(event, msg);
                 break;
             case 2:
                 crm_diff_update_v2(event, msg);
                 break;
             default:
                 crm_err("Unknown patch format: %d", format);
         }
     }
 
     if (current_cib == NULL) {
         if(!stale) {
             out->info(out, "--- Stale data ---");
         }
         stale = TRUE;
         return;
     }
 
     stale = FALSE;
     refresh_after_event(cib_updated, FALSE);
 }
 
 static int
 mon_refresh_display(gpointer user_data)
 {
     int rc = pcmk_rc_ok;
 
     last_refresh = time(NULL);
 
     if (output_format == mon_output_none || output_format == mon_output_unset) {
         return G_SOURCE_REMOVE;
     }
 
     if (fence_history == pcmk__fence_history_full &&
         !pcmk_all_flags_set(show, pcmk_section_fencing_all) &&
         output_format != mon_output_xml) {
         fence_history = pcmk__fence_history_reduced;
-     }
+    }
+
+    // Get an up-to-date pacemakerd status for the cluster summary
+    if (cib->variant == cib_native) {
+        pcmk__pacemakerd_status(out, crm_system_name, options.reconnect_ms / 2,
+                                false, &pcmkd_state);
+    }
 
     if (out->dest != stdout) {
         out->reset(out);
     }
 
-    rc = pcmk__output_cluster_status(out, st, cib, current_cib, fence_history,
-                                     show, show_opts, options.only_node,
-                                     options.only_rsc, options.neg_location_prefix,
+    rc = pcmk__output_cluster_status(out, st, cib, current_cib, pcmkd_state,
+                                     fence_history, show, show_opts,
+                                     options.only_node,options.only_rsc,
+                                     options.neg_location_prefix,
                                      output_format == mon_output_monitor);
 
     if (output_format == mon_output_monitor && rc != pcmk_rc_ok) {
         clean_up(MON_STATUS_WARN);
         return G_SOURCE_REMOVE;
     } else if (rc == pcmk_rc_schema_validation) {
         clean_up(CRM_EX_CONFIG);
         return G_SOURCE_REMOVE;
     }
 
     if (out->dest != stdout) {
         out->finish(out, CRM_EX_OK, true, NULL);
     }
 
     return G_SOURCE_CONTINUE;
 }
 
-/* This function is called for fencing events (see fencing_connect for which ones) when
- * --watch-fencing is used on the command line.
+/* This function is called for fencing events (see setup_fencer_connection() for
+ * which ones) when --watch-fencing is used on the command line
  */
 static void
 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
 {
     if (st->state == stonith_disconnected) {
         /* disconnect cib as well and have everything reconnect */
         mon_cib_connection_destroy(NULL);
     } else if (options.external_agent) {
         char *desc = stonith__event_description(e);
 
         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
         free(desc);
     }
 }
 
 /* Cause the screen to be redrawn (via mainloop_set_trigger) when various conditions are met:
  *
  * - If the last update occurred more than reconnect_ms ago (defaults to 5s, but
  *   can be changed via the -i command line option), or
  * - After every 10 CIB updates, or
  * - If it's been 2s since the last update
  *
  * This function sounds like it would be more broadly useful, but it is only called when a
  * fencing event is received or a CIB diff occurrs.
  */
 static void
 refresh_after_event(gboolean data_updated, gboolean enforce)
 {
     static int updates = 0;
     time_t now = time(NULL);
 
     if (data_updated) {
         updates++;
     }
 
     if(refresh_timer == NULL) {
         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
     }
 
     if (reconnect_timer > 0) {
         /* we will receive a refresh request after successful reconnect */
         mainloop_timer_stop(refresh_timer);
         return;
     }
 
     /* as we're not handling initial failure of fencer-connection as
      * fatal give it a retry here
      * not getting here if cib-reconnection is already on the way
      */
-    fencing_connect();
+    setup_fencer_connection();
 
     if (enforce ||
         ((now - last_refresh) > (options.reconnect_ms / 1000)) ||
         updates >= 10) {
         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else {
         mainloop_timer_start(refresh_timer);
     }
 }
 
-/* This function is called for fencing events (see fencing_connect for which ones) when
- * --watch-fencing is NOT used on the command line.
+/* This function is called for fencing events (see setup_fencer_connection() for
+ * which ones) when --watch-fencing is NOT used on the command line
  */
 static void
 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
 {
     if (st->state == stonith_disconnected) {
         /* disconnect cib as well and have everything reconnect */
         mon_cib_connection_destroy(NULL);
     } else {
         out->progress(out, false);
         refresh_after_event(TRUE, FALSE);
     }
 }
 
 /*
  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
  * deallocate memory and show usage-message if requested.
  *
  * We don't actually return, but nominally returning crm_exit_t allows a usage
  * like "return clean_up(exit_code);" which helps static analysis understand the
  * code flow.
  */
 static crm_exit_t
 clean_up(crm_exit_t exit_code)
 {
     /* Quitting crm_mon is much more complicated than it ought to be. */
 
     /* (1) Close connections, free things, etc. */
     cib__clean_up_connection(&cib);
     stonith_api_delete(st);
     free(options.neg_location_prefix);
     free(options.only_node);
     free(options.only_rsc);
     free(options.pid_file);
     g_slist_free_full(options.includes_excludes, free);
 
     g_strfreev(processed_args);
 
     /* (2) If this is abnormal termination and we're in curses mode, shut down
      * curses first.  Any messages displayed to the screen before curses is shut
      * down will be lost because doing the shut down will also restore the
      * screen to whatever it looked like before crm_mon was started.
      */
     if ((error != NULL || exit_code == CRM_EX_USAGE) && output_format == mon_output_console) {
         out->finish(out, exit_code, false, NULL);
         pcmk__output_free(out);
         out = NULL;
     }
 
     /* (3) If this is a command line usage related failure, print the usage
      * message.
      */
     if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
         char *help = g_option_context_get_help(context, TRUE, NULL);
 
         fprintf(stderr, "%s", help);
         g_free(help);
     }
 
     pcmk__free_arg_context(context);
 
     /* (4) If this is any kind of error, print the error out and exit.  Make
      * sure to handle situations both before and after formatted output is
      * set up.  We want errors to appear formatted if at all possible.
      */
     if (error != NULL) {
         if (out != NULL) {
             out->err(out, "%s: %s", g_get_prgname(), error->message);
             out->finish(out, exit_code, true, NULL);
             pcmk__output_free(out);
         } else {
             fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         }
 
         g_clear_error(&error);
         crm_exit(exit_code);
     }
 
     /* (5) Print formatted output to the screen if we made it far enough in
      * crm_mon to be able to do so.
      */
     if (out != NULL) {
         if (!options.daemonize) {
             out->finish(out, exit_code, true, NULL);
         }
 
         pcmk__output_free(out);
         pcmk__unregister_formats();
     }
 
     crm_exit(exit_code);
 }
diff --git a/tools/crm_mon.h b/tools/crm_mon.h
index a602ceb671..1d3f99905d 100644
--- a/tools/crm_mon.h
+++ b/tools/crm_mon.h
@@ -1,73 +1,71 @@
 /*
  * Copyright 2019-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.
  */
 
 #ifndef CRM_MON__H
 #define CRM_MON__H
 
 #include <crm_internal.h>
 
 #include <glib.h>
 
 #include <crm/common/output_internal.h>
 #include <crm/pengine/pe_types.h>
 #include <crm/stonith-ng.h>
 
 /*
  * The man pages for both curses and ncurses suggest inclusion of "curses.h".
  * We believe the following to be acceptable and portable.
  */
 
 #  if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBCURSES)
 #    if defined(HAVE_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
 #      include <ncurses.h>
 #      define CURSES_ENABLED 1
 #    elif defined(HAVE_NCURSES_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
 #      include <ncurses/ncurses.h>
 #      define CURSES_ENABLED 1
 #    elif defined(HAVE_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
 #      include <curses.h>
 #      define CURSES_ENABLED 1
 #    elif defined(HAVE_CURSES_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
 #      include <curses/curses.h>
 #      define CURSES_ENABLED 1
 #    else
 #      define CURSES_ENABLED 0
 #    endif
 #  else
 #    define CURSES_ENABLED 0
 #  endif
 
 typedef enum mon_output_format_e {
     mon_output_unset,
     mon_output_none,
     mon_output_monitor,
     mon_output_plain,
     mon_output_console,
     mon_output_xml,
     mon_output_legacy_xml,
     mon_output_html,
     mon_output_cgi
 } mon_output_format_t;
 
-void blank_screen(void);
-
 void crm_mon_register_messages(pcmk__output_t *out);
 
 #if CURSES_ENABLED
 pcmk__output_t *crm_mon_mk_curses_output(char **argv);
 void curses_formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 void curses_formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 void curses_indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
 void curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
 
 extern GOptionEntry crm_mon_curses_output_entries[];
 #define CRM_MON_SUPPORTED_FORMAT_CURSES { "console", crm_mon_mk_curses_output, crm_mon_curses_output_entries }
 #endif
 
 #endif
diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c
index 0631dd55a1..f496beeab2 100644
--- a/tools/crm_mon_curses.c
+++ b/tools/crm_mon_curses.c
@@ -1,511 +1,497 @@
 /*
  * Copyright 2019-2022 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 <stdarg.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <crm/crm.h>
 #include <crm/common/output.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/pengine/internal.h>
 #include <glib.h>
 #include <pacemaker-internal.h>
 
 #include "crm_mon.h"
 
 #if CURSES_ENABLED
 
 GOptionEntry crm_mon_curses_output_entries[] = {
     { NULL }
 };
 
 typedef struct curses_list_data_s {
     unsigned int len;
     char *singular_noun;
     char *plural_noun;
 } curses_list_data_t;
 
 typedef struct private_data_s {
     GQueue *parent_q;
 } private_data_t;
 
 static void
 curses_free_priv(pcmk__output_t *out) {
     private_data_t *priv = NULL;
 
     if (out == NULL || out->priv == NULL) {
         return;
     }
 
     priv = out->priv;
 
     g_queue_free(priv->parent_q);
     free(priv);
     out->priv = NULL;
 }
 
 static bool
 curses_init(pcmk__output_t *out) {
     private_data_t *priv = NULL;
 
     CRM_ASSERT(out != NULL);
 
     /* If curses_init was previously called on this output struct, just return. */
     if (out->priv != NULL) {
         return true;
     } else {
         out->priv = calloc(1, sizeof(private_data_t));
         if (out->priv == NULL) {
             return false;
         }
 
         priv = out->priv;
     }
 
     priv->parent_q = g_queue_new();
 
     initscr();
     cbreak();
     noecho();
 
     return true;
 }
 
 static void
 curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     CRM_ASSERT(out != NULL);
 
     echo();
     nocbreak();
     endwin();
 }
 
 static void
 curses_reset(pcmk__output_t *out) {
     CRM_ASSERT(out != NULL);
 
     curses_free_priv(out);
     curses_init(out);
 }
 
 static void
 curses_subprocess_output(pcmk__output_t *out, int exit_status,
                          const char *proc_stdout, const char *proc_stderr) {
     CRM_ASSERT(out != NULL);
 
     if (proc_stdout != NULL) {
         printw("%s\n", proc_stdout);
     }
 
     if (proc_stderr != NULL) {
         printw("%s\n", proc_stderr);
     }
 
     clrtoeol();
     refresh();
 }
 
 /* curses_version is defined in curses.h, so we can't use that name here.
  * Note that this function prints out via text, not with curses.
  */
 static void
 curses_ver(pcmk__output_t *out, bool extended) {
     CRM_ASSERT(out != NULL);
 
     if (extended) {
         printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
     } else {
         printf("Pacemaker %s\n", PACEMAKER_VERSION);
         printf("Written by Andrew Beekhof and the "
                "Pacemaker project contributors\n");
     }
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 curses_error(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
 
     CRM_ASSERT(out != NULL);
 
     /* Informational output does not get indented, to separate it from other
      * potentially indented list output.
      */
     va_start(ap, format);
     vw_printw(stdscr, format, ap);
     va_end(ap);
 
     /* Add a newline. */
     addch('\n');
 
     clrtoeol();
     refresh();
     sleep(2);
 }
 
 G_GNUC_PRINTF(2, 3)
 static int
 curses_info(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
 
     CRM_ASSERT(out != NULL);
 
     if (out->is_quiet(out)) {
         return pcmk_rc_no_output;
     }
 
     /* Informational output does not get indented, to separate it from other
      * potentially indented list output.
      */
     va_start(ap, format);
     vw_printw(stdscr, format, ap);
     va_end(ap);
 
     /* Add a newline. */
     addch('\n');
 
     clrtoeol();
     refresh();
     return pcmk_rc_ok;
 }
 
 static void
 curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     CRM_ASSERT(out != NULL);
     curses_indented_printf(out, "%s", buf);
 }
 
 G_GNUC_PRINTF(4, 5)
 static void
 curses_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
                   const char *format, ...) {
     private_data_t *priv = NULL;
     curses_list_data_t *new_list = NULL;
     va_list ap;
 
     CRM_ASSERT(out != NULL && out->priv != NULL);
     priv = out->priv;
 
     /* Empty formats can be used to create a new level of indentation, but without
      * displaying some sort of list header.  In that case we need to not do any of
      * this stuff. vw_printw will act weird if told to print a NULL.
      */
     if (format != NULL) {
         va_start(ap, format);
 
         curses_indented_vprintf(out, format, ap);
         printw(":\n");
 
         va_end(ap);
     }
 
     new_list = calloc(1, sizeof(curses_list_data_t));
     new_list->len = 0;
     pcmk__str_update(&new_list->singular_noun, singular_noun);
     pcmk__str_update(&new_list->plural_noun, plural_noun);
 
     g_queue_push_tail(priv->parent_q, new_list);
 }
 
 G_GNUC_PRINTF(3, 4)
 static void
 curses_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
     va_list ap;
 
     CRM_ASSERT(out != NULL);
 
     va_start(ap, format);
 
     if (id != NULL) {
         curses_indented_printf(out, "%s: ", id);
         vw_printw(stdscr, format, ap);
     } else {
         curses_indented_vprintf(out, format, ap);
     }
 
     addch('\n');
     va_end(ap);
 
     out->increment_list(out);
 }
 
 static void
 curses_increment_list(pcmk__output_t *out) {
     private_data_t *priv = NULL;
     gpointer tail;
 
     CRM_ASSERT(out != NULL && out->priv != NULL);
     priv = out->priv;
 
     tail = g_queue_peek_tail(priv->parent_q);
     CRM_ASSERT(tail != NULL);
     ((curses_list_data_t *) tail)->len++;
 }
 
 static void
 curses_end_list(pcmk__output_t *out) {
     private_data_t *priv = NULL;
     curses_list_data_t *node = NULL;
 
     CRM_ASSERT(out != NULL && out->priv != NULL);
     priv = out->priv;
 
     node = g_queue_pop_tail(priv->parent_q);
 
     if (node->singular_noun != NULL && node->plural_noun != NULL) {
         if (node->len == 1) {
             curses_indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
         } else {
             curses_indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
         }
     }
 
     free(node);
 }
 
 static bool
 curses_is_quiet(pcmk__output_t *out) {
     CRM_ASSERT(out != NULL);
     return out->quiet;
 }
 
 static void
 curses_spacer(pcmk__output_t *out) {
     CRM_ASSERT(out != NULL);
     addch('\n');
 }
 
 static void
 curses_progress(pcmk__output_t *out, bool end) {
     CRM_ASSERT(out != NULL);
 
     if (end) {
         printw(".\n");
     } else {
         addch('.');
     }
 }
 
 static void
 curses_prompt(const char *prompt, bool do_echo, char **dest)
 {
     int rc = OK;
 
     CRM_ASSERT(prompt != NULL && dest != NULL);
 
     /* This is backwards from the text version of this function on purpose.  We
      * disable echo by default in curses_init, so we need to enable it here if
      * asked for.
      */
     if (do_echo) {
         rc = echo();
     }
 
     if (rc == OK) {
         printw("%s: ", prompt);
 
         if (*dest != NULL) {
             free(*dest);
         }
 
         *dest = calloc(1, 1024);
         /* On older systems, scanw is defined as taking a char * for its first argument,
          * while newer systems rightly want a const char *.  Accomodate both here due
          * to building with -Werror.
          */
         rc = scanw((NCURSES_CONST char *) "%1023s", *dest);
         addch('\n');
     }
 
     if (rc < 1) {
         free(*dest);
         *dest = NULL;
     }
 
     if (do_echo) {
         noecho();
     }
 }
 
 pcmk__output_t *
 crm_mon_mk_curses_output(char **argv) {
     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 
     if (retval == NULL) {
         return NULL;
     }
 
     retval->fmt_name = "console";
     retval->request = pcmk__quote_cmdline(argv);
 
     retval->init = curses_init;
     retval->free_priv = curses_free_priv;
     retval->finish = curses_finish;
     retval->reset = curses_reset;
 
     retval->register_message = pcmk__register_message;
     retval->message = pcmk__call_message;
 
     retval->subprocess_output = curses_subprocess_output;
     retval->version = curses_ver;
     retval->err = curses_error;
     retval->info = curses_info;
     retval->transient = curses_info;
     retval->output_xml = curses_output_xml;
 
     retval->begin_list = curses_begin_list;
     retval->list_item = curses_list_item;
     retval->increment_list = curses_increment_list;
     retval->end_list = curses_end_list;
 
     retval->is_quiet = curses_is_quiet;
     retval->spacer = curses_spacer;
     retval->progress = curses_progress;
     retval->prompt = curses_prompt;
 
     return retval;
 }
 
 G_GNUC_PRINTF(2, 0)
 void
 curses_formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
     vw_printw(stdscr, format, args);
 
     clrtoeol();
     refresh();
 }
 
 G_GNUC_PRINTF(2, 3)
 void
 curses_formatted_printf(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
 
     va_start(ap, format);
     curses_formatted_vprintf(out, format, ap);
     va_end(ap);
 }
 
 G_GNUC_PRINTF(2, 0)
 void
 curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
     int level = 0;
     private_data_t *priv = NULL;
 
     CRM_ASSERT(out != NULL && out->priv != NULL);
 
     priv = out->priv;
 
     level = g_queue_get_length(priv->parent_q);
 
     for (int i = 0; i < level; i++) {
         printw("  ");
     }
 
     if (level > 0) {
         printw("* ");
     }
 
     curses_formatted_vprintf(out, format, args);
 }
 
 G_GNUC_PRINTF(2, 3)
 void
 curses_indented_printf(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
 
     va_start(ap, format);
     curses_indented_vprintf(out, format, ap);
     va_end(ap);
 }
 
 PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int")
 static int
 cluster_maint_mode_console(pcmk__output_t *out, va_list args) {
     unsigned long long flags = va_arg(args, unsigned long long);
 
     if (pcmk_is_set(flags, pe_flag_maintenance_mode)) {
         curses_formatted_printf(out, "\n              *** Resource management is DISABLED ***\n");
         curses_formatted_printf(out, "  The cluster will not attempt to start, stop or recover services\n");
         return pcmk_rc_ok;
     } else if (pcmk_is_set(flags, pe_flag_stop_everything)) {
         curses_formatted_printf(out, "\n    *** Resource management is DISABLED ***\n");
         curses_formatted_printf(out, "  The cluster will keep all resources stopped\n");
         return pcmk_rc_ok;
     } else {
         return pcmk_rc_no_output;
     }
 }
 
-PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t",
+PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *",
+                  "enum pcmk_pacemakerd_state", "crm_exit_t",
                   "stonith_history_t *", "enum pcmk__fence_history", "uint32_t",
                   "uint32_t", "const char *", "GList *", "GList *")
 static int
 cluster_status_console(pcmk__output_t *out, va_list args) {
     int rc = pcmk_rc_no_output;
 
-    blank_screen();
+    clear();
     rc = pcmk__cluster_status_text(out, args);
     refresh();
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
                   "const char *", "uint32_t")
 static int
 stonith_event_console(pcmk__output_t *out, va_list args)
 {
     stonith_history_t *event = va_arg(args, stonith_history_t *);
     bool full_history = va_arg(args, int);
     bool completed_only G_GNUC_UNUSED = va_arg(args, int);
     const char *succeeded = va_arg(args, const char *);
     uint32_t show_opts = va_arg(args, uint32_t);
 
     gchar *desc = stonith__history_description(event, full_history, succeeded,
                                                show_opts);
 
 
     curses_indented_printf(out, "%s\n", desc);
     g_free(desc);
     return pcmk_rc_ok;
 }
 
 static pcmk__message_entry_t fmt_functions[] = {
     { "cluster-status", "console", cluster_status_console },
     { "maint-mode", "console", cluster_maint_mode_console },
     { "stonith-event", "console", stonith_event_console },
 
     { NULL, NULL, NULL }
 };
 
 #endif
 
 void
 crm_mon_register_messages(pcmk__output_t *out) {
 #if CURSES_ENABLED
     pcmk__register_messages(out, fmt_functions);
 #endif
 }
-
-void
-blank_screen(void)
-{
-#if CURSES_ENABLED
-    int lpc = 0;
-
-    for (lpc = 0; lpc < LINES; lpc++) {
-        move(lpc, 0);
-        clrtoeol();
-    }
-    move(0, 0);
-    refresh();
-#endif
-}
diff --git a/xml/api/crm_mon-2.26.rng b/xml/api/crm_mon-2.26.rng
new file mode 100644
index 0000000000..ac6410cc5e
--- /dev/null
+++ b/xml/api/crm_mon-2.26.rng
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+
+    <start>
+        <ref name="element-crm-mon"/>
+    </start>
+
+    <define name="element-crm-mon">
+        <choice>
+            <ref name="element-crm-mon-disconnected" />
+            <group>
+                <optional>
+                    <externalRef href="pacemakerd-health-2.25.rng" />
+                </optional>
+                <optional>
+                    <ref name="element-summary" />
+                </optional>
+                <optional>
+                    <ref name="nodes-list" />
+                </optional>
+                <optional>
+                    <ref name="resources-list" />
+                </optional>
+                <optional>
+                    <ref name="node-attributes-list" />
+                </optional>
+                <optional>
+                    <externalRef href="node-history-2.12.rng"/>
+                </optional>
+                <optional>
+                    <ref name="failures-list" />
+                </optional>
+                <optional>
+                    <ref name="fence-event-list" />
+                </optional>
+                <optional>
+                    <ref name="tickets-list" />
+                </optional>
+                <optional>
+                    <ref name="bans-list" />
+                </optional>
+            </group>
+        </choice>
+    </define>
+
+    <define name="element-crm-mon-disconnected">
+        <element name="crm-mon-disconnected">
+            <optional>
+                <attribute name="description"> <text /> </attribute>
+            </optional>
+            <optional>
+                <attribute name="pacemakerd-state"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-summary">
+        <element name="summary">
+            <optional>
+                <element name="stack">
+                    <attribute name="type"> <text /> </attribute>
+                    <optional>
+                        <attribute name="pacemakerd-state">
+                            <text />
+                        </attribute>
+                    </optional>
+                </element>
+            </optional>
+            <optional>
+                <element name="current_dc">
+                    <attribute name="present"> <data type="boolean" /> </attribute>
+                    <optional>
+                        <group>
+                            <attribute name="version"> <text /> </attribute>
+                            <attribute name="name"> <text /> </attribute>
+                            <attribute name="id"> <text /> </attribute>
+                            <attribute name="with_quorum"> <data type="boolean" /> </attribute>
+                        </group>
+                    </optional>
+                    <optional>
+                        <attribute name="mixed_version"> <data type="boolean" /> </attribute>
+                    </optional>
+                </element>
+            </optional>
+            <optional>
+                <element name="last_update">
+                    <attribute name="time"> <text /> </attribute>
+                </element>
+                <element name="last_change">
+                    <attribute name="time"> <text /> </attribute>
+                    <attribute name="user"> <text /> </attribute>
+                    <attribute name="client"> <text /> </attribute>
+                    <attribute name="origin"> <text /> </attribute>
+                </element>
+            </optional>
+            <optional>
+                <element name="nodes_configured">
+                    <attribute name="number"> <data type="nonNegativeInteger" /> </attribute>
+                </element>
+                <element name="resources_configured">
+                    <attribute name="number"> <data type="nonNegativeInteger" /> </attribute>
+                    <attribute name="disabled"> <data type="nonNegativeInteger" /> </attribute>
+                    <attribute name="blocked"> <data type="nonNegativeInteger" /> </attribute>
+                </element>
+            </optional>
+            <optional>
+                <element name="cluster_options">
+                    <attribute name="stonith-enabled"> <data type="boolean" /> </attribute>
+                    <attribute name="symmetric-cluster"> <data type="boolean" /> </attribute>
+                    <attribute name="no-quorum-policy"> <text /> </attribute>
+                    <attribute name="maintenance-mode"> <data type="boolean" /> </attribute>
+                    <attribute name="stop-all-resources"> <data type="boolean" /> </attribute>
+                    <attribute name="stonith-timeout-ms"> <data type="integer" /> </attribute>
+                    <attribute name="priority-fencing-delay-ms"> <data type="integer" /> </attribute>
+                </element>
+            </optional>
+        </element>
+    </define>
+
+    <define name="resources-list">
+        <element name="resources">
+            <zeroOrMore>
+                <externalRef href="resources-2.24.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="nodes-list">
+        <element name="nodes">
+            <zeroOrMore>
+                <externalRef href="nodes-2.24.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="node-attributes-list">
+        <element name="node_attributes">
+            <zeroOrMore>
+                <externalRef href="node-attrs-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="failures-list">
+        <element name="failures">
+            <zeroOrMore>
+                <externalRef href="failure-2.8.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="fence-event-list">
+        <element name="fence_history">
+            <optional>
+                <attribute name="status"> <data type="integer" /> </attribute>
+            </optional>
+            <zeroOrMore>
+                <externalRef href="fence-event-2.15.rng" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="tickets-list">
+        <element name="tickets">
+            <zeroOrMore>
+                <ref name="element-ticket" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="bans-list">
+        <element name="bans">
+            <zeroOrMore>
+                <ref name="element-ban" />
+            </zeroOrMore>
+        </element>
+    </define>
+
+    <define name="element-ticket">
+        <element name="ticket">
+            <attribute name="id"> <text /> </attribute>
+            <attribute name="status">
+                <choice>
+                    <value>granted</value>
+                    <value>revoked</value>
+                </choice>
+            </attribute>
+            <attribute name="standby"> <data type="boolean" /> </attribute>
+            <optional>
+                <attribute name="last-granted"> <text /> </attribute>
+            </optional>
+        </element>
+    </define>
+
+    <define name="element-ban">
+        <element name="ban">
+            <attribute name="id"> <text /> </attribute>
+            <attribute name="resource"> <text /> </attribute>
+            <attribute name="node"> <text /> </attribute>
+            <attribute name="weight"> <data type="integer" /> </attribute>
+            <attribute name="promoted-only"> <data type="boolean" /> </attribute>
+            <!-- DEPRECATED: master_only is a duplicate of promoted-only that is
+                 provided solely for API backward compatibility. It will be
+                 removed in a future release. Check promoted-only instead.
+              -->
+            <attribute name="master_only"> <data type="boolean" /> </attribute>
+        </element>
+    </define>
+</grammar>