diff --git a/include/crm/common/cmdline_internal.h b/include/crm/common/cmdline_internal.h
index cbed6d63f4..e07db6b7fc 100644
--- a/include/crm/common/cmdline_internal.h
+++ b/include/crm/common/cmdline_internal.h
@@ -1,91 +1,113 @@
 /*
  * Copyright 2019 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 CMDLINE_INTERNAL__H
 #define CMDLINE_INTERNAL__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 #include 
 
 typedef struct {
     char *summary;
 
     gboolean version;
     gboolean quiet;
     unsigned int verbosity;
 
     char *output_ty;
     char *output_dest;
 } pcmk__common_args_t;
 
 /*!
  * \internal
  * \brief Create and return a GOptionContext containing the command line options
  *        supported by all tools.
  *
  * \note Formatted output options will be added unless fmts is NULL.  This allows
  *       for using this function in tools that have not yet been converted to
  *       formatted output.  It should not be NULL in any tool that calls
  *       pcmk__register_formats() as that function adds its own command line
  *       options.
  *
  * \param[in,out] common_args A ::pcmk__common_args_t structure where the
  *                            results of handling command options will be written.
  * \param[in]     fmts        The help string for which formats are supported.
  */
 GOptionContext *
 pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts);
 
 /*!
  * \internal
  * \brief Pre-process command line arguments to preserve compatibility with
  *        getopt behavior.
  *
  * getopt and glib have slightly different behavior when it comes to processing
  * single command line arguments.  getopt allows this:  -x, while glib will
  * try to handle  like it is additional single letter arguments.  glib
  * prefers -x  instead.
  *
  * This function scans argv, looking for any single letter command line options
  * (indicated by the 'special' parameter).  When one is found, everything after
  * that argument to the next whitespace is converted into its own value.  Single
  * letter command line options can come in a group after a single dash, but
  * this function will expand each group into many arguments.
  *
  * Long options and anything after "--" is preserved.  The result of this function
  * can then be passed to ::g_option_context_parse_strv for actual processing.
  *
  * In pseudocode, this:
  *
  * pcmk__cmdline_preproc(4, ["-XbA", "--blah=foo", "-aF", "-Fval", "--", "--extra", "-args"], "aF")
  *
  * Would be turned into this:
  *
  * ["-X", "-b", "-A", "--blah=foo", "-a", "F", "-F", "val", "--", "--extra", "-args"]
  *
  * This function does not modify argv, and the return value is built of copies
  * of all the command line arguments.  It is up to the caller to free this memory
  * after use.
  *
  * \param[in] argc    The length of argv.
  * \param[in] argv    The command line arguments.
  * \param[in] special Single-letter command line arguments that take a value.
  *                    These letters will all have pre-processing applied.
  */
 char **
 pcmk__cmdline_preproc(int argc, char **argv, const char *special);
 
+/*!
+ * \internal
+ * \brief Process extra arguments as if they were provided by the user on the
+ *        command line.
+ *
+ * \param[in,out] context The command line option processing context.
+ * \param[out]    error   A place for errors to be collected.
+ * \param[in]     format  The command line to be processed, potentially with
+ *                        format specifiers.
+ * \param[in]     ...     Arguments to be formatted.
+ *
+ * \note The first item in the list of arguments must be the name of the
+ *       program, exactly as if the format string were coming from the
+ *       command line.  Otherwise, the first argument will be ignored.
+ *
+ * \return TRUE if processing succeeded, or FALSE otherwise.  If FALSE, error
+ *         should be checked and displayed to the user.
+ */
+G_GNUC_PRINTF(3, 4)
+gboolean
+pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...);
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c
index b2eb707fae..30ab8bbd4a 100644
--- a/lib/common/cmdline.c
+++ b/lib/common/cmdline.c
@@ -1,157 +1,184 @@
 /*
  * Copyright 2019 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 _GNU_SOURCE
+#  define _GNU_SOURCE
+#endif
+
 #include 
 #include 
 
+#include 
 #include 
 #include 
 
 static gboolean
 bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
     common_args->verbosity++;
     return TRUE;
 }
 
 static void
 free_common_args(gpointer data) {
     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
 
     free(common_args->summary);
     free(common_args->output_ty);
     free(common_args->output_dest);
 }
 
 GOptionContext *
 pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts) {
     char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
     GOptionContext *context;
     GOptionGroup *main_group;
 
     GOptionEntry main_entries[3] = {
         { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
           "Display version information and exit.",
           NULL },
         { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
           "Increase debug output (may be specified multiple times).",
           NULL },
 
         { NULL }
     };
 
     main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
     g_option_group_add_entries(main_group, main_entries);
 
     context = g_option_context_new(NULL);
     g_option_context_set_summary(context, common_args->summary);
     g_option_context_set_description(context, desc);
     g_option_context_set_main_group(context, main_group);
 
     if (fmts != NULL) {
         GOptionEntry output_main_entries[3] = {
             { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
               NULL,
               "FORMAT" },
             { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
               "Specify the destination for output, \"-\" for stdout or a filename.", "DEST" },
 
             { NULL }
         };
 
         output_main_entries[0].description = crm_strdup_printf("Specify the format for output, one of: %s", fmts);
         g_option_context_add_main_entries(context, output_main_entries, NULL);
     }
 
     free(desc);
 
     return context;
 }
 
 char **
 pcmk__cmdline_preproc(int argc, char **argv, const char *special) {
     char **retval = NULL;
     GPtrArray *arr = g_ptr_array_new();
     bool saw_dash_dash = false;
 
     for (int i = 0; i < argc; i++) {
         /* If this is the first time we saw "--" in the command line, set
          * a flag so we know to just copy everything after it over.  We also
          * want to copy the "--" over so whatever actually parses the command
          * line when we're done knows where arguments end.
          */
         if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
             saw_dash_dash = true;
         }
 
         if (saw_dash_dash == true) {
             g_ptr_array_add(arr, strdup(argv[i]));
             continue;
         }
 
         /* This is a short argument, or perhaps several.  Iterate over it
          * and explode them out into individual arguments.
          */
         if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
             /* Skip over leading dash */
             char *ch = argv[i]+1;
 
             while (*ch != '\0') {
                 /* This is a special short argument that takes an option.  getopt
                  * allows values to be interspersed with a list of arguments, but
                  * glib does not.  Grab both the argument and its value and
                  * separate them into a new argument.
                  */
                 if (strchr(special, *ch) != NULL) {
                     /* The argument does not occur at the end of this string of
                      * arguments.  Take everything through the end as its value.
                      */
                     if (*(ch+1) != '\0') {
                         g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch));
                         g_ptr_array_add(arr, strdup(ch+1));
                         break;
 
                     /* The argument occurs at the end of this string.  Hopefully
                      * whatever comes next in argv is its value.  It may not be,
                      * but that is not for us to decide.
                      */
                     } else {
                         g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch));
                         ch++;
                     }
 
                 /* This is a regular short argument.  Just copy it over. */
                 } else {
                     g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch));
                     ch++;
                 }
             }
 
         /* This is a long argument, or an option, or something else.
          * Copy it over - everything else is copied, so this keeps it easy for
          * the caller to know what to do with the memory when it's done.
          */
         } else {
             g_ptr_array_add(arr, strdup(argv[i]));
         }
     }
 
     /* Convert the GPtrArray into a char **, which the command line parsing
      * code knows how to deal with.  Then we can free the array (but not its
      * contents).
      */
     retval = calloc(arr->len+1, sizeof(char *));
     for (int i = 0; i < arr->len; i++) {
         retval [i] = (char *) g_ptr_array_index(arr, i);
     }
 
     g_ptr_array_free(arr, FALSE);
 
     return retval;
 }
+
+G_GNUC_PRINTF(3, 4)
+gboolean
+pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) {
+    int len = 0;
+    char *buf = NULL;
+    gchar **extra_args = NULL;
+    va_list ap;
+    gboolean retval = TRUE;
+
+    va_start(ap, format);
+    len = vasprintf(&buf, format, ap);
+    CRM_ASSERT(len > 0);
+    va_end(ap);
+
+    extra_args = g_strsplit(buf, " ", -1);
+    retval = g_option_context_parse_strv(context, &extra_args, error);
+
+    g_strfreev(extra_args);
+    free(buf);
+    return retval;
+}
diff --git a/lib/common/output_html.c b/lib/common/output_html.c
index e45edd25d6..1a0ad5c69e 100644
--- a/lib/common/output_html.c
+++ b/lib/common/output_html.c
@@ -1,345 +1,345 @@
 /*
  * Copyright 2019 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 _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 static const char *stylesheet_default =
     ".bold { font-weight: bold }\n"
     ".warning { color: red, font-weight: bold }";
 
 static gboolean cgi_output = FALSE;
 static int meta_refresh = 0;
 static char *stylesheet_link = NULL;
 static char *title = NULL;
 
 GOptionEntry pcmk__html_output_entries[] = {
-    { "cgi-output", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
+    { "output-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
       "Add text needed to use output in a CGI program",
       NULL },
 
-    { "meta-refresh", 0, 0, G_OPTION_ARG_INT, &meta_refresh,
+    { "output-meta-refresh", 0, 0, G_OPTION_ARG_INT, &meta_refresh,
       "How often to refresh",
       "SECONDS" },
 
-    { "stylesheet-link", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
+    { "output-stylesheet-link", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
       "Link to an external CSS stylesheet",
       "URI" },
 
-    { "title", 0, 0, G_OPTION_ARG_STRING, &title,
+    { "output-title", 0, 0, G_OPTION_ARG_STRING, &title,
       "Page title (defaults to command line)",
       "TITLE" },
 
     { NULL }
 };
 
 typedef struct private_data_s {
     xmlNode *root;
     GQueue *parent_q;
     GSList *errors;
 } private_data_t;
 
 static void
 html_free_priv(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     if (priv == NULL) {
         return;
     }
 
     xmlFreeNode(priv->root);
     g_queue_free(priv->parent_q);
     g_slist_free(priv->errors);
     free(priv);
 }
 
 static bool
 html_init(pcmk__output_t *out) {
     private_data_t *priv = NULL;
 
     /* If html_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();
 
     priv->root = create_xml_node(NULL, "html");
     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
 
     xmlSetProp(priv->root, (pcmkXmlStr) "lang", (pcmkXmlStr) "en");
     g_queue_push_tail(priv->parent_q, priv->root);
     priv->errors = NULL;
 
     pcmk__output_xml_create_parent(out, "body");
 
     return true;
 }
 
 static void
 add_error_node(gpointer data, gpointer user_data) {
     char *str = (char *) data;
     pcmk__output_t *out = (pcmk__output_t *) user_data;
     out->list_item(out, NULL, str);
 }
 
 static void
 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     private_data_t *priv = out->priv;
     htmlNodePtr head_node = NULL;
     htmlNodePtr charset_node = NULL;
 
     /* If root is NULL, html_init failed and we are being called from pcmk__output_free
      * in the pcmk__output_new path.
      */
     if (priv->root == NULL) {
         return;
     }
 
     if (cgi_output && print) {
         fprintf(out->dest, "Content-Type: text/html\n\n");
     }
 
     /* Add the head node last - it's not needed earlier because it doesn't contain
      * anything else that the user could add, and we want it done last to pick up
      * any options that may have been given.
      */
     head_node = xmlNewNode(NULL, (pcmkXmlStr) "head");
 
     if (title != NULL ) {
         pcmk_create_xml_text_node(head_node, "title", title);
     } else if (out->request != NULL) {
         pcmk_create_xml_text_node(head_node, "title", out->request);
     }
 
     charset_node = create_xml_node(head_node, "meta");
     xmlSetProp(charset_node, (pcmkXmlStr) "charset", (pcmkXmlStr) "utf-8");
 
     if (meta_refresh != 0) {
         htmlNodePtr refresh_node = create_xml_node(head_node, "meta");
         xmlSetProp(refresh_node, (pcmkXmlStr) "http-equiv", (pcmkXmlStr) "refresh");
         xmlSetProp(refresh_node, (pcmkXmlStr) "content", (pcmkXmlStr) crm_itoa(meta_refresh));
     }
 
     /* Stylesheets are included two different ways.  The first is via a built-in
      * default (see the stylesheet_default const above).  The second is via the
      * "stylesheet-link" option, and this should obviously be a link to a
      * stylesheet.  The second can override the first.  At least one should be
      * given.
      */
     pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
 
     if (stylesheet_link != NULL) {
         htmlNodePtr link_node = create_xml_node(head_node, "link");
         xmlSetProp(link_node, (pcmkXmlStr) "rel", (pcmkXmlStr) "stylesheet");
         xmlSetProp(link_node, (pcmkXmlStr) "href", (pcmkXmlStr) stylesheet_link);
     }
 
     xmlAddPrevSibling(priv->root->children, head_node);
 
     if (g_slist_length(priv->errors) > 0) {
         out->begin_list(out, "Errors", NULL, NULL);
         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
         out->end_list(out);
     }
 
     if (print) {
         htmlDocDump(out->dest, priv->root->doc);
     }
 
     if (copy_dest != NULL) {
         *copy_dest = copy_xml(priv->root);
     }
 }
 
 static void
 html_reset(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     htmlDocDump(out->dest, priv->root->doc);
 
     html_free_priv(out);
     html_init(out);
 }
 
 static void
 html_subprocess_output(pcmk__output_t *out, int exit_status,
                       const char *proc_stdout, const char *proc_stderr) {
     char *rc_buf = NULL;
     private_data_t *priv = out->priv;
     CRM_ASSERT(priv != NULL);
 
     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
 
     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
     pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
 
     if (proc_stdout != NULL) {
         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
     }
     if (proc_stderr != NULL) {
         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
     }
 
     free(rc_buf);
 }
 
 static void
 html_version(pcmk__output_t *out, bool extended) {
     private_data_t *priv = out->priv;
     CRM_ASSERT(priv != NULL);
 
     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
     pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
     pcmk__output_create_html_node(out, "div", NULL, NULL, "Author: Andrew Beekhof");
     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 html_err(pcmk__output_t *out, const char *format, ...) {
     private_data_t *priv = out->priv;
     int len = 0;
     char *buf = NULL;
     va_list ap;
 
     CRM_ASSERT(priv != NULL);
     va_start(ap, format);
     len = vasprintf(&buf, format, ap);
     CRM_ASSERT(len > 0);
     va_end(ap);
 
     priv->errors = g_slist_append(priv->errors, buf);
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 html_info(pcmk__output_t *out, const char *format, ...) {
     /* This function intentially left blank */
 }
 
 static void
 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     htmlNodePtr node = NULL;
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
     xmlSetProp(node, (pcmkXmlStr) "lang", (pcmkXmlStr) "xml");
 }
 
 static void
 html_begin_list(pcmk__output_t *out, const char *name,
                const char *singular_noun, const char *plural_noun) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     if (name != NULL) {
         pcmk__output_create_xml_text_node(out, "h2", name);
     }
 
     pcmk__output_xml_create_parent(out, "ul");
 }
 
 static void
 html_list_item(pcmk__output_t *out, const char *name, const char *content) {
     private_data_t *priv = out->priv;
     htmlNodePtr item_node = NULL;
 
     CRM_ASSERT(priv != NULL);
 
     item_node = pcmk__output_create_xml_text_node(out, "li", content);
 
     if (name != NULL) {
         xmlSetProp(item_node, (pcmkXmlStr) "class", (pcmkXmlStr) name);
     }
 }
 
 static void
 html_end_list(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     g_queue_pop_tail(priv->parent_q);
 }
 
 pcmk__output_t *
 pcmk__mk_html_output(char **argv) {
     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 
     if (retval == NULL) {
         return NULL;
     }
 
     retval->fmt_name = "html";
     retval->request = g_strjoinv(" ", argv);
     retval->supports_quiet = false;
 
     retval->init = html_init;
     retval->free_priv = html_free_priv;
     retval->finish = html_finish;
     retval->reset = html_reset;
 
     retval->register_message = pcmk__register_message;
     retval->message = pcmk__call_message;
 
     retval->subprocess_output = html_subprocess_output;
     retval->version = html_version;
     retval->info = html_info;
     retval->err = html_err;
     retval->output_xml = html_output_xml;
 
     retval->begin_list = html_begin_list;
     retval->list_item = html_list_item;
     retval->end_list = html_end_list;
 
     return retval;
 }
 
 xmlNodePtr
 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
                        const char *class_name, const char *text) {
     htmlNodePtr node = pcmk__output_create_xml_text_node(out, element_name, text);
 
     if (class_name != NULL) {
         xmlSetProp(node, (pcmkXmlStr) "class", (pcmkXmlStr) class_name);
     }
 
     if (id != NULL) {
         xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) id);
     }
 
     return node;
 }
diff --git a/lib/common/output_text.c b/lib/common/output_text.c
index 5dd9faeac1..93a9f2bece 100644
--- a/lib/common/output_text.c
+++ b/lib/common/output_text.c
@@ -1,266 +1,266 @@
 /*
  * Copyright 2019 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 
 #include 
 #include 
 #include 
 
 static gboolean fancy = FALSE;
 
 GOptionEntry pcmk__text_output_entries[] = {
-    { "fancy", 0, 0, G_OPTION_ARG_NONE, &fancy,
+    { "output-fancy", 0, 0, G_OPTION_ARG_NONE, &fancy,
       "Use more highly formatted output",
       NULL },
 
     { NULL }
 };
 
 typedef struct text_list_data_s {
     unsigned int len;
     char *singular_noun;
     char *plural_noun;
 } text_list_data_t;
 
 typedef struct private_data_s {
     GQueue *parent_q;
 } private_data_t;
 
 static void
 text_free_priv(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     if (priv == NULL) {
         return;
     }
 
     g_queue_free(priv->parent_q);
     free(priv);
 }
 
 static bool
 text_init(pcmk__output_t *out) {
     private_data_t *priv = NULL;
 
     /* If text_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();
     return true;
 }
 
 static void
 text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* This function intentionally left blank */
 }
 
 static void
 text_reset(pcmk__output_t *out) {
     CRM_ASSERT(out->priv != NULL);
 
     text_free_priv(out);
     text_init(out);
 }
 
 static void
 text_subprocess_output(pcmk__output_t *out, int exit_status,
                        const char *proc_stdout, const char *proc_stderr) {
     if (proc_stdout != NULL) {
         fprintf(out->dest, "%s\n", proc_stdout);
     }
 
     if (proc_stderr != NULL) {
         fprintf(out->dest, "%s\n", proc_stderr);
     }
 }
 
 static void
 text_version(pcmk__output_t *out, bool extended) {
     if (extended) {
         fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
     } else {
         fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION);
         fprintf(out->dest, "Written by Andrew Beekhof\n");
     }
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 text_err(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
     int len = 0;
 
     va_start(ap, format);
 
     /* Informational output does not get indented, to separate it from other
      * potentially indented list output.
      */
     len = vfprintf(stderr, format, ap);
     CRM_ASSERT(len > 0);
     va_end(ap);
 
     /* Add a newline. */
     fprintf(stderr, "\n");
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 text_info(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
     int len = 0;
 
     va_start(ap, format);
 
     /* Informational output does not get indented, to separate it from other
      * potentially indented list output.
      */
     len = vfprintf(out->dest, format, ap);
     CRM_ASSERT(len > 0);
     va_end(ap);
 
     /* Add a newline. */
     fprintf(out->dest, "\n");
 }
 
 static void
 text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
     pcmk__indented_printf(out, "%s", buf);
 }
 
 static void
 text_begin_list(pcmk__output_t *out, const char *name, const char *singular_noun,
                 const char *plural_noun) {
     private_data_t *priv = out->priv;
     text_list_data_t *new_list = NULL;
 
     CRM_ASSERT(priv != NULL);
 
     if (fancy) {
         pcmk__indented_printf(out, "%s:\n", name);
     }
 
     new_list = calloc(1, sizeof(text_list_data_t));
     new_list->len = 0;
     new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun);
     new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun);
 
     g_queue_push_tail(priv->parent_q, new_list);
 }
 
 static void
 text_list_item(pcmk__output_t *out, const char *id, const char *content) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     if (fancy) {
         if (id != NULL) {
             pcmk__indented_printf(out, "* %s: %s\n", id, content);
         } else {
             pcmk__indented_printf(out, "* %s\n", content);
         }
     } else {
         fprintf(out->dest, "%s\n", content);
     }
 
     ((text_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++;
 }
 
 static void
 text_end_list(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
     text_list_data_t *node = NULL;
 
     CRM_ASSERT(priv != NULL);
     node = g_queue_pop_tail(priv->parent_q);
 
     if (node->singular_noun != NULL && node->plural_noun != NULL) {
         if (node->len == 1) {
             pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
         } else {
             pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
         }
     }
 
     free(node);
 }
 
 pcmk__output_t *
 pcmk__mk_text_output(char **argv) {
     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 
     if (retval == NULL) {
         return NULL;
     }
 
     retval->fmt_name = "text";
     retval->request = g_strjoinv(" ", argv);
     retval->supports_quiet = true;
 
     retval->init = text_init;
     retval->free_priv = text_free_priv;
     retval->finish = text_finish;
     retval->reset = text_reset;
 
     retval->register_message = pcmk__register_message;
     retval->message = pcmk__call_message;
 
     retval->subprocess_output = text_subprocess_output;
     retval->version = text_version;
     retval->info = text_info;
     retval->err = text_err;
     retval->output_xml = text_output_xml;
 
     retval->begin_list = text_begin_list;
     retval->list_item = text_list_item;
     retval->end_list = text_end_list;
 
     return retval;
 }
 
 G_GNUC_PRINTF(2, 3)
 void
 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
     va_list ap;
     int len = 0;
 
     if (fancy) {
         int level = 0;
         private_data_t *priv = out->priv;
 
         CRM_ASSERT(priv != NULL);
 
         level = g_queue_get_length(priv->parent_q);
 
         for (int i = 0; i < level; i++) {
             putc('\t', out->dest);
         }
 
         if (level > 0) {
             fprintf(out->dest, "* ");
         }
     }
 
     va_start(ap, format);
     len = vfprintf(out->dest, format, ap);
     CRM_ASSERT(len > 0);
     va_end(ap);
 }
diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c
index f9085492b4..7fb492eef6 100644
--- a/lib/common/output_xml.c
+++ b/lib/common/output_xml.c
@@ -1,339 +1,368 @@
 /*
  * Copyright 2019 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 _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
+#include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
+static gboolean legacy_xml = FALSE;
+
 GOptionEntry pcmk__xml_output_entries[] = {
+    { "output-legacy-xml", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml,
+      NULL,
+      NULL },
+
     { NULL }
 };
 
 typedef struct private_data_s {
     xmlNode *root;
     GQueue *parent_q;
     GSList *errors;
+    bool legacy_xml;
 } private_data_t;
 
 static void
 xml_free_priv(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     if (priv == NULL) {
         return;
     }
 
     xmlFreeNode(priv->root);
     g_queue_free(priv->parent_q);
     g_slist_free(priv->errors);
     free(priv);
 }
 
 static bool
 xml_init(pcmk__output_t *out) {
     private_data_t *priv = NULL;
 
     /* If xml_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->root = create_xml_node(NULL, "pacemaker-result");
-    xmlSetProp(priv->root, (pcmkXmlStr) "api-version", (pcmkXmlStr) PCMK__API_VERSION);
+    if (legacy_xml) {
+        priv->root = create_xml_node(NULL, "crm_mon");
+        xmlSetProp(priv->root, (pcmkXmlStr) "version", (pcmkXmlStr) VERSION);
+    } else {
+        priv->root = create_xml_node(NULL, "pacemaker-result");
+        xmlSetProp(priv->root, (pcmkXmlStr) "api-version", (pcmkXmlStr) PCMK__API_VERSION);
 
-    if (out->request != NULL) {
-        xmlSetProp(priv->root, (pcmkXmlStr) "request", (pcmkXmlStr) out->request);
+        if (out->request != NULL) {
+            xmlSetProp(priv->root, (pcmkXmlStr) "request", (pcmkXmlStr) out->request);
+        }
     }
 
     priv->parent_q = g_queue_new();
     priv->errors = NULL;
     g_queue_push_tail(priv->parent_q, priv->root);
 
+    /* Copy this from the file-level variable.  This means that it is only settable
+     * as a command line option, and that pcmk__output_new must be called after all
+     * command line processing is completed.
+     */
+    priv->legacy_xml = legacy_xml;
+
     return true;
 }
 
 static void
 add_error_node(gpointer data, gpointer user_data) {
     char *str = (char *) data;
     xmlNodePtr node = (xmlNodePtr) user_data;
     pcmk_create_xml_text_node(node, "error", str);
 }
 
 static void
 xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     xmlNodePtr node;
-    char *rc_as_str = NULL;
     private_data_t *priv = out->priv;
 
     /* If root is NULL, xml_init failed and we are being called from pcmk__output_free
      * in the pcmk__output_new path.
      */
     if (priv->root == NULL) {
         return;
     }
 
-    rc_as_str = crm_itoa(exit_status);
+    if (!legacy_xml) {
+        char *rc_as_str = crm_itoa(exit_status);
 
-    node = create_xml_node(priv->root, "status");
-    xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
-    xmlSetProp(node, (pcmkXmlStr) "message", (pcmkXmlStr) crm_exit_str(exit_status));
+        node = create_xml_node(priv->root, "status");
+        xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
+        xmlSetProp(node, (pcmkXmlStr) "message", (pcmkXmlStr) crm_exit_str(exit_status));
+
+        if (g_slist_length(priv->errors) > 0) {
+            xmlNodePtr errors_node = create_xml_node(node, "errors");
+            g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
+        }
 
-    if (g_slist_length(priv->errors) > 0) {
-        xmlNodePtr errors_node = create_xml_node(node, "errors");
-        g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
+        free(rc_as_str);
     }
 
     if (print) {
         char *buf = dump_xml_formatted_with_text(priv->root);
         fprintf(out->dest, "%s", buf);
         free(buf);
     }
 
     if (copy_dest != NULL) {
         *copy_dest = copy_xml(priv->root);
     }
-
-    free(rc_as_str);
 }
 
 static void
 xml_reset(pcmk__output_t *out) {
     char *buf = NULL;
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     buf = dump_xml_formatted_with_text(priv->root);
     fprintf(out->dest, "%s", buf);
 
     free(buf);
     xml_free_priv(out);
     xml_init(out);
 }
 
 static void
 xml_subprocess_output(pcmk__output_t *out, int exit_status,
                       const char *proc_stdout, const char *proc_stderr) {
     xmlNodePtr node, child_node;
     char *rc_as_str = NULL;
 
     rc_as_str = crm_itoa(exit_status);
 
     node = pcmk__output_xml_create_parent(out, "command");
     xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str);
 
     if (proc_stdout != NULL) {
         child_node = pcmk_create_xml_text_node(node, "output", proc_stdout);
         xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stdout");
     }
 
     if (proc_stderr != NULL) {
         child_node = pcmk_create_xml_text_node(node, "output", proc_stderr);
         xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stderr");
     }
 
     pcmk__output_xml_add_node(out, node);
     free(rc_as_str);
 }
 
 static void
 xml_version(pcmk__output_t *out, bool extended) {
     xmlNodePtr node;
     private_data_t *priv = out->priv;
     CRM_ASSERT(priv != NULL);
 
     node = pcmk__output_create_xml_node(out, "version");
     xmlSetProp(node, (pcmkXmlStr) "program", (pcmkXmlStr) "Pacemaker");
     xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) PACEMAKER_VERSION);
     xmlSetProp(node, (pcmkXmlStr) "author", (pcmkXmlStr) "Andrew Beekhof");
     xmlSetProp(node, (pcmkXmlStr) "build", (pcmkXmlStr) BUILD_VERSION);
     xmlSetProp(node, (pcmkXmlStr) "features", (pcmkXmlStr) CRM_FEATURES);
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 xml_err(pcmk__output_t *out, const char *format, ...) {
     private_data_t *priv = out->priv;
     int len = 0;
     char *buf = NULL;
     va_list ap;
 
     CRM_ASSERT(priv != NULL);
     va_start(ap, format);
     len = vasprintf(&buf, format, ap);
     CRM_ASSERT(len > 0);
     va_end(ap);
 
     priv->errors = g_slist_append(priv->errors, buf);
 }
 
 G_GNUC_PRINTF(2, 3)
 static void
 xml_info(pcmk__output_t *out, const char *format, ...) {
     /* This function intentially left blank */
 }
 
 static void
 xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     xmlNodePtr parent = NULL;
     xmlNodePtr cdata_node = NULL;
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     parent = pcmk__output_create_xml_node(out, name);
     cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf));
     xmlAddChild(parent, cdata_node);
 }
 
 static void
 xml_begin_list(pcmk__output_t *out, const char *name,
                const char *singular_noun, const char *plural_noun) {
-    xmlNodePtr list_node = NULL;
+    if (legacy_xml) {
+        pcmk__output_xml_create_parent(out, name);
+    } else {
+        xmlNodePtr list_node = NULL;
 
-    list_node = pcmk__output_xml_create_parent(out, "list");
-    xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
+        list_node = pcmk__output_xml_create_parent(out, "list");
+        xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
+    }
 }
 
 static void
 xml_list_item(pcmk__output_t *out, const char *name, const char *content) {
     private_data_t *priv = out->priv;
     xmlNodePtr item_node = NULL;
 
     CRM_ASSERT(priv != NULL);
 
     item_node = pcmk__output_create_xml_text_node(out, "item", content);
     xmlSetProp(item_node, (pcmkXmlStr) "name", (pcmkXmlStr) name);
 }
 
 static void
 xml_end_list(pcmk__output_t *out) {
-    char *buf = NULL;
     private_data_t *priv = out->priv;
-    xmlNodePtr node;
 
     CRM_ASSERT(priv != NULL);
 
-    node = g_queue_pop_tail(priv->parent_q);
-    buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
-    xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf);
-    free(buf);
+    if (priv->legacy_xml) {
+        g_queue_pop_tail(priv->parent_q);
+    } else {
+        char *buf = NULL;
+        xmlNodePtr node;
+
+        node = g_queue_pop_tail(priv->parent_q);
+        buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
+        xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf);
+        free(buf);
+    }
 }
 
 pcmk__output_t *
 pcmk__mk_xml_output(char **argv) {
     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 
     if (retval == NULL) {
         return NULL;
     }
 
     retval->fmt_name = "xml";
     retval->request = g_strjoinv(" ", argv);
     retval->supports_quiet = false;
 
     retval->init = xml_init;
     retval->free_priv = xml_free_priv;
     retval->finish = xml_finish;
     retval->reset = xml_reset;
 
     retval->register_message = pcmk__register_message;
     retval->message = pcmk__call_message;
 
     retval->subprocess_output = xml_subprocess_output;
     retval->version = xml_version;
     retval->info = xml_info;
     retval->err = xml_err;
     retval->output_xml = xml_output_xml;
 
     retval->begin_list = xml_begin_list;
     retval->list_item = xml_list_item;
     retval->end_list = xml_end_list;
 
     return retval;
 }
 
 xmlNodePtr
 pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name) {
     xmlNodePtr node = pcmk__output_create_xml_node(out, name);
     pcmk__output_xml_push_parent(out, node);
     return node;
 }
 
 void
 pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
     CRM_ASSERT(node != NULL);
 
     xmlAddChild(g_queue_peek_tail(priv->parent_q), node);
 }
 
 xmlNodePtr
 pcmk__output_create_xml_node(pcmk__output_t *out, const char *name) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     return create_xml_node(g_queue_peek_tail(priv->parent_q), name);
 }
 
 xmlNodePtr
 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
     xmlNodePtr node = pcmk__output_create_xml_node(out, name);
     xmlNodeSetContent(node, (pcmkXmlStr) content);
     return node;
 }
 
 void
 pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
     CRM_ASSERT(parent != NULL);
 
     g_queue_push_tail(priv->parent_q, parent);
 }
 
 void
 pcmk__output_xml_pop_parent(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
     CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
 
     g_queue_pop_tail(priv->parent_q);
 }
 
 xmlNodePtr
 pcmk__output_xml_peek_parent(pcmk__output_t *out) {
     private_data_t *priv = out->priv;
 
     CRM_ASSERT(priv != NULL);
 
     /* If queue is empty NULL will be returned */
     return g_queue_peek_tail(priv->parent_q);
 }
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 3b65984b25..655e5c7816 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,151 +1,151 @@
 #
 # Copyright 2004-2019 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 $(top_srcdir)/Makefile.common
 
 if BUILD_SYSTEMD
 systemdunit_DATA = crm_mon.service
 endif
 
 noinst_HEADERS		= crm_mon.h crm_resource.h
 
 pcmkdir			= $(datadir)/$(PACKAGE)
 pcmk_DATA		= report.common report.collector
 
 sbin_SCRIPTS		= crm_report crm_standby crm_master crm_failcount
 if BUILD_CIBSECRETS
 sbin_SCRIPTS		+= cibsecret
 endif
 EXTRA_DIST		= $(sbin_SCRIPTS) stonith_admin.8.inc
 
 sbin_PROGRAMS		= attrd_updater \
 			  cibadmin \
 			  crmadmin \
 			  crm_simulate \
 			  crm_attribute \
 			  crm_diff \
 			  crm_error \
 			  crm_mon \
 			  crm_node \
 			  crm_resource \
 			  crm_rule \
 			  crm_shadow \
 			  crm_verify \
 			  crm_ticket \
 			  iso8601 \
 			  stonith_admin
 
 if BUILD_SERVICELOG
 sbin_PROGRAMS		+= notifyServicelogEvent
 endif
 if BUILD_OPENIPMI_SERVICELOG
 sbin_PROGRAMS		+= ipmiservicelogd
 endif
 
 ## SOURCES
 
 MAN8DEPS		= crm_attribute crm_node
 
 crmadmin_SOURCES	= crmadmin.c
 crmadmin_LDADD		= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_error_SOURCES	= crm_error.c
 crm_error_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
 cibadmin_SOURCES	= cibadmin.c
 cibadmin_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_shadow_SOURCES	= crm_shadow.c
 crm_shadow_LDADD	= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_node_SOURCES	= crm_node.c
 crm_node_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_simulate_SOURCES	= crm_simulate.c
 
 crm_simulate_LDADD	= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_diff_SOURCES	= crm_diff.c
 crm_diff_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
-crm_mon_SOURCES		= crm_mon.c crm_mon_print.c crm_mon_runtime.c
+crm_mon_SOURCES		= crm_mon.c crm_mon_curses.c crm_mon_print.c crm_mon_runtime.c
 crm_mon_LDADD		= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/fencing/libstonithd.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la	\
 			  $(CURSESLIBS)
 
 # Arguments could be made that this should live in crm/pengine
 crm_verify_SOURCES	= crm_verify.c
 crm_verify_LDADD	= $(top_builddir)/lib/pengine/libpe_status.la 	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_attribute_SOURCES	= crm_attribute.c
 crm_attribute_LDADD	= $(top_builddir)/lib/cluster/libcrmcluster.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_resource_SOURCES	= crm_resource.c crm_resource_ban.c crm_resource_runtime.c crm_resource_print.c
 crm_resource_LDADD	= $(top_builddir)/lib/pengine/libpe_rules.la  	\
 			  $(top_builddir)/lib/fencing/libstonithd.la	\
 			  $(top_builddir)/lib/lrmd/liblrmd.la 		\
 			  $(top_builddir)/lib/services/libcrmservice.la \
 			  $(top_builddir)/lib/pengine/libpe_status.la 	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_rule_SOURCES 	= crm_rule.c
 crm_rule_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/pengine/libpe_rules.la   \
 			  $(top_builddir)/lib/pengine/libpe_status.la   \
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 iso8601_SOURCES		= iso8601.c
 iso8601_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
 attrd_updater_SOURCES	= attrd_updater.c
 attrd_updater_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_ticket_SOURCES	= crm_ticket.c
 crm_ticket_LDADD	= $(top_builddir)/lib/pengine/libpe_rules.la	\
 			  $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 stonith_admin_SOURCES	= stonith_admin.c
 stonith_admin_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/fencing/libstonithd.la
 
 if BUILD_SERVICELOG
 notifyServicelogEvent_SOURCES	= notifyServicelogEvent.c
 notifyServicelogEvent_CFLAGS	= $(SERVICELOG_CFLAGS)
 notifyServicelogEvent_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS)
 endif
 
 if BUILD_OPENIPMI_SERVICELOG
 ipmiservicelogd_SOURCES	= ipmiservicelogd.c
 ipmiservicelogd_CFLAGS	= $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS)
 ipmiservicelogd_LDFLAGS	= $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS)
 endif
 
 CLEANFILES = $(man8_MANS)
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index 59c2c567d7..798a3c2390 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,1764 +1,1897 @@
 /*
  * Copyright 2004-2019 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 
 
 #include 
 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include   /* crm_ends_with_ext */
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 #include "crm_mon.h"
 
 /*
  * Definitions indicating which items to print
  */
 
 static unsigned int show = mon_show_default;
 
 /*
  * Definitions indicating how to output
  */
 
-static mon_output_format_t output_format = mon_output_console;
+static mon_output_format_t output_format = mon_output_unset;
 
 static char *output_filename = NULL;   /* if sending output to a file, its name */
 
 /* other globals */
 static GMainLoop *mainloop = NULL;
 static guint timer_id = 0;
 static mainloop_timer_t *refresh_timer = NULL;
 static pe_working_set_t *mon_data_set = NULL;
 
 static cib_t *cib = NULL;
 static stonith_t *st = NULL;
 static xmlNode *current_cib = NULL;
 
+static pcmk__common_args_t *args = NULL;
+static pcmk__output_t *out = NULL;
 static GOptionContext *context = NULL;
 
-#if CURSES_ENABLED
-static gboolean curses_console_initialized = FALSE;
-#endif
-
 /* FIXME allow, detect, and correctly interpret glob pattern or regex? */
 const char *print_neg_location_prefix = "";
 
 long last_refresh = 0;
 crm_trigger_t *refresh_trigger = NULL;
 
+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 }
+};
+
 /* 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
 
 struct {
     int reconnect_msec;
     int fence_history_level;
     int verbose;
     gboolean daemonize;
     gboolean show_bans;
     char *pid_file;
     char *external_agent;
     char *external_recipient;
     unsigned int mon_ops;
 } options = {
     .reconnect_msec = 5000,
     .fence_history_level = 1,
     .mon_ops = mon_op_default
 };
 
 static void clean_up_connections(void);
 static crm_exit_t clean_up(crm_exit_t exit_code);
 static void crm_diff_update(const char *event, xmlNode * msg);
 static gboolean mon_refresh_display(gpointer user_data);
 static int cib_connect(gboolean full);
 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 kick_refresh(gboolean data_updated);
 
 static gboolean
 as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    if (args->output_ty != NULL) {
+        free(args->output_ty);
+    }
+
+    args->output_ty = strdup("html");
     output_format = mon_output_cgi;
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (optarg == NULL) {
         g_set_error(error, G_OPTION_ERROR, 1, "--as-html requires filename");
         return FALSE;
     }
 
+    if (args->output_ty != NULL) {
+        free(args->output_ty);
+    }
+
+    args->output_ty = strdup("html");
     output_format = mon_output_html;
     output_filename = strdup(optarg);
     umask(S_IWGRP | S_IWOTH);
     return TRUE;
 }
 
 static gboolean
 as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    if (args->output_ty != NULL) {
+        free(args->output_ty);
+    }
+
+    args->output_ty = strdup("text");
     output_format = mon_output_monitor;
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
-    output_format = mon_output_xml;
+    if (args->output_ty != NULL) {
+        free(args->output_ty);
+    }
+
+    args->output_ty = strdup("xml");
+    output_format = mon_output_legacy_xml;
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     int rc = crm_atoi(optarg, "2");
 
     if (rc == -1 || rc > 3) {
         g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
         return FALSE;
     } else {
         options.fence_history_level = rc;
     }
 
     return TRUE;
 }
 
 static gboolean
 group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_group_by_node;
     return TRUE;
 }
 
 static gboolean
 hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show &= ~mon_show_headers;
     return TRUE;
 }
 
 static gboolean
 inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_inactive_resources;
     return TRUE;
 }
 
 static gboolean
 no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
-    if (output_format == mon_output_console) {
-        output_format = mon_output_plain;
-    }
-
+    output_format = mon_output_plain;
     return TRUE;
 }
 
 static gboolean
 one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    if (args->output_ty != NULL) {
+        free(args->output_ty);
+    }
+
+    args->output_ty = strdup("text");
+    output_format = mon_output_plain;
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_print_brief;
     return TRUE;
 }
 
 static gboolean
 print_clone_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_print_clone_detail;
     return TRUE;
 }
 
 static gboolean
 print_pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_print_pending;
     return TRUE;
 }
 
 static gboolean
 reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     int rc = crm_get_msec(optarg);
 
     if (rc == -1) {
         g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
         return FALSE;
     } else {
         options.reconnect_msec = crm_get_msec(optarg);
     }
 
     return TRUE;
 }
 
 static gboolean
 show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show |= mon_show_attributes;
     return TRUE;
 }
 
 static gboolean
 show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show |= mon_show_bans;
 
     if (optarg != NULL) {
         print_neg_location_prefix = optarg;
     }
 
     return TRUE;
 }
 
 static gboolean
 show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show |= mon_show_failcounts;
     return TRUE;
 }
 
 static gboolean
 show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show |= mon_show_operations;
     return TRUE;
 }
 
 static gboolean
 show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     show |= mon_show_tickets;
     return TRUE;
 }
 
 static gboolean
 use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     setenv("CIB_file", optarg, 1);
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 watch_fencing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.mon_ops |= mon_op_watch_fencing;
     return TRUE;
 }
 
 #define INDENT "                                    "
 
 /* *INDENT-OFF* */
 static GOptionEntry addl_entries[] = {
     { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb,
       "Update frequency in seconds",
       "SECONDS" },
 
     { "one-shot", '1', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, one_shot_cb,
       "Display the cluster status once on the console and exit",
       NULL },
 
     { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
       "Disable the use of ncurses",
       NULL },
 
     { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
       "Run in the background as a daemon",
       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" },
 
     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
       NULL,
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry display_entries[] = {
     { "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, show_operations_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 },
 
     { "watch-fencing", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, watch_fencing_cb,
       "Listen for fencing events. For use with --external-agent",
       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', 0, 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_clone_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_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_pending_cb,
       "Display pending state if 'record-pending' is enabled",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry mode_entries[] = {
     { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
       "Write cluster status to the named HTML file",
       "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.",
       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)",
       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 }
 };
 /* *INDENT-ON* */
 
 static gboolean
 mon_timer_popped(gpointer data)
 {
     int rc = pcmk_ok;
 
 #if CURSES_ENABLED
     if (output_format == mon_output_console) {
         clear();
         refresh();
     }
 #endif
 
     if (timer_id > 0) {
         g_source_remove(timer_id);
         timer_id = 0;
     }
 
     print_as(output_format, "Reconnecting...\n");
     rc = cib_connect(TRUE);
 
     if (rc != pcmk_ok) {
         timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL);
     }
     return FALSE;
 }
 
 static void
 mon_cib_connection_destroy(gpointer user_data)
 {
     print_as(output_format, "Connection to the cluster-daemons terminated\n");
     if (refresh_timer != NULL) {
         /* we'll trigger a refresh after reconnect */
         mainloop_timer_stop(refresh_timer);
     }
     if (timer_id) {
         /* we'll trigger a new reconnect-timeout at the end */
         g_source_remove(timer_id);
         timer_id = 0;
     }
     if (st) {
         /* the client API won't properly reconnect notifications
          * if they are still in the table - so remove them
          */
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
         if (st->state != stonith_disconnected) {
             st->cmds->disconnect(st);
         }
     }
     if (cib) {
         cib->cmds->signoff(cib);
         timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL);
     }
     return;
 }
 
 /*
  * Mainloop signal handler.
  */
 static void
 mon_shutdown(int nsig)
 {
     clean_up(CRM_EX_OK);
 }
 
 #if CURSES_ENABLED
 static sighandler_t ncurses_winch_handler;
 
 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);
         mainloop_set_trigger(refresh_trigger);
     }
     not_done--;
 }
 #endif
 
 static int
 cib_connect(gboolean full)
 {
     int rc = pcmk_ok;
     static gboolean need_pass = TRUE;
 
     CRM_CHECK(cib != NULL, return -EINVAL);
 
     if (getenv("CIB_passwd") != NULL) {
         need_pass = FALSE;
     }
 
     if (is_set(options.mon_ops, mon_op_fence_connect) && st == NULL) {
         st = stonith_api_new();
     }
 
     if (is_set(options.mon_ops, mon_op_fence_connect) && st != NULL && st->state == stonith_disconnected) {
         rc = st->cmds->connect(st, crm_system_name, NULL);
         if (rc == pcmk_ok) {
             crm_trace("Setting up stonith callbacks");
             if (is_set(options.mon_ops, mon_op_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);
             }
         }
     }
 
     if (cib->state != cib_connected_query && cib->state != cib_connected_command) {
         crm_trace("Connecting to the CIB");
         if ((output_format == mon_output_console) && need_pass && (cib->variant == cib_remote)) {
             need_pass = FALSE;
             print_as(output_format, "Password:");
         }
 
         rc = cib->cmds->signon(cib, crm_system_name, cib_query);
 
         if (rc != pcmk_ok) {
             return rc;
         }
 
         rc = cib->cmds->query(cib, NULL, ¤t_cib, cib_scope_local | cib_sync_call);
         if (rc == pcmk_ok) {
             mon_refresh_display(&output_format);
         }
 
         if (rc == pcmk_ok && full) {
             if (rc == pcmk_ok) {
                 rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy);
                 if (rc == -EPROTONOSUPPORT) {
                     print_as
                         (output_format, "Notification setup not supported, won't be able to reconnect after failure");
                     if (output_format == mon_output_console) {
                         sleep(2);
                     }
                     rc = pcmk_ok;
                 }
 
             }
 
             if (rc == pcmk_ok) {
                 cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
                 rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
             }
 
             if (rc != pcmk_ok) {
                 print_as(output_format, "Notification setup failed, could not monitor CIB actions");
                 if (output_format == mon_output_console) {
                     sleep(2);
                 }
                 clean_up_connections();
             }
         }
     }
     return rc;
 }
 
 #if CURSES_ENABLED
 static const char *
 get_option_desc(char c)
 {
     GOptionEntry *entry;
 
     for (entry = display_entries; entry != NULL; entry++) {
         if (entry->short_name != c) {
             continue;
         }
 
         return strdup(entry->description);
     }
 
     return NULL;
 }
 
 #define print_option_help(output_format, option, condition) \
     print_as(output_format, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
 
 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':
                 if (!options.fence_history_level) {
                     options.mon_ops |= mon_op_fence_history;
                     options.mon_ops |= mon_op_fence_connect;
                     if (st == NULL) {
                         mon_cib_connection_destroy(NULL);
                     }
                 }
                 show ^= mon_show_fence_history;
                 break;
             case 'c':
                 show ^= mon_show_tickets;
                 break;
             case 'f':
                 show ^= mon_show_failcounts;
                 break;
             case 'n':
                 options.mon_ops ^= mon_op_group_by_node;
                 break;
             case 'o':
                 show ^= mon_show_operations;
                 if ((show & mon_show_operations) == 0) {
                     options.mon_ops &= ~mon_op_print_timing;
                 }
                 break;
             case 'r':
                 options.mon_ops ^= mon_op_inactive_resources;
                 break;
             case 'R':
                 options.mon_ops ^= mon_op_print_clone_detail;
                 break;
             case 't':
                 options.mon_ops ^= mon_op_print_timing;
                 if (is_set(options.mon_ops, mon_op_print_timing)) {
                     show |= mon_show_operations;
                 }
                 break;
             case 'A':
                 show ^= mon_show_attributes;
                 break;
             case 'L':
                 show ^= mon_show_bans;
                 break;
             case 'D':
                 /* If any header is shown, clear them all, otherwise set them all */
                 if (show & mon_show_headers) {
                     show &= ~mon_show_headers;
                 } else {
                     show |= mon_show_headers;
                 }
                 break;
             case 'b':
                 options.mon_ops ^= mon_op_print_brief;
                 break;
             case 'j':
                 options.mon_ops ^= mon_op_print_pending;
                 break;
             case '?':
                 config_mode = TRUE;
                 break;
             default:
                 goto refresh;
         }
 
         if (!config_mode)
             goto refresh;
 
         blank_screen();
 
         print_as(output_format, "Display option change mode\n");
         print_as(output_format, "\n");
         print_option_help(output_format, 'c', show & mon_show_tickets);
         print_option_help(output_format, 'f', show & mon_show_failcounts);
         print_option_help(output_format, 'n', is_set(options.mon_ops, mon_op_group_by_node));
         print_option_help(output_format, 'o', show & mon_show_operations);
         print_option_help(output_format, 'r', is_set(options.mon_ops, mon_op_inactive_resources));
         print_option_help(output_format, 't', is_set(options.mon_ops, mon_op_print_timing));
         print_option_help(output_format, 'A', show & mon_show_attributes);
         print_option_help(output_format, 'L', show & mon_show_bans);
         print_option_help(output_format, 'D', (show & mon_show_headers) == 0);
         print_option_help(output_format, 'R', is_set(options.mon_ops, mon_op_print_clone_detail));
         print_option_help(output_format, 'b', is_set(options.mon_ops, mon_op_print_brief));
         print_option_help(output_format, 'j', is_set(options.mon_ops, mon_op_print_pending));
         print_option_help(output_format, 'm', (show & mon_show_fence_history));
         print_as(output_format, "\n");
         print_as(output_format, "Toggle fields via field letter, type any other key to return");
     }
 
 refresh:
     mon_refresh_display(NULL);
     return TRUE;
 }
 #endif
 
 // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
 static void
 avoid_zombies()
 {
     struct sigaction sa;
 
     memset(&sa, 0, sizeof(struct sigaction));
     if (sigemptyset(&sa.sa_mask) < 0) {
         crm_warn("Cannot avoid zombies: %s", pcmk_strerror(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_strerror(errno));
     }
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args) {
     GOptionContext *context = NULL;
     GOptionGroup *mode_group, *display_group, *addl_group;
     GOptionGroup *main_group;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     const char *examples = "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 --as-html /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 --as-xml\n";
 
-    context = pcmk__build_arg_context(args, NULL);
+    context = pcmk__build_arg_context(args, "console (default), html, text, xml");
     g_option_context_set_description(context, examples);
 
     /* Add the -Q option, which cannot be part of the globally supported options
      * because some tools use that flag for something else.
      */
     main_group = g_option_context_get_main_group(context);
     g_option_group_add_entries(main_group, extra_prog_entries);
 
     mode_group = g_option_group_new("mode", "Mode Options (mutually exclusive):", "Show mode options", NULL, NULL);
     g_option_group_add_entries(mode_group, mode_entries);
     g_option_context_add_group(context, mode_group);
 
     display_group = g_option_group_new("display", "Display Options:", "Show display options", NULL, NULL);
     g_option_group_add_entries(display_group, display_entries);
     g_option_context_add_group(context, display_group);
 
     addl_group = g_option_group_new("additional", "Additional Options:", "Show additional options", NULL, NULL);
     g_option_group_add_entries(addl_group, addl_entries);
     g_option_context_add_group(context, addl_group);
 
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_ok;
     char **processed_args = NULL;
 
-    pcmk__common_args_t *args = calloc(1, sizeof(pcmk__common_args_t));
-
     GError *error = NULL;
 
+    args = calloc(1, sizeof(pcmk__common_args_t));
     if (args == NULL) {
         crm_exit(crm_errno2exit(-ENOMEM));
     }
 
     args->summary = strdup("Provides a summary of cluster's current state.\n\n"
                            "Outputs varying levels of detail in a number of different formats.");
     context = build_arg_context(args);
+    pcmk__register_formats(context, formats);
 
     options.pid_file = strdup("/tmp/ClusterMon.pid");
     crm_log_cli_init("crm_mon");
 
     // Avoid needing to wait for subprocesses forked for -E/--external-agent
     avoid_zombies();
 
     if (crm_ends_with_ext(argv[0], ".cgi") == TRUE) {
         output_format = mon_output_cgi;
         options.mon_ops |= mon_op_one_shot;
     }
 
     processed_args = pcmk__cmdline_preproc(argc, argv, "ehimpxEL");
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         return clean_up(CRM_EX_USAGE);
     }
 
     for (int i = 0; i < options.verbose; i++) {
         crm_bump_log_level(argc, argv);
     }
 
+    /* Which output format to use could come from two places:  The --as-xml
+     * style arguments we gave in mode_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.
+     */
+    if (output_format == mon_output_unset) {
+        gboolean retval = TRUE;
+
+        g_clear_error(&error);
+
+        /* NOTE:  There is no way to specify CGI mode or simple mode with --output-as.
+         * Those will need to get handled eventually, at which point something else
+         * will need to be added to this block.
+         */
+        if (safe_str_eq(args->output_ty, "html")) {
+            retval = as_html_cb("h", args->output_dest, NULL, &error);
+        } else if (safe_str_eq(args->output_ty, "text")) {
+            retval = no_curses_cb("N", NULL, NULL, &error);
+        } else if (safe_str_eq(args->output_ty, "xml")) {
+            if (args->output_ty != NULL) {
+                free(args->output_ty);
+            }
+
+            args->output_ty = strdup("xml");
+            output_format = mon_output_xml;
+            options.mon_ops |= mon_op_one_shot;
+        } else {
+            /* Neither old nor new arguments were given, so set the default. */
+            if (args->output_ty != NULL) {
+                free(args->output_ty);
+            }
+
+            args->output_ty = strdup("console");
+            output_format = mon_output_console;
+        }
+
+        if (!retval) {
+            fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+            return clean_up(CRM_EX_USAGE);
+        }
+    }
+
+    /* 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.
+     */
+    g_clear_error(&error);
+
+    if (output_format == mon_output_plain) {
+        if (!pcmk__force_args(context, &error, "%s --output-fancy", g_get_prgname())) {
+            fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+            return clean_up(CRM_EX_USAGE);
+        }
+    } else if (output_format == mon_output_html) {
+        if (!pcmk__force_args(context, &error, "%s --output-meta-refresh %d",
+                              g_get_prgname(), options.reconnect_msec/1000)) {
+            fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+            return clean_up(CRM_EX_USAGE);
+        }
+    } else if (output_format == mon_output_cgi) {
+        if (!pcmk__force_args(context, &error, "%s --output-cgi", g_get_prgname())) {
+            fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+            return clean_up(CRM_EX_USAGE);
+        }
+    } else if (output_format == mon_output_legacy_xml) {
+        output_format = mon_output_xml;
+        if (!pcmk__force_args(context, &error, "%s --output-legacy-xml", g_get_prgname())) {
+            fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+            return clean_up(CRM_EX_USAGE);
+        }
+    }
+
+    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
+    if (rc != 0) {
+        fprintf(stderr, "Error creating output format %s: %s\n", args->output_ty, pcmk_strerror(rc));
+        return clean_up(CRM_EX_ERROR);
+    }
+
+    stonith__register_messages(out);
+
     if (args->version) {
-        crm_help('$', CRM_EX_OK);
+        /* FIXME: For the moment, this won't do anything on XML or HTML formats
+         * because finish is not getting called.  That's commented out in
+         * clean_up.
+         */
+        out->version(out, false);
+        return clean_up(CRM_EX_OK);
     }
 
     if (args->quiet) {
         show &= ~mon_show_times;
     }
 
     if (is_set(options.mon_ops, mon_op_watch_fencing)) {
         options.mon_ops |= mon_op_fence_connect;
         /* don't moan as fence_history_level == 1 is default */
         options.fence_history_level = 0;
     }
 
     /* create the cib-object early to be able to do further
      * decisions based on the cib-source
      */
     cib = cib_new();
 
     if (cib == NULL) {
         rc = -EINVAL;
     } else {
         switch (cib->variant) {
 
             case cib_native:
                 /* cib & fencing - everything 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. */
                 options.fence_history_level = 0;
                 options.mon_ops |= mon_op_one_shot;
                 break;
 
             case cib_remote:
                 /* updates coming in but no fencing */
                 options.fence_history_level = 0;
                 break;
 
             case cib_undefined:
             case cib_database:
             default:
                 /* something is odd */
                 rc = -EINVAL;
                 crm_err("Invalid cib-source");
                 break;
         }
     }
 
     switch (options.fence_history_level) {
         case 3:
             options.mon_ops |= mon_op_fence_full_history;
             /* fall through to next lower level */
         case 2:
             show |= mon_show_fence_history;
             /* fall through to next lower level */
         case 1:
             options.mon_ops |= mon_op_fence_history;
             options.mon_ops |= mon_op_fence_connect;
             break;
         default:
             break;
     }
 
     /* Extra sanity checks when in CGI mode */
     if (output_format == mon_output_cgi) {
         if (output_filename != NULL) {
             fprintf(stderr, "CGI mode cannot be used with -h\n");
             return clean_up(CRM_EX_USAGE);
         } else if (cib && cib->variant == cib_file) {
             fprintf(stderr, "CGI mode used with CIB file\n");
             return clean_up(CRM_EX_USAGE);
         } else if (options.external_agent != NULL) {
             fprintf(stderr, "CGI mode cannot be used with --external-agent\n");
             return clean_up(CRM_EX_USAGE);
         } else if (options.daemonize == TRUE) {
             fprintf(stderr, "CGI mode cannot be used with -d\n");
             return clean_up(CRM_EX_USAGE);
         }
     }
 
     /* XML output always prints everything */
     if (output_format == mon_output_xml) {
         show = mon_show_all;
         options.mon_ops |= mon_op_print_timing;
     }
 
     if (is_set(options.mon_ops, mon_op_one_shot)) {
         if (output_format == mon_output_console) {
             output_format = mon_output_plain;
         }
 
     } else if (options.daemonize) {
         if ((output_format == mon_output_console) || (output_format == mon_output_plain)) {
             output_format = mon_output_none;
         }
         crm_enable_stderr(FALSE);
 
         if ((output_format != mon_output_html)
             && !options.external_agent) {
             printf ("Looks like you forgot to specify one or more of: "
                     "--as-html, --external-agent\n");
             return clean_up(CRM_EX_USAGE);
         }
 
         if (cib) {
             /* to be on the safe side don't have cib-object around
              * when we are forking
              */
             cib_delete(cib);
             cib = NULL;
             crm_make_daemon(crm_system_name, TRUE, options.pid_file);
             cib = cib_new();
             if (cib == NULL) {
                 rc = -EINVAL;
             }
             /* otherwise assume we've got the same cib-object we've just destroyed
              * in our parent
              */
         }
 
 
     } else if (output_format == mon_output_console) {
 #if CURSES_ENABLED
         initscr();
         cbreak();
         noecho();
         crm_enable_stderr(FALSE);
-        curses_console_initialized = TRUE;
 #else
         options.mon_ops |= mon_op_one_shot;
         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
     }
 
     crm_info("Starting %s", crm_system_name);
 
     if (cib) {
 
         do {
             if (is_not_set(options.mon_ops, mon_op_one_shot)) {
                 print_as(output_format ,"Waiting until cluster is available on this node ...\n");
             }
             rc = cib_connect(is_not_set(options.mon_ops, mon_op_one_shot));
 
             if (is_set(options.mon_ops, mon_op_one_shot)) {
                 break;
 
             } else if (rc != pcmk_ok) {
                 sleep(options.reconnect_msec / 1000);
 #if CURSES_ENABLED
                 if (output_format == mon_output_console) {
                     clear();
                     refresh();
                 }
 #endif
             } else {
                 if (output_format == mon_output_html) {
                     print_as(output_format, "Writing html to %s ...\n", output_filename);
                 }
             }
 
         } while (rc == -ENOTCONN);
     }
 
     if (rc != pcmk_ok) {
         if (output_format == mon_output_monitor) {
             printf("CLUSTER CRIT: Connection to cluster failed: %s\n",
                     pcmk_strerror(rc));
             return clean_up(MON_STATUS_CRIT);
         } else {
             if (rc == -ENOTCONN) {
                 print_as(output_format ,"\nError: cluster is not available on this node\n");
             } else {
                 print_as(output_format ,"\nConnection to cluster failed: %s\n",
                          pcmk_strerror(rc));
             }
         }
         if (output_format == mon_output_console) {
             sleep(2);
         }
         return clean_up(crm_errno2exit(rc));
     }
 
     if (is_set(options.mon_ops, mon_op_one_shot)) {
         return clean_up(CRM_EX_OK);
     }
 
     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;
         g_io_add_watch(g_io_channel_unix_new(STDIN_FILENO), G_IO_IN, detect_user_input, NULL);
     }
 #endif
     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
 
     g_main_loop_run(mainloop);
     g_main_loop_unref(mainloop);
 
     crm_info("Exiting %s", crm_system_name);
 
     return clean_up(CRM_EX_OK);
 }
 
 #define mon_warn(output_format, mon_ops, fmt...) do {			\
 	if (is_not_set(mon_ops, mon_op_has_warnings)) {			\
 	    print_as(output_format, "CLUSTER WARN:");		\
 	} else {				\
 	    print_as(output_format, ",");			\
 	}					\
 	print_as(output_format, fmt);				\
 	mon_ops |= mon_op_has_warnings;			\
     } while(0)
 
 /*!
  * \internal
  * \brief Print one-line status suitable for use with monitoring software
  *
  * \param[in] data_set  Working set of CIB state
  * \param[in] history   List of stonith actions
  *
  * \note This function's output (and the return code when the program exits)
  *       should conform to https://www.monitoring-plugins.org/doc/guidelines.html
  */
 static void
 print_simple_status(pe_working_set_t * data_set, stonith_history_t *history,
                     unsigned int mon_ops, mon_output_format_t output_format)
 {
     GListPtr gIter = NULL;
     int nodes_online = 0;
     int nodes_standby = 0;
     int nodes_maintenance = 0;
 
     if (data_set->dc_node == NULL) {
         mon_warn(output_format, mon_ops, " No DC");
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->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 {
             mon_warn(output_format, mon_ops, " offline node: %s", node->details->uname);
         }
     }
 
     if (is_not_set(mon_ops, mon_op_has_warnings)) {
         int nresources = count_resources(data_set, NULL);
 
         print_as(output_format, "CLUSTER OK: %d node%s online", nodes_online, s_if_plural(nodes_online));
         if (nodes_standby > 0) {
             print_as(output_format, ", %d standby node%s", nodes_standby, s_if_plural(nodes_standby));
         }
         if (nodes_maintenance > 0) {
             print_as(output_format, ", %d maintenance node%s", nodes_maintenance, s_if_plural(nodes_maintenance));
         }
         print_as(output_format, ", %d resource%s configured", nresources, s_if_plural(nresources));
     }
 
     print_as(output_format, "\n");
 }
 
 /*!
  * \internal
  * \brief Reduce the stonith-history
  *        for successful actions we keep the last of every action-type & target
  *        for failed actions we record as well who had failed
  *        for actions in progress we keep full track
  *
  * \param[in] history    List of stonith actions
  *
  */
 static stonith_history_t *
 reduce_stonith_history(stonith_history_t *history)
 {
     stonith_history_t *new = history, *hp, *np;
 
     if (new) {
         hp = new->next;
         new->next = NULL;
 
         while (hp) {
             stonith_history_t *hp_next = hp->next;
 
             hp->next = NULL;
 
             for (np = new; ; np = np->next) {
                 if ((hp->state == st_done) || (hp->state == st_failed)) {
                     /* action not in progress */
                     if (safe_str_eq(hp->target, np->target) &&
                         safe_str_eq(hp->action, np->action) &&
                         (hp->state == np->state) &&
                         ((hp->state == st_done) ||
                          safe_str_eq(hp->delegate, np->delegate))) {
                             /* purge older hp */
                             stonith_history_free(hp);
                             break;
                     }
                 }
 
                 if (!np->next) {
                     np->next = hp;
                     break;
                 }
             }
             hp = hp_next;
         }
     }
 
     return new;
 }
 
 /*!
  * \internal
  * \brief Sort the stonith-history
  *        sort by competed most current on the top
  *        pending actions lacking a completed-stamp are gathered at the top
  *
  * \param[in] history    List of stonith actions
  *
  */
 static stonith_history_t *
 sort_stonith_history(stonith_history_t *history)
 {
     stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
 
     for (hp = history; hp; ) {
         tmp = hp->next;
         if ((hp->state == st_done) || (hp->state == st_failed)) {
             /* sort into new */
             if ((!new) || (hp->completed > new->completed)) {
                 hp->next = new;
                 new = hp;
             } else {
                 np = new;
                 do {
                     if ((!np->next) || (hp->completed > np->next->completed)) {
                         hp->next = np->next;
                         np->next = hp;
                         break;
                     }
                     np = np->next;
                 } while (1);
             }
         } else {
             /* put into pending */
             hp->next = pending;
             pending = hp;
         }
         hp = tmp;
     }
 
     /* pending actions don't have a completed-stamp so make them go front */
     if (pending) {
         stonith_history_t *last_pending = pending;
 
         while (last_pending->next) {
             last_pending = last_pending->next;
         }
 
         last_pending->next = new;
         new = pending;
     }
     return new;
 }
 
 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 = crm_itoa(rc);
     char *status_s = crm_itoa(status);
     char *target_rc_s = crm_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 void
 handle_rsc_op(xmlNode * xml, const char *node_id)
 {
     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) {
         xmlNode *cIter;
 
         for(cIter = xml->children; cIter; cIter = cIter->next) {
             handle_rsc_op(cIter, node_id);
         }
 
         return;
     }
 
     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;
     }
 
     if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
                                  &target_rc)) {
         crm_err("Invalid event %s detected for %s", magic, id);
         return;
     }
 
     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 && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) {
         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_strerror(pcmk_ok);
     if (status == PCMK_LRM_OP_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_LRM_OP_DONE) {
         desc = services_ocf_exitcode_str(rc);
         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
 
     } else {
         desc = services_lrm_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);
 }
 
 static gboolean
 mon_trigger_refresh(gpointer user_data)
 {
     mainloop_set_trigger(refresh_trigger);
     return FALSE;
 }
 
 #define NODE_PATT "/lrm[@id="
 static char *
 get_node_from_xpath(const char *xpath)
 {
     char *nodeid = NULL;
     char *tmp = strstr(xpath, NODE_PATT);
 
     if(tmp) {
         tmp += strlen(NODE_PATT);
         tmp += 1;
 
         nodeid = strdup(tmp);
         tmp = strstr(nodeid, "\'");
         CRM_ASSERT(tmp);
         tmp[0] = 0;
     }
     return nodeid;
 }
 
 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 = __xml_first_child(diff); change != NULL; change = __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) {
             xmlNode *state = NULL;
             xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
 
             for (state = __xml_first_child(status); state != NULL; state = __xml_next(state)) {
                 node = crm_element_value(state, XML_ATTR_UNAME);
                 if (node == NULL) {
                     node = ID(state);
                 }
                 handle_rsc_op(state, node);
             }
 
         } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
             xmlNode *state = NULL;
 
             for (state = __xml_first_child(match); state != NULL; state = __xml_next(state)) {
                 node = crm_element_value(state, XML_ATTR_UNAME);
                 if (node == NULL) {
                     node = ID(state);
                 }
                 handle_rsc_op(state, node);
             }
 
         } 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, node);
 
         } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
             node = ID(match);
             handle_rsc_op(match, node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
             char *local_node = get_node_from_xpath(xpath);
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
             char *local_node = get_node_from_xpath(xpath);
 
             handle_rsc_op(match, local_node);
             free(local_node);
 
         } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
             char *local_node = get_node_from_xpath(xpath);
 
             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);
 
     print_dot(output_format);
 
     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, ¤t_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) {
             print_as(output_format, "--- Stale data ---");
         }
         stale = TRUE;
         return;
     }
 
     stale = FALSE;
     kick_refresh(cib_updated);
 }
 
 static gboolean
 mon_refresh_display(gpointer user_data)
 {
     xmlNode *cib_copy = copy_xml(current_cib);
     stonith_history_t *stonith_history = NULL;
 
     /* stdout for everything except the HTML case, which does a bunch of file
      * renaming.  We'll handle changing stream in print_html_status.
      */
-    mon_state_t state = { .stream = stdout, .output_format = output_format };
+    mon_state_t state = { .stream = stdout, .output_format = output_format, .out = out };
 
     last_refresh = time(NULL);
 
     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
         if (cib) {
             cib->cmds->signoff(cib);
         }
         print_as(output_format, "Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation));
         if (output_format == mon_output_console) {
             sleep(2);
         }
         clean_up(CRM_EX_CONFIG);
         return FALSE;
     }
 
     /* get the stonith-history if there is evidence we need it
      */
     while (is_set(options.mon_ops, mon_op_fence_history)) {
         if (st != NULL) {
             if (st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120)) {
                 fprintf(stderr, "Critical: Unable to get stonith-history\n");
                 mon_cib_connection_destroy(NULL);
             } else {
                 stonith_history = sort_stonith_history(stonith_history);
                 if (is_not_set(options.mon_ops, mon_op_fence_full_history) && output_format != mon_output_xml) {
                     stonith_history = reduce_stonith_history(stonith_history);
                 }
                 break; /* all other cases are errors */
             }
         } else {
             fprintf(stderr, "Critical: No stonith-API\n");
         }
         free_xml(cib_copy);
         print_as(output_format, "Reading stonith-history failed");
         if (output_format == mon_output_console) {
             sleep(2);
         }
         return FALSE;
     }
 
     if (mon_data_set == NULL) {
         mon_data_set = pe_new_working_set();
         CRM_ASSERT(mon_data_set != NULL);
     }
 
     mon_data_set->input = cib_copy;
     cluster_status(mon_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 (show & (mon_show_bans | mon_show_tickets)) {
         xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
                                                    mon_data_set->input);
         unpack_constraints(cib_constraints, mon_data_set);
     }
 
     switch (output_format) {
         case mon_output_html:
         case mon_output_cgi:
             if (print_html_status(&state, mon_data_set, output_filename,
                                   stonith_history, options.mon_ops, show,
                                   print_neg_location_prefix, options.reconnect_msec) != 0) {
                 fprintf(stderr, "Critical: Unable to output html file\n");
                 clean_up(CRM_EX_CANTCREAT);
                 return FALSE;
             }
             break;
 
+        case mon_output_legacy_xml:
         case mon_output_xml:
             print_xml_status(&state, mon_data_set, stonith_history,
                              options.mon_ops, show, print_neg_location_prefix);
             break;
 
         case mon_output_monitor:
             print_simple_status(mon_data_set, stonith_history, options.mon_ops, output_format);
             if (is_set(options.mon_ops, mon_op_has_warnings)) {
                 clean_up(MON_STATUS_WARN);
                 return FALSE;
             }
             break;
 
         case mon_output_plain:
         case mon_output_console:
             print_status(&state, mon_data_set, stonith_history, options.mon_ops,
                          show, print_neg_location_prefix);
             break;
 
+        case mon_output_unset:
         case mon_output_none:
             break;
     }
 
     stonith_history_free(stonith_history);
     stonith_history = NULL;
     pe_reset_working_set(mon_data_set);
     return TRUE;
 }
 
 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 = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
                                     e->operation, e->origin, e->target, pcmk_strerror(e->result),
                                     e->id);
         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
         free(desc);
     }
 }
 
 static void
 kick_refresh(gboolean data_updated)
 {
     static int updates = 0;
     long now = time(NULL);
 
     if (data_updated) {
         updates++;
     }
 
     if(refresh_timer == NULL) {
         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
     }
 
     /* Refresh
      * - immediately if the last update was more than 5s ago
      * - every 10 cib-updates
      * - at most 2s after the last update
      */
     if ((now - last_refresh) > (options.reconnect_msec / 1000)) {
         mainloop_set_trigger(refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else if(updates >= 10) {
         mainloop_set_trigger(refresh_trigger);
         mainloop_timer_stop(refresh_timer);
         updates = 0;
 
     } else {
         mainloop_timer_start(refresh_timer);
     }
 }
 
 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 {
         print_dot(output_format);
         kick_refresh(TRUE);
     }
 }
 
 static void
 clean_up_connections(void)
 {
     if (cib != NULL) {
         cib->cmds->signoff(cib);
         cib_delete(cib);
         cib = NULL;
     }
 
     if (st != NULL) {
         if (st->state != stonith_disconnected) {
             st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
             st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
             st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
             st->cmds->disconnect(st);
         }
         stonith_api_delete(st);
         st = NULL;
     }
 }
 
 /*
  * 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)
 {
 #if CURSES_ENABLED
-    if (curses_console_initialized) {
+    if (output_format == mon_output_console) {
         output_format = mon_output_plain;
         echo();
         nocbreak();
         endwin();
-        curses_console_initialized = FALSE;
     }
 #endif
 
     clean_up_connections();
     free(output_filename);
     free(options.pid_file);
 
     pe_free_working_set(mon_data_set);
     mon_data_set = NULL;
 
     if (exit_code == CRM_EX_USAGE) {
         if (output_format == mon_output_cgi) {
             fprintf(stdout, "Content-Type: text/plain\n"
                             "Status: 500\n\n");
         } else {
             fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
         }
     }
+
+    g_option_context_free(context);
+
+    if (out != NULL) {
+        /* FIXME: When we are ready to enable formatted output, uncomment
+         * the following line:
+         */
+        /* out->finish(out, exit_code, true, NULL); */
+        pcmk__output_free(out);
+    }
+
     crm_exit(exit_code);
 }
diff --git a/tools/crm_mon.h b/tools/crm_mon.h
index b6df3aa17f..90724e21b8 100644
--- a/tools/crm_mon.h
+++ b/tools/crm_mon.h
@@ -1,119 +1,130 @@
 /*
  * Copyright 2019 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 
 
 #include 
 #include 
 #include 
 #include 
 
 /* Never display node attributes whose name starts with one of these prefixes */
 #define FILTER_STR { CRM_FAIL_COUNT_PREFIX, CRM_LAST_FAILURE_PREFIX,       \
                      "shutdown", "terminate", "standby", "probe_complete", \
                      "#", NULL }
 
 /* Convenience macro for prettifying output (e.g. "node" vs "nodes") */
 #define s_if_plural(i) (((i) == 1)? "" : "s")
 
 #if CURSES_ENABLED
 #  define print_dot(output_format) if (output_format == mon_output_console) { \
 	printw(".");				\
 	clrtoeol();				\
 	refresh();				\
     } else {					\
 	fprintf(stdout, ".");			\
     }
 #else
 #  define print_dot(output_format) fprintf(stdout, ".");
 #endif
 
 #if CURSES_ENABLED
 #  define print_as(output_format, fmt, args...) if (output_format == mon_output_console) { \
 	printw(fmt, ##args);				\
 	clrtoeol();					\
 	refresh();					\
     } else {						\
 	fprintf(stdout, fmt, ##args);			\
     }
 #else
 #  define print_as(output_format, fmt, args...) fprintf(stdout, fmt, ##args);
 #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;
 
 #define mon_show_times         (0x0001U)
 #define mon_show_stack         (0x0002U)
 #define mon_show_dc            (0x0004U)
 #define mon_show_count         (0x0008U)
 #define mon_show_nodes         (0x0010U)
 #define mon_show_resources     (0x0020U)
 #define mon_show_attributes    (0x0040U)
 #define mon_show_failcounts    (0x0080U)
 #define mon_show_operations    (0x0100U)
 #define mon_show_tickets       (0x0200U)
 #define mon_show_bans          (0x0400U)
 #define mon_show_fence_history (0x0800U)
 
 #define mon_show_headers       (mon_show_times | mon_show_stack | mon_show_dc \
                                | mon_show_count)
 #define mon_show_default       (mon_show_headers | mon_show_nodes \
                                | mon_show_resources)
 #define mon_show_all           (mon_show_default | mon_show_attributes \
                                | mon_show_failcounts | mon_show_operations \
                                | mon_show_tickets | mon_show_bans \
                                | mon_show_fence_history)
 
 #define mon_op_group_by_node        (0x0001U)
 #define mon_op_inactive_resources   (0x0002U)
 #define mon_op_one_shot             (0x0004U)
 #define mon_op_has_warnings         (0x0008U)
 #define mon_op_print_timing         (0x0010U)
 #define mon_op_watch_fencing        (0x0020U)
 #define mon_op_fence_history        (0x0040U)
 #define mon_op_fence_full_history   (0x0080U)
 #define mon_op_fence_connect        (0x0100U)
 #define mon_op_print_brief          (0x0200U)
 #define mon_op_print_pending        (0x0400U)
 #define mon_op_print_clone_detail   (0x0800U)
 
 #define mon_op_default              (mon_op_print_pending)
 
 typedef struct {
     FILE *stream;
     mon_output_format_t output_format;
+    pcmk__output_t *out;
 } mon_state_t;
 
 void print_status(mon_state_t *state, pe_working_set_t *data_set,
                   stonith_history_t *stonith_history, unsigned int mon_ops,
                   unsigned int show, const char *prefix);
 void print_xml_status(mon_state_t *state, pe_working_set_t *data_set,
                       stonith_history_t *stonith_history, unsigned int mon_ops,
                       unsigned int show, const char *prefix);
 int print_html_status(mon_state_t *state, pe_working_set_t *data_set,
                       const char *filename, stonith_history_t *stonith_history,
                       unsigned int mon_ops, unsigned int show, const char *prefix,
                       unsigned int reconnect_msec);
 
 void append_attr_list(gpointer name, gpointer value, gpointer user_data);
 void blank_screen(void);
 int count_resources(pe_working_set_t *data_set, resource_t *rsc);
 void crm_mon_get_parameters(resource_t *rsc, pe_working_set_t *data_set);
 const char *get_cluster_stack(pe_working_set_t *data_set);
 char *get_node_display_name(node_t *node, unsigned int mon_ops);
 int get_resource_display_options(unsigned int mon_ops,
                                  mon_output_format_t output_format);
+
+pcmk__output_t *crm_mon_mk_curses_output(char **argv);
+void curses_indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+#if CURSES_ENABLED
+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
diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c
new file mode 100644
index 0000000000..521b434856
--- /dev/null
+++ b/tools/crm_mon_curses.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2019 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 
+#include 
+#include 
+#include 
+#include 
+
+#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 = out->priv;
+
+    if (priv == NULL) {
+        return;
+    }
+
+    g_queue_free(priv->parent_q);
+    free(priv);
+}
+
+static bool
+curses_init(pcmk__output_t *out) {
+    private_data_t *priv = 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();
+
+    return true;
+}
+
+static void
+curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
+}
+
+static void
+curses_reset(pcmk__output_t *out) {
+    CRM_ASSERT(out->priv != 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) {
+    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) {
+    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\n");
+    }
+}
+
+G_GNUC_PRINTF(2, 3)
+static void
+curses_err_info(pcmk__output_t *out, const char *format, ...) {
+    va_list ap;
+
+    /* 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();
+}
+
+static void
+curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
+    private_data_t *priv = out->priv;
+
+    CRM_ASSERT(priv != NULL);
+    curses_indented_printf(out, "%s", buf);
+}
+
+static void
+curses_begin_list(pcmk__output_t *out, const char *name, const char *singular_noun,
+                const char *plural_noun) {
+    private_data_t *priv = out->priv;
+    curses_list_data_t *new_list = NULL;
+
+    CRM_ASSERT(priv != NULL);
+
+    curses_indented_printf(out, "%s:\n", name);
+
+    new_list = calloc(1, sizeof(curses_list_data_t));
+    new_list->len = 0;
+    new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun);
+    new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun);
+
+    g_queue_push_tail(priv->parent_q, new_list);
+}
+
+static void
+curses_list_item(pcmk__output_t *out, const char *id, const char *content) {
+    private_data_t *priv = out->priv;
+
+    CRM_ASSERT(priv != NULL);
+
+    if (id != NULL) {
+        curses_indented_printf(out, "* %s: %s\n", id, content);
+    } else {
+        curses_indented_printf(out, "* %s\n", content);
+    }
+
+    ((curses_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++;
+}
+
+static void
+curses_end_list(pcmk__output_t *out) {
+    private_data_t *priv = out->priv;
+    curses_list_data_t *node = NULL;
+
+    CRM_ASSERT(priv != NULL);
+    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);
+        }
+    } else {
+        putc('\n', out->dest);
+    }
+
+    free(node);
+}
+
+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 = g_strjoinv(" ", argv);
+    retval->supports_quiet = true;
+
+    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_err_info;
+    retval->info = curses_err_info;
+    retval->output_xml = curses_output_xml;
+
+    retval->begin_list = curses_begin_list;
+    retval->list_item = curses_list_item;
+    retval->end_list = curses_end_list;
+
+    return retval;
+}
+
+G_GNUC_PRINTF(2, 3)
+void
+curses_indented_printf(pcmk__output_t *out, const char *format, ...) {
+    va_list ap;
+    int level = 0;
+    private_data_t *priv = out->priv;
+
+    CRM_ASSERT(priv != NULL);
+
+    level = g_queue_get_length(priv->parent_q);
+
+    for (int i = 0; i < level; i++) {
+        addch('\t');
+    }
+
+    if (level > 0) {
+        printw("* ");
+    }
+
+    va_start(ap, format);
+    vw_printw(stdscr, format, ap);
+    va_end(ap);
+
+    clrtoeol();
+    refresh();
+}
+
+#else
+
+pcmk__output_t *
+crm_mon_mk_curses_output(char **argv) {
+    /* curses was disabled in the build, so fall back to text. */
+    return pcmk__mk_text_output(argv);
+}
+
+G_GNUC_PRINTF(2, 3)
+void
+curses_indented_printf(pcmk__output_t *out, const char *format, ...) {
+    return;
+}
+
+#endif
diff --git a/tools/crm_mon_print.c b/tools/crm_mon_print.c
index 2f7db4c745..2081878934 100644
--- a/tools/crm_mon_print.c
+++ b/tools/crm_mon_print.c
@@ -1,2595 +1,2593 @@
 /*
  * Copyright 2019 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 
 #include 
 #include 
 #include 
 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 
 #include "crm_mon.h"
 
 static void print_nvpair(mon_state_t *state, const char *name, const char *value,
                          const char *units, time_t epoch_time);
 static void print_node_start(mon_state_t *state, node_t *node, unsigned int mon_ops);
 static void print_node_end(mon_state_t *state);
 static void print_resources_heading(mon_state_t *state, unsigned int mon_ops);
 static void print_resources_closing(mon_state_t *state, gboolean printed_heading,
                                     unsigned int mon_ops);
 static void print_resources(mon_state_t *state, pe_working_set_t *data_set,
                             int print_opts, unsigned int mon_ops);
 static void print_rsc_history_start(mon_state_t *state, pe_working_set_t *data_set,
                                     node_t *node, resource_t *rsc, const char *rsc_id,
                                     gboolean all);
 static void print_rsc_history_end(mon_state_t *state);
 static void print_op_history(mon_state_t *state, pe_working_set_t *data_set,
                              node_t *node, xmlNode *xml_op, const char *task,
                              const char *interval_ms_s, int rc, unsigned int mon_ops);
 static void print_rsc_history(mon_state_t *state, pe_working_set_t *data_set,
                               node_t *node, xmlNode *rsc_entry, gboolean operations,
                               unsigned int mon_ops);
 static void print_node_history(mon_state_t *state, pe_working_set_t *data_set,
                                xmlNode *node_state, gboolean operations,
                                unsigned int mon_ops);
 static gboolean print_attr_msg(mon_state_t *state, node_t * node, GListPtr rsc_list,
                                const char *attrname, const char *attrvalue);
 static void print_node_attribute(gpointer name, gpointer user_data);
 static void print_node_summary(mon_state_t *state, pe_working_set_t * data_set,
                                gboolean operations, unsigned int mon_ops);
 static void print_ticket(gpointer name, gpointer value, gpointer user_data);
 static void print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set);
 static void print_ban(mon_state_t *state, pe_node_t *node, pe__location_t *location,
                       unsigned int mon_ops);
 static void print_neg_locations(mon_state_t *state, pe_working_set_t *data_set,
                                 unsigned int mon_ops, const char *prefix);
 static void print_node_attributes(mon_state_t *state, pe_working_set_t *data_set,
                                   unsigned int mon_ops);
 static void print_cluster_summary_header(mon_state_t *state);
 static void print_cluster_summary_footer(mon_state_t *state);
 static void print_cluster_times(mon_state_t *state, pe_working_set_t *data_set);
 static void print_cluster_stack(mon_state_t *state, const char *stack_s);
 static void print_cluster_dc(mon_state_t *state, pe_working_set_t *data_set,
                              unsigned int mon_ops);
 static void print_cluster_counts(mon_state_t *state, pe_working_set_t *data_set,
                                  const char *stack_s);
 static void print_cluster_options(mon_state_t *state, pe_working_set_t *data_set);
 static void print_cluster_summary(mon_state_t *state, pe_working_set_t *data_set,
                                   unsigned int mon_ops, unsigned int show);
 static void print_failed_action(mon_state_t *state, xmlNode *xml_op);
 static void print_failed_actions(mon_state_t *state, pe_working_set_t *data_set);
 static void print_stonith_action(mon_state_t *state, stonith_history_t *event, unsigned int mon_ops);
 static void print_failed_stonith_actions(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops);
 static void print_stonith_pending(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops);
 static void print_stonith_history(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops);
 
 /*!
  * \internal
  * \brief Print a [name]=[value][units] pair, optionally using time string
  *
  * \param[in] stream      File stream to display output to
  * \param[in] name        Name to display
  * \param[in] value       Value to display (or NULL to convert time instead)
  * \param[in] units       Units to display (or NULL for no units)
  * \param[in] epoch_time  Epoch time to convert if value is NULL
  */
 static void
 print_nvpair(mon_state_t *state, const char *name, const char *value,
              const char *units, time_t epoch_time)
 {
     /* print name= */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, " %s=", name);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
         case mon_output_xml:
             fprintf(state->stream, " %s=", name);
             break;
 
         default:
             break;
     }
 
     /* If we have a value (and optionally units), print it */
     if (value) {
         switch (state->output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, "%s%s", value, (units? units : ""));
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, "%s%s", value, (units? units : ""));
                 break;
 
             case mon_output_xml:
                 fprintf(state->stream, "\"%s%s\"", value, (units? units : ""));
                 break;
 
             default:
                 break;
         }
 
     /* Otherwise print user-friendly time string */
     } else {
         static char empty_str[] = "";
         char *c, *date_str = asctime(localtime(&epoch_time));
 
         for (c = (date_str != NULL) ? date_str : empty_str; *c != '\0'; ++c) {
             if (*c == '\n') {
                 *c = '\0';
                 break;
             }
         }
         switch (state->output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, "'%s'", date_str);
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
             case mon_output_xml:
                 fprintf(state->stream, "\"%s\"", date_str);
                 break;
 
             default:
                 break;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Print whatever is needed to start a node section
  *
  * \param[in] stream     File stream to display output to
  * \param[in] node       Node to print
  */
 static void
 print_node_start(mon_state_t *state, node_t *node, unsigned int mon_ops)
 {
     char *node_name;
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             node_name = get_node_display_name(node, mon_ops);
             print_as(state->output_format, "* Node %s:\n", node_name);
             free(node_name);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             node_name = get_node_display_name(node, mon_ops);
             fprintf(state->stream, "  Node: %s
\n  \n", node_name);
             free(node_name);
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "        \n", node->details->uname);
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print whatever is needed to end a node section
  *
  * \param[in] stream     File stream to display output to
  */
 static void
 print_node_end(mon_state_t *state)
 {
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "  
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "        \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print resources section heading appropriate to options
  *
  * \param[in] stream      File stream to display output to
  */
 static void
 print_resources_heading(mon_state_t *state, unsigned int mon_ops)
 {
     const char *heading;
 
     if (is_set(mon_ops, mon_op_group_by_node)) {
 
         /* Active resources have already been printed by node */
         heading = is_set(mon_ops, mon_op_inactive_resources) ? "Inactive resources" : NULL;
 
     } else if (is_set(mon_ops, mon_op_inactive_resources)) {
         heading = "Full list of resources";
 
     } else {
         heading = "Active resources";
     }
 
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\n%s:\n\n", heading);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "
\n %s
\n", heading);
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
 }
 
 /*!
  * \internal
  * \brief Print whatever resource section closing is appropriate
  *
  * \param[in] stream     File stream to display output to
  */
 static void
 print_resources_closing(mon_state_t *state, gboolean printed_heading,
                         unsigned int mon_ops)
 {
     const char *heading;
 
     /* What type of resources we did or did not display */
     if (is_set(mon_ops, mon_op_group_by_node)) {
         heading = "inactive ";
     } else if (is_set(mon_ops, mon_op_inactive_resources)) {
         heading = "";
     } else {
         heading = "active ";
     }
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             if (!printed_heading) {
                 print_as(state->output_format, "\nNo %sresources\n\n", heading);
             }
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             if (!printed_heading) {
                 fprintf(state->stream, " 
\n No %sresources
\n", heading);
             }
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    %s\n",
                     (printed_heading? "" : ""));
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print whatever resource section(s) are appropriate
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Cluster state to display
  * \param[in] print_opts  Bitmask of pe_print_options
  */
 static void
 print_resources(mon_state_t *state, pe_working_set_t *data_set,
                 int print_opts, unsigned int mon_ops)
 {
     GListPtr rsc_iter;
     const char *prefix = NULL;
     gboolean printed_heading = FALSE;
     gboolean brief_output = is_set(mon_ops, mon_op_print_brief);
 
     /* If we already showed active resources by node, and
      * we're not showing inactive resources, we have nothing to do
      */
     if (is_set(mon_ops, mon_op_group_by_node) && is_not_set(mon_ops, mon_op_inactive_resources)) {
         return;
     }
 
     /* XML uses an indent, and ignores brief option for resources */
     if (state->output_format == mon_output_xml) {
         prefix = "        ";
         brief_output = FALSE;
     }
 
     /* If we haven't already printed resources grouped by node,
      * and brief output was requested, print resource summary */
     if (brief_output && is_not_set(mon_ops, mon_op_group_by_node)) {
         print_resources_heading(state, mon_ops);
         printed_heading = TRUE;
         print_rscs_brief(data_set->resources, NULL, print_opts, state->stream,
                          is_set(mon_ops, mon_op_inactive_resources));
     }
 
     /* For each resource, display it if appropriate */
     for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
         resource_t *rsc = (resource_t *) rsc_iter->data;
 
         /* 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 (is_set(rsc->flags, pe_rsc_orphan) && !is_active) {
             continue;
 
         /* Skip active resources if we already displayed them by node */
         } else if (is_set(mon_ops, mon_op_group_by_node)) {
             if (is_active) {
                 continue;
             }
 
         /* Skip primitives already counted in a brief summary */
         } else if (brief_output && (rsc->variant == pe_native)) {
             continue;
 
         /* Skip resources that aren't at least partially active,
          * unless we're displaying inactive resources
          */
         } else if (!partially_active && is_not_set(mon_ops, mon_op_inactive_resources)) {
             continue;
         }
 
         /* Print this resource */
         if (printed_heading == FALSE) {
             print_resources_heading(state, mon_ops);
             printed_heading = TRUE;
         }
         rsc->fns->print(rsc, prefix, print_opts, state->stream);
     }
 
     print_resources_closing(state, printed_heading, mon_ops);
 }
 
 /*!
  * \internal
  * \brief Print heading for resource history
  *
  * \param[in] stream      File stream to display output to
  * \param[in] data_set    Current state of CIB
  * \param[in] node        Node that ran this resource
  * \param[in] rsc         Resource to print
  * \param[in] rsc_id      ID of resource to print
  * \param[in] all         Whether to print every resource or just failed ones
  */
 static void
 print_rsc_history_start(mon_state_t *state, pe_working_set_t *data_set,
                         node_t *node, resource_t *rsc, const char *rsc_id,
                         gboolean all)
 {
     time_t last_failure = 0;
     int failcount = rsc?
                     pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
                                      NULL, data_set)
                     : 0;
 
     if (!all && !failcount && (last_failure <= 0)) {
         return;
     }
 
     /* Print resource ID */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "   %s:", rsc_id);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "   %s:", rsc_id);
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "            output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, " orphan");
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, " orphan");
                 break;
 
             case mon_output_xml:
                 fprintf(state->stream, " orphan=\"true\"");
                 break;
 
             default:
                 break;
         }
 
     /* If resource is not an orphan, print some details */
     } else if (all || failcount || (last_failure > 0)) {
 
         /* Print migration threshold */
         switch (state->output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, " migration-threshold=%d", rsc->migration_threshold);
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, " migration-threshold=%d", rsc->migration_threshold);
                 break;
 
             case mon_output_xml:
                 fprintf(state->stream, " orphan=\"false\" migration-threshold=\"%d\"",
                         rsc->migration_threshold);
                 break;
 
             default:
                 break;
         }
 
         /* Print fail count if any */
         if (failcount > 0) {
             switch (state->output_format) {
                 case mon_output_plain:
                 case mon_output_console:
                     print_as(state->output_format, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
                     break;
 
                 case mon_output_html:
                 case mon_output_cgi:
                     fprintf(state->stream, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
                     break;
 
                 case mon_output_xml:
                     fprintf(state->stream, " " CRM_FAIL_COUNT_PREFIX "=\"%d\"",
                             failcount);
                     break;
 
                 default:
                     break;
             }
         }
 
         /* Print last failure time if any */
         if (last_failure > 0) {
             print_nvpair(state, CRM_LAST_FAILURE_PREFIX, NULL, NULL, last_failure);
         }
     }
 
     /* End the heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "\n    \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, ">\n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print closing for resource history
  *
  * \param[in] stream      File stream to display output to
  */
 static void
 print_rsc_history_end(mon_state_t *state)
 {
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "    
\n   \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "            \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print operation history
  *
  * \param[in] stream        File stream to display output to
  * \param[in] data_set      Current state of CIB
  * \param[in] node          Node this operation is for
  * \param[in] xml_op        Root of XML tree describing this operation
  * \param[in] task          Task parsed from this operation's XML
  * \param[in] interval_ms_s Interval parsed from this operation's XML
  * \param[in] rc            Return code parsed from this operation's XML
  */
 static void
 print_op_history(mon_state_t *state, pe_working_set_t *data_set, node_t *node,
                  xmlNode *xml_op, const char *task, const char *interval_ms_s,
                  int rc, unsigned int mon_ops)
 {
     const char *value = NULL;
     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
 
     /* Begin the operation description */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "    + (%s) %s:", call, task);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "     (%s) %s:", call, task);
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "                 0) {
                 print_nvpair(state, attr, NULL, NULL, int_value);
             }
         }
 
         attr = XML_RSC_OP_LAST_RUN;
         value = crm_element_value(xml_op, attr);
         if (value) {
             int_value = crm_parse_int(value, NULL);
             if (int_value > 0) {
                 print_nvpair(state, attr, NULL, NULL, int_value);
             }
         }
 
         attr = XML_RSC_OP_T_EXEC;
         value = crm_element_value(xml_op, attr);
         if (value) {
             print_nvpair(state, attr, value, "ms", 0);
         }
 
         attr = XML_RSC_OP_T_QUEUE;
         value = crm_element_value(xml_op, attr);
         if (value) {
             print_nvpair(state, attr, value, "ms", 0);
         }
     }
 
     /* End the operation description */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, " rc=%d (%s)\n", rc, services_ocf_exitcode_str(rc));
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " rc=%d (%s)\n", rc, services_ocf_exitcode_str(rc));
             break;
 
         case mon_output_xml:
             fprintf(state->stream, " rc=\"%d\" rc_text=\"%s\" />\n", rc, services_ocf_exitcode_str(rc));
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print resource operation/failure history
  *
  * \param[in] stream      File stream to display output to
  * \param[in] data_set    Current state of CIB
  * \param[in] node        Node that ran this resource
  * \param[in] rsc_entry   Root of XML tree describing resource status
  * \param[in] operations  Whether to print operations or just failcounts
  */
 static void
 print_rsc_history(mon_state_t *state, pe_working_set_t *data_set, node_t *node,
                   xmlNode *rsc_entry, gboolean operations, unsigned int mon_ops)
 {
     GListPtr gIter = NULL;
     GListPtr op_list = NULL;
     gboolean printed = FALSE;
     const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
     resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
     xmlNode *rsc_op = NULL;
 
     /* If we're not showing operations, just print the resource failure summary */
     if (operations == FALSE) {
         print_rsc_history_start(state, data_set, node, rsc, rsc_id, FALSE);
         print_rsc_history_end(state);
         return;
     }
 
     /* Create a list of this resource's operations */
     for (rsc_op = __xml_first_child(rsc_entry); rsc_op != NULL; rsc_op = __xml_next(rsc_op)) {
         if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) {
             op_list = g_list_append(op_list, rsc_op);
         }
     }
     op_list = g_list_sort(op_list, sort_op_by_callid);
 
     /* 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 rc = crm_parse_int(op_rc, "0");
 
         /* Display 0-interval monitors as "probe" */
         if (safe_str_eq(task, CRMD_ACTION_STATUS)
             && ((interval_ms_s == NULL) || safe_str_eq(interval_ms_s, "0"))) {
             task = "probe";
         }
 
         /* Ignore notifies and some probes */
         if (safe_str_eq(task, CRMD_ACTION_NOTIFY) || (safe_str_eq(task, "probe") && (rc == 7))) {
             continue;
         }
 
         /* If this is the first printed operation, print heading for resource */
         if (printed == FALSE) {
             printed = TRUE;
             print_rsc_history_start(state, data_set, node, rsc, rsc_id, TRUE);
         }
 
         /* Print the operation */
         print_op_history(state, data_set, node, xml_op, task, interval_ms_s, rc, mon_ops);
     }
 
     /* Free the list we created (no need to free the individual items) */
     g_list_free(op_list);
 
     /* If we printed anything, close the resource */
     if (printed) {
         print_rsc_history_end(state);
     }
 }
 
 /*!
  * \internal
  * \brief Print node operation/failure history
  *
  * \param[in] stream      File stream to display output to
  * \param[in] data_set    Current state of CIB
  * \param[in] node_state  Root of XML tree describing node status
  * \param[in] operations  Whether to print operations or just failcounts
  */
 static void
 print_node_history(mon_state_t *state, pe_working_set_t *data_set,
                    xmlNode *node_state, gboolean operations,
                    unsigned int mon_ops)
 {
     node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
     xmlNode *lrm_rsc = NULL;
     xmlNode *rsc_entry = NULL;
 
     if (node && node->details && node->details->online) {
         print_node_start(state, node, mon_ops);
 
         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 = __xml_first_child(lrm_rsc); rsc_entry != NULL;
              rsc_entry = __xml_next(rsc_entry)) {
 
             if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) {
                 print_rsc_history(state, data_set, node, rsc_entry, operations, mon_ops);
             }
         }
 
         print_node_end(state);
     }
 }
 
 /*!
  * \internal
  * \brief Print extended information about an attribute if appropriate
  *
  * \param[in] data_set  Working set of CIB state
  *
  * \return TRUE if extended information was 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 gboolean
 print_attr_msg(mon_state_t *state, node_t * node, GListPtr rsc_list,
                const char *attrname, const char *attrvalue)
 {
     GListPtr gIter = NULL;
 
     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
         resource_t *rsc = (resource_t *) gIter->data;
         const char *type = g_hash_table_lookup(rsc->meta, "type");
 
         if (rsc->children != NULL) {
             if (print_attr_msg(state, node, rsc->children, attrname, attrvalue)) {
                 return TRUE;
             }
         }
 
         if (safe_str_eq(type, "ping") || safe_str_eq(type, "pingd")) {
             const char *name = g_hash_table_lookup(rsc->parameters, "name");
 
             if (name == NULL) {
                 name = "pingd";
             }
 
             /* To identify the resource with the attribute name. */
             if (safe_str_eq(name, attrname)) {
                 int host_list_num = 0;
                 int expected_score = 0;
                 int value = crm_parse_int(attrvalue, "0");
                 const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list");
                 const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier");
 
                 if(hosts) {
                     char **host_list = g_strsplit(hosts, " ", 0);
                     host_list_num = g_strv_length(host_list);
                     g_strfreev(host_list);
                 }
 
                 /* pingd multiplier is the same as the default value. */
                 expected_score = host_list_num * crm_parse_int(multiplier, "1");
 
                 switch (state->output_format) {
                     case mon_output_plain:
                     case mon_output_console:
                         if (value <= 0) {
                             print_as(state->output_format, "\t: Connectivity is lost");
                         } else if (value < expected_score) {
                             print_as(state->output_format, "\t: Connectivity is degraded (Expected=%d)", expected_score);
                         }
                         break;
 
                     case mon_output_html:
                     case mon_output_cgi:
                         if (value <= 0) {
                             fprintf(state->stream, " (connectivity is lost)");
                         } else if (value < expected_score) {
                             fprintf(state->stream, " (connectivity is degraded -- expected %d)",
                                     expected_score);
                         }
                         break;
 
                     case mon_output_xml:
                         fprintf(state->stream, " expected=\"%d\"", expected_score);
                         break;
 
                     default:
                         break;
                 }
                 return TRUE;
             }
         }
     }
     return FALSE;
 }
 
 /* structure for passing multiple user data to g_list_foreach() */
 struct mon_attr_data {
     mon_state_t *state;
     node_t *node;
 };
 
 static void
 print_node_attribute(gpointer name, gpointer user_data)
 {
     const char *value = NULL;
     struct mon_attr_data *data = (struct mon_attr_data *) user_data;
 
     value = pe_node_attribute_raw(data->node, name);
 
     /* Print attribute name and value */
     switch (data->state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(data->state->output_format, "    + %-32s\t: %-10s", (char *)name, value);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(data->state->stream, "   %s: %s",
                     (char *)name, value);
             break;
 
         case mon_output_xml:
             fprintf(data->state->stream,
                     "            state, data->node, data->node->details->running_rsc,
                    name, value);
 
     /* Close out the attribute */
     switch (data->state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(data->state->output_format, "\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(data->state->stream, "\n");
             break;
 
         case mon_output_xml:
             fprintf(data->state->stream, " />\n");
             break;
 
         default:
             break;
     }
 }
 
 static void
 print_node_summary(mon_state_t *state, pe_working_set_t * data_set,
                    gboolean operations, unsigned int mon_ops)
 {
     xmlNode *node_state = NULL;
     xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
 
     /* Print heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             if (operations) {
                 print_as(state->output_format, "\nOperations:\n");
             } else {
                 print_as(state->output_format, "\nMigration Summary:\n");
             }
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             if (operations) {
                 fprintf(state->stream, "
\n Operations
\n");
             } else {
                 fprintf(state->stream, " 
\n Migration Summary
\n");
             }
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     /* Print each node in the CIB status */
     for (node_state = __xml_first_child(cib_status); node_state != NULL;
          node_state = __xml_next(node_state)) {
         if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) {
             print_node_history(state, data_set, node_state, operations, mon_ops);
         }
     }
 
     /* Close section */
     switch (state->output_format) {
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 static void
 print_ticket(gpointer name, gpointer value, gpointer user_data)
 {
     mon_state_t *data = (mon_state_t *) user_data;
     ticket_t *ticket = (ticket_t *) value;
 
     switch (data->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(data->output_format, "* %s:\t%s%s", ticket->id,
                      (ticket->granted? "granted" : "revoked"),
                      (ticket->standby? " [standby]" : ""));
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(data->stream, "  %s: %s%s", ticket->id,
                     (ticket->granted? "granted" : "revoked"),
                     (ticket->standby? " [standby]" : ""));
             break;
 
         case mon_output_xml:
             fprintf(data->stream, "        id, (ticket->granted? "granted" : "revoked"),
                     (ticket->standby? "true" : "false"));
             break;
 
         default:
             break;
     }
     if (ticket->last_granted > -1) {
         print_nvpair(data, "last-granted", NULL, NULL, ticket->last_granted);
     }
     switch (data->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(data->output_format, "\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(data->stream, "\n");
             break;
 
         case mon_output_xml:
             fprintf(data->stream, " />\n");
             break;
 
         default:
             break;
     }
 }
 
 static void
 print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set)
 {
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nTickets:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " 
\n Tickets
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     /* Print each ticket */
     g_hash_table_foreach(data_set->tickets, print_ticket, state);
 
     /* Close section */
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " 
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print a negative location constraint
  *
  * \param[in] stream     File stream to display output to
  * \param[in] node       Node affected by constraint
  * \param[in] location   Constraint to print
  */
 static void
 print_ban(mon_state_t *state, pe_node_t *node, pe__location_t *location,
           unsigned int mon_ops)
 {
     char *node_name = NULL;
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             node_name = get_node_display_name(node, mon_ops);
             print_as(state->output_format, " %s\tprevents %s from running %son %s\n",
                      location->id, location->rsc_lh->id,
                      ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
                      node_name);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             node_name = get_node_display_name(node, mon_ops);
             fprintf(state->stream, "  %s prevents %s from running %son %s\n",
                      location->id, location->rsc_lh->id,
                      ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
                      node_name);
             break;
 
         case mon_output_xml:
             fprintf(state->stream,
                     "        \n",
                     location->id, location->rsc_lh->id, node->details->uname, node->weight,
                     ((location->role_filter == RSC_ROLE_MASTER)? "true" : "false"));
             break;
 
         default:
             break;
     }
     free(node_name);
 }
 
 /*!
  * \internal
  * \brief Print section for negative location constraints
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set corresponding to CIB status to display
  */
 static void
 print_neg_locations(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops,
                     const char *prefix)
 {
     GListPtr gIter, gIter2;
 
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nNegative Location Constraints:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "
\n Negative Location Constraints
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     /* Print each ban */
     for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
         pe__location_t *location = gIter->data;
         if (!g_str_has_prefix(location->id, prefix))
             continue;
         for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
             node_t *node = (node_t *) gIter2->data;
 
             if (node->weight < 0) {
                 print_ban(state, node, location, mon_ops);
             }
         }
     }
 
     /* Close section */
     switch (state->output_format) {
         case mon_output_cgi:
         case mon_output_html:
             fprintf(state->stream, " 
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print node attributes section
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  */
 static void
 print_node_attributes(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops)
 {
     GListPtr gIter = NULL;
 
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nNode Attributes:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "
\n Node Attributes
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     /* Unpack all resource parameters (it would be more efficient to do this
      * only when needed for the first time in print_attr_msg())
      */
     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
         crm_mon_get_parameters(gIter->data, data_set);
     }
 
     /* Display each node's attributes */
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         struct mon_attr_data data;
 
         data.state = state;
         data.node = (node_t *) gIter->data;
 
         if (data.node && data.node->details && data.node->details->online) {
             GList *attr_list = NULL;
 
             print_node_start(state, data.node, mon_ops);
             g_hash_table_foreach(data.node->details->attrs, append_attr_list, attr_list);
 
             g_list_foreach(attr_list, print_node_attribute, &data);
             g_list_free(attr_list);
             print_node_end(state);
         }
     }
 
     /* Print section footer */
     switch (state->output_format) {
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print header for cluster summary if needed
  *
  * \param[in] stream     File stream to display output to
  */
 static void
 print_cluster_summary_header(mon_state_t *state)
 {
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " Cluster Summary
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print footer for cluster summary if needed
  *
  * \param[in] stream     File stream to display output to
  */
 static void
 print_cluster_summary_footer(mon_state_t *state)
 {
     switch (state->output_format) {
         case mon_output_cgi:
         case mon_output_html:
             fprintf(state->stream, " 
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print times the display was last updated and CIB last changed
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  */
 static void
 print_cluster_times(mon_state_t *state, pe_working_set_t *data_set)
 {
     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);
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console: {
             const char *now_str = crm_now_string(NULL);
 
             print_as(state->output_format, "Last updated: %s", now_str ? now_str : "Could not determine current time");
             print_as(state->output_format, (user || client || origin)? "\n" : "\t\t");
             print_as(state->output_format, "Last change: %s", last_written ? last_written : "");
             if (user) {
                 print_as(state->output_format, " by %s", user);
             }
             if (client) {
                 print_as(state->output_format, " via %s", client);
             }
             if (origin) {
                 print_as(state->output_format, " on %s", origin);
             }
             print_as(state->output_format, "\n");
             break;
         }
 
         case mon_output_html:
         case mon_output_cgi: {
             const char *now_str = crm_now_string(NULL);
 
             fprintf(state->stream, " Last updated: %s
\n",
                     now_str ? now_str : "Could not determine current time");
             fprintf(state->stream, " Last change: %s", last_written ? last_written : "");
             if (user) {
                 fprintf(state->stream, " by %s", user);
             }
             if (client) {
                 fprintf(state->stream, " via %s", client);
             }
             if (origin) {
                 fprintf(state->stream, " on %s", origin);
             }
             fprintf(state->stream, "
\n");
             break;
         }
 
         case mon_output_xml: {
             const char *now_str = crm_now_string(NULL);
 
             fprintf(state->stream, "        \n",
                     now_str ? now_str : "Could not determine current time");
             fprintf(state->stream, "        \n",
                     last_written ? last_written : "", user ? user : "",
                     client ? client : "", origin ? origin : "");
             break;
         }
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print cluster stack
  *
  * \param[in] stream     File stream to display output to
  * \param[in] stack_s    Stack name
  */
 static void
 print_cluster_stack(mon_state_t *state, const char *stack_s)
 {
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "Stack: %s\n", stack_s);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " Stack: %s
\n", stack_s);
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "        \n", stack_s);
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print current DC and its version
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  */
 static void
 print_cluster_dc(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops)
 {
     node_t *dc = data_set->dc_node;
     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 = dc? get_node_display_name(dc, mon_ops) : NULL;
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "Current DC: ");
             if (dc) {
                 print_as(state->output_format, "%s (version %s) - partition %s quorum\n",
                          dc_name, (dc_version_s? dc_version_s : "unknown"),
                          (crm_is_true(quorum) ? "with" : "WITHOUT"));
             } else {
                 print_as(state->output_format, "NONE\n");
             }
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " Current DC: ");
             if (dc) {
                 fprintf(state->stream, "%s (version %s) - partition %s quorum",
                         dc_name, (dc_version_s? dc_version_s : "unknown"),
                         (crm_is_true(quorum)? "with" : "WITHOUT"));
             } else {
                 fprintf(state->stream, "NONE");
             }
             fprintf(state->stream, "
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream,  "        stream,
                         "present=\"true\" version=\"%s\" name=\"%s\" id=\"%s\" with_quorum=\"%s\"",
                         (dc_version_s? dc_version_s : ""), dc->details->uname, dc->details->id,
                         (crm_is_true(quorum) ? "true" : "false"));
             } else {
                 fprintf(state->stream, "present=\"false\"");
             }
             fprintf(state->stream, " />\n");
             break;
 
         default:
             break;
     }
     free(dc_name);
 }
 
 /*!
  * \internal
  * \brief Print counts of configured nodes and resources
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  * \param[in] stack_s    Stack name
  */
 static void
 print_cluster_counts(mon_state_t *state, pe_working_set_t *data_set, const char *stack_s)
 {
     int nnodes = g_list_length(data_set->nodes);
     int nresources = count_resources(data_set, NULL);
 
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
 
             print_as(state->output_format, "\n%d node%s configured\n", nnodes, s_if_plural(nnodes));
 
             print_as(state->output_format, "%d resource%s configured",
                      nresources, s_if_plural(nresources));
             if(data_set->disabled_resources || data_set->blocked_resources) {
                 print_as(state->output_format, " (");
                 if (data_set->disabled_resources) {
                     print_as(state->output_format, "%d DISABLED", data_set->disabled_resources);
                 }
                 if (data_set->disabled_resources && data_set->blocked_resources) {
                     print_as(state->output_format, ", ");
                 }
                 if (data_set->blocked_resources) {
                     print_as(state->output_format, "%d BLOCKED from starting due to failure",
                              data_set->blocked_resources);
                 }
                 print_as(state->output_format, ")");
             }
             print_as(state->output_format, "\n");
 
             break;
 
         case mon_output_html:
         case mon_output_cgi:
 
             fprintf(state->stream, " %d node%s configured
\n",
                     nnodes, s_if_plural(nnodes));
 
             fprintf(state->stream, " %d resource%s configured",
                     nresources, s_if_plural(nresources));
             if (data_set->disabled_resources || data_set->blocked_resources) {
                 fprintf(state->stream, " (");
                 if (data_set->disabled_resources) {
                     fprintf(state->stream, "%d DISABLED",
                             data_set->disabled_resources);
                 }
                 if (data_set->disabled_resources && data_set->blocked_resources) {
                     fprintf(state->stream, ", ");
                 }
                 if (data_set->blocked_resources) {
                     fprintf(state->stream,
                             "%d BLOCKED from starting due to failure",
                             data_set->blocked_resources);
                 }
                 fprintf(state->stream, ")");
             }
             fprintf(state->stream, "
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream,
                     "        \n",
                     g_list_length(data_set->nodes));
             fprintf(state->stream,
                     "        \n",
                     count_resources(data_set, NULL),
                     data_set->disabled_resources, data_set->blocked_resources);
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print cluster-wide options
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  *
  * \note Currently this is only implemented for HTML and XML output, and
  *       prints only a few options. If there is demand, more could be added.
  */
 static void
 print_cluster_options(mon_state_t *state, pe_working_set_t *data_set)
 {
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
                 print_as(state->output_format, "\n              *** Resource management is DISABLED ***");
                 print_as(state->output_format, "\n  The cluster will not attempt to start, stop or recover services");
                 print_as(state->output_format, "\n");
             }
             break;
 
         case mon_output_html:
             fprintf(state->stream, " 
\n Config Options
\n");
             fprintf(state->stream, " \n");
             fprintf(state->stream, "  | STONITH of failed nodes | %s | 
|---|
\n",
                     is_set(data_set->flags, pe_flag_stonith_enabled)? "enabled" : "disabled");
 
             fprintf(state->stream, "  | Cluster is | %ssymmetric | 
|---|
\n",
                     is_set(data_set->flags, pe_flag_symmetric_cluster)? "" : "a");
 
             fprintf(state->stream, "  | No Quorum Policy | ");
             switch (data_set->no_quorum_policy) {
                 case no_quorum_freeze:
                     fprintf(state->stream, "Freeze resources");
                     break;
                 case no_quorum_stop:
                     fprintf(state->stream, "Stop ALL resources");
                     break;
                 case no_quorum_ignore:
                     fprintf(state->stream, "Ignore");
                     break;
                 case no_quorum_suicide:
                     fprintf(state->stream, "Suicide");
                     break;
             }
             fprintf(state->stream, " | 
|---|
\n");
 
             fprintf(state->stream, "  | Resource management | ");
             if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
                 fprintf(state->stream, "DISABLED (the cluster will "
                                 "not attempt to start, stop or recover services)");
             } else {
                 fprintf(state->stream, "enabled");
             }
             fprintf(state->stream, " | 
|---|
\n");
 
             fprintf(state->stream, "
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "        stream, " stonith-enabled=\"%s\"",
                     is_set(data_set->flags, pe_flag_stonith_enabled)?
                     "true" : "false");
             fprintf(state->stream, " symmetric-cluster=\"%s\"",
                     is_set(data_set->flags, pe_flag_symmetric_cluster)?
                     "true" : "false");
             fprintf(state->stream, " no-quorum-policy=\"");
             switch (data_set->no_quorum_policy) {
                 case no_quorum_freeze:
                     fprintf(state->stream, "freeze");
                     break;
                 case no_quorum_stop:
                     fprintf(state->stream, "stop");
                     break;
                 case no_quorum_ignore:
                     fprintf(state->stream, "ignore");
                     break;
                 case no_quorum_suicide:
                     fprintf(state->stream, "suicide");
                     break;
             }
             fprintf(state->stream, "\"");
             fprintf(state->stream, " maintenance-mode=\"%s\"",
                     is_set(data_set->flags, pe_flag_maintenance_mode)?
                     "true" : "false");
             fprintf(state->stream, " />\n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print a summary of cluster-wide information
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  */
 static void
 print_cluster_summary(mon_state_t *state, pe_working_set_t *data_set,
                       unsigned int mon_ops, unsigned int show)
 {
     const char *stack_s = get_cluster_stack(data_set);
     gboolean header_printed = FALSE;
 
     if (show & mon_show_stack) {
         if (header_printed == FALSE) {
             print_cluster_summary_header(state);
             header_printed = TRUE;
         }
         print_cluster_stack(state, stack_s);
     }
 
     /* Always print DC if none, even if not requested */
     if ((data_set->dc_node == NULL) || (show & mon_show_dc)) {
         if (header_printed == FALSE) {
             print_cluster_summary_header(state);
             header_printed = TRUE;
         }
         print_cluster_dc(state, data_set, mon_ops);
     }
 
     if (show & mon_show_times) {
         if (header_printed == FALSE) {
             print_cluster_summary_header(state);
             header_printed = TRUE;
         }
         print_cluster_times(state, data_set);
     }
 
     if (is_set(data_set->flags, pe_flag_maintenance_mode)
         || data_set->disabled_resources
         || data_set->blocked_resources
         || is_set(show, mon_show_count)) {
         if (header_printed == FALSE) {
             print_cluster_summary_header(state);
             header_printed = TRUE;
         }
         print_cluster_counts(state, data_set, stack_s);
     }
 
     /* There is not a separate option for showing cluster options, so show with
      * stack for now; a separate option could be added if there is demand
      */
     if (show & mon_show_stack) {
         print_cluster_options(state, data_set);
     }
 
     if (header_printed) {
         print_cluster_summary_footer(state);
     }
 }
 
 /*!
  * \internal
  * \brief Print a failed action
  *
  * \param[in] stream     File stream to display output to
  * \param[in] xml_op     Root of XML tree describing failed action
  */
 static void
 print_failed_action(mon_state_t *state, xmlNode *xml_op)
 {
     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
     const char *op_key_attr = "op_key";
     const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE);
     const char *node = crm_element_value(xml_op, XML_ATTR_UNAME);
     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
     const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
     int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0");
     int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0");
     char *exit_reason_cleaned;
 
     /* If no op_key was given, use id instead */
     if (op_key == NULL) {
         op_key = ID(xml_op);
         op_key_attr = "id";
     }
 
     /* If no exit reason was given, use "none" */
     if (exit_reason == NULL) {
         exit_reason = "none";
     }
 
     /* Print common action information */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "* %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
                      op_key, node, services_ocf_exitcode_str(rc), rc,
                      call, services_lrm_status_str(status), exit_reason);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "  %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
                      op_key, node, services_ocf_exitcode_str(rc), rc,
                      call, services_lrm_status_str(status), exit_reason);
             break;
 
         case mon_output_xml:
             exit_reason_cleaned = crm_xml_escape(exit_reason);
             fprintf(state->stream, "        stream, " exitstatus=\"%s\" exitreason=\"%s\" exitcode=\"%d\"",
                     services_ocf_exitcode_str(rc), exit_reason_cleaned, rc);
             fprintf(state->stream, " call=\"%s\" status=\"%s\"",
                     call, services_lrm_status_str(status));
             free(exit_reason_cleaned);
             break;
 
         default:
             break;
     }
 
     /* If last change was given, print timing information as well */
     if (last) {
         time_t run_at = crm_parse_int(last, "0");
         char *run_at_s = ctime(&run_at);
 
         if (run_at_s) {
             run_at_s[24] = 0; /* Overwrite the newline */
         }
 
         switch (state->output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, ",\n    last-rc-change='%s', queued=%sms, exec=%sms",
                          run_at_s? run_at_s : "",
                          crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
                          crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, " last-rc-change='%s', queued=%sms, exec=%sms",
                         run_at_s? run_at_s : "",
                         crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
                         crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
                 break;
 
             case mon_output_xml:
                 fprintf(state->stream,
                         " last-rc-change=\"%s\" queued=\"%s\" exec=\"%s\" interval=\"%u\" task=\"%s\"",
                         run_at_s? run_at_s : "",
                         crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
                         crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
                         crm_parse_ms(crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS)),
                         crm_element_value(xml_op, XML_LRM_ATTR_TASK));
                 break;
 
             default:
                 break;
         }
     }
 
     /* End the action listing */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, " />\n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print a section for failed actions
  *
  * \param[in] stream     File stream to display output to
  * \param[in] data_set   Working set of CIB state
  */
 static void
 print_failed_actions(mon_state_t *state, pe_working_set_t *data_set)
 {
     xmlNode *xml_op = NULL;
 
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nFailed Resource Actions:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream,
                     " 
\n Failed Resource Actions
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     /* Print each failed action */
     for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
          xml_op = __xml_next(xml_op)) {
         print_failed_action(state, xml_op);
     }
 
     /* End section */
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " 
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print a stonith action
  *
  * \param[in] stream     File stream to display output to
  * \param[in] event      stonith event
  */
 static void
 print_stonith_action(mon_state_t *state, stonith_history_t *event, unsigned int mon_ops)
 {
     const char *action_s = stonith_action_str(event->action);
     char *run_at_s = ctime(&event->completed);
 
     if ((run_at_s) && (run_at_s[0] != 0)) {
         run_at_s[strlen(run_at_s)-1] = 0; /* Overwrite the newline */
     }
 
     switch(state->output_format) {
         case mon_output_xml:
             fprintf(state->stream, "        target, event->action);
             switch(event->state) {
                 case st_done:
                     fprintf(state->stream, " state=\"success\"");
                     break;
                 case st_failed:
                     fprintf(state->stream, " state=\"failed\"");
                     break;
                 default:
                     fprintf(state->stream, " state=\"pending\"");
             }
             fprintf(state->stream, " origin=\"%s\" client=\"%s\"",
                     event->origin, event->client);
             if (event->delegate) {
                 fprintf(state->stream, " delegate=\"%s\"", event->delegate);
             }
             switch(event->state) {
                 case st_done:
                 case st_failed:
                     fprintf(state->stream, " completed=\"%s\"", run_at_s?run_at_s:"");
                     break;
                 default:
                     break;
             }
             fprintf(state->stream, " />\n");
             break;
 
         case mon_output_plain:
         case mon_output_console:
             switch(event->state) {
                 case st_done:
                     print_as(state->output_format, "* %s of %s successful: delegate=%s, client=%s, origin=%s,\n"
                              "    %s='%s'\n",
                              action_s, event->target,
                              event->delegate ? event->delegate : "",
                              event->client, event->origin,
                              is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-successful",
                              run_at_s?run_at_s:"");
                     break;
                 case st_failed:
                     print_as(state->output_format, "* %s of %s failed: delegate=%s, client=%s, origin=%s,\n"
                              "    %s='%s'\n",
                              action_s, event->target,
                              event->delegate ? event->delegate : "",
                              event->client, event->origin,
                              is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-failed",
                              run_at_s?run_at_s:"");
                     break;
                 default:
                     print_as(state->output_format, "* %s of %s pending: client=%s, origin=%s\n",
                              action_s, event->target,
                              event->client, event->origin);
             }
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             switch(event->state) {
                 case st_done:
                     fprintf(state->stream, "  %s of %s successful: delegate=%s, "
                                     "client=%s, origin=%s, %s='%s'\n",
                                     action_s, event->target,
                                     event->delegate ? event->delegate : "",
                                     event->client, event->origin,
                                     is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-successful",
                                     run_at_s?run_at_s:"");
                     break;
                 case st_failed:
                     fprintf(state->stream, "  %s of %s failed: delegate=%s, "
                                     "client=%s, origin=%s, %s='%s'\n",
                                     action_s, event->target,
                                     event->delegate ? event->delegate : "",
                                     event->client, event->origin,
                                     is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-failed",
                                     run_at_s?run_at_s:"");
                     break;
                 default:
                     fprintf(state->stream, "  %s of %s pending: client=%s, "
                                     "origin=%s\n",
                                     action_s, event->target,
                                     event->client, event->origin);
             }
             break;
 
         default:
             /* no support for fence history for other formats so far */
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print a section for failed stonith actions
  *
  * \param[in] stream     File stream to display output to
  * \param[in] history    List of stonith actions
  *
  */
 static void
 print_failed_stonith_actions(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops)
 {
     stonith_history_t *hp;
 
     for (hp = history; hp; hp = hp->next) {
         if (hp->state == st_failed) {
             break;
         }
     }
     if (!hp) {
         return;
     }
 
     /* Print section heading */
     switch (state->output_format) {
         /* no need to take care of xml in here as xml gets full
          * history anyway
          */
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nFailed Fencing Actions:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "
\n Failed Fencing Actions
\n \n");
             break;
 
         default:
             break;
     }
 
     /* Print each failed stonith action */
     for (hp = history; hp; hp = hp->next) {
         if (hp->state == st_failed) {
             print_stonith_action(state, hp, mon_ops);
         }
     }
 
     /* End section */
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " 
\n");
             break;
 
         default:
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Print pending stonith actions
  *
  * \param[in] stream     File stream to display output to
  * \param[in] history    List of stonith actions
  *
  */
 static void
 print_stonith_pending(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops)
 {
     /* xml-output always shows the full history
      * so we'll never have to show pending-actions
      * separately
      */
     if (history && (history->state != st_failed) &&
         (history->state != st_done)) {
         stonith_history_t *hp;
 
         /* Print section heading */
         switch (state->output_format) {
             case mon_output_plain:
             case mon_output_console:
                 print_as(state->output_format, "\nPending Fencing Actions:\n");
                 break;
 
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, "
\n Pending Fencing Actions
\n \n");
                 break;
 
             default:
                 break;
         }
 
         for (hp = history; hp; hp = hp->next) {
             if ((hp->state == st_failed) || (hp->state == st_done)) {
                 break;
             }
             print_stonith_action(state, hp, mon_ops);
         }
 
         /* End section */
         switch (state->output_format) {
             case mon_output_html:
             case mon_output_cgi:
                 fprintf(state->stream, " 
\n");
                 break;
 
         default:
             break;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Print a section for stonith-history
  *
  * \param[in] stream     File stream to display output to
  * \param[in] history    List of stonith actions
  *
  */
 static void
 print_stonith_history(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops)
 {
     stonith_history_t *hp;
 
     /* Print section heading */
     switch (state->output_format) {
         case mon_output_plain:
         case mon_output_console:
             print_as(state->output_format, "\nFencing History:\n");
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, "
\n Fencing History
\n \n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 
     for (hp = history; hp; hp = hp->next) {
         if ((hp->state != st_failed) || (state->output_format == mon_output_xml)) {
             print_stonith_action(state, hp, mon_ops);
         }
     }
 
     /* End section */
     switch (state->output_format) {
         case mon_output_html:
         case mon_output_cgi:
             fprintf(state->stream, " 
\n");
             break;
 
         case mon_output_xml:
             fprintf(state->stream, "    \n");
             break;
 
         default:
             break;
     }
 }
 
 void
 print_status(mon_state_t *state, pe_working_set_t *data_set,
              stonith_history_t *stonith_history, unsigned int mon_ops,
              unsigned int show, const char *prefix)
 {
     GListPtr gIter = NULL;
     int print_opts = get_resource_display_options(mon_ops, state->output_format);
 
     /* space-separated lists of node names */
     char *online_nodes = NULL;
     char *online_remote_nodes = NULL;
     char *online_guest_nodes = NULL;
     char *offline_nodes = NULL;
     char *offline_remote_nodes = NULL;
 
     if (state->output_format == mon_output_console) {
         blank_screen();
     }
     print_cluster_summary(state, data_set, mon_ops, show);
     print_as(state->output_format, "\n");
 
     /* Gather node information (and print if in bad state or grouping by node) */
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
         const char *node_mode = NULL;
         char *node_name = get_node_display_name(node, mon_ops);
 
         /* Get node mode */
         if (node->details->unclean) {
             if (node->details->online) {
                 node_mode = "UNCLEAN (online)";
 
             } else if (node->details->pending) {
                 node_mode = "UNCLEAN (pending)";
 
             } else {
                 node_mode = "UNCLEAN (offline)";
             }
 
         } else if (node->details->pending) {
             node_mode = "pending";
 
         } else if (node->details->standby_onfail && node->details->online) {
             node_mode = "standby (on-fail)";
 
         } else if (node->details->standby) {
             if (node->details->online) {
                 if (node->details->running_rsc) {
                     node_mode = "standby (with active resources)";
                 } else {
                     node_mode = "standby";
                 }
             } else {
                 node_mode = "OFFLINE (standby)";
             }
 
         } else if (node->details->maintenance) {
             if (node->details->online) {
                 node_mode = "maintenance";
             } else {
                 node_mode = "OFFLINE (maintenance)";
             }
 
         } else if (node->details->online) {
             node_mode = "online";
             if (is_not_set(mon_ops, mon_op_group_by_node)) {
                 if (pe__is_guest_node(node)) {
                     online_guest_nodes = add_list_element(online_guest_nodes, node_name);
                 } else if (pe__is_remote_node(node)) {
                     online_remote_nodes = add_list_element(online_remote_nodes, node_name);
                 } else {
                     online_nodes = add_list_element(online_nodes, node_name);
                 }
                 free(node_name);
                 continue;
             }
         } else {
             node_mode = "OFFLINE";
             if (is_not_set(mon_ops, mon_op_group_by_node)) {
                 if (pe__is_remote_node(node)) {
                     offline_remote_nodes = add_list_element(offline_remote_nodes, node_name);
                 } else if (pe__is_guest_node(node)) {
                     /* ignore offline guest nodes */
                 } else {
                     offline_nodes = add_list_element(offline_nodes, node_name);
                 }
                 free(node_name);
                 continue;
             }
         }
 
         /* If we get here, node is in bad state, or we're grouping by node */
 
         /* Print the node name and status */
         if (pe__is_guest_node(node)) {
             print_as(state->output_format, "Guest");
         } else if (pe__is_remote_node(node)) {
             print_as(state->output_format, "Remote");
         }
         print_as(state->output_format, "Node %s: %s\n", node_name, node_mode);
 
         /* If we're grouping by node, print its resources */
         if (is_set(mon_ops, mon_op_group_by_node)) {
             if (is_set(mon_ops, mon_op_print_brief)) {
                 print_rscs_brief(node->details->running_rsc, "\t", print_opts | pe_print_rsconly,
                                  state->stream, FALSE);
             } else {
                 GListPtr gIter2 = NULL;
 
                 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
                     resource_t *rsc = (resource_t *) gIter2->data;
 
                     rsc->fns->print(rsc, "\t", print_opts | pe_print_rsconly, state->stream);
                 }
             }
         }
         free(node_name);
     }
 
     /* If we're not grouping by node, summarize nodes by status */
     if (online_nodes) {
         print_as(state->output_format, "Online: [%s ]\n", online_nodes);
         free(online_nodes);
     }
     if (offline_nodes) {
         print_as(state->output_format, "OFFLINE: [%s ]\n", offline_nodes);
         free(offline_nodes);
     }
     if (online_remote_nodes) {
         print_as(state->output_format, "RemoteOnline: [%s ]\n", online_remote_nodes);
         free(online_remote_nodes);
     }
     if (offline_remote_nodes) {
         print_as(state->output_format, "RemoteOFFLINE: [%s ]\n", offline_remote_nodes);
         free(offline_remote_nodes);
     }
     if (online_guest_nodes) {
         print_as(state->output_format, "GuestOnline: [%s ]\n", online_guest_nodes);
         free(online_guest_nodes);
     }
 
     /* Print resources section, if needed */
     print_resources(state, data_set, print_opts, mon_ops);
 
     /* print Node Attributes section if requested */
     if (show & mon_show_attributes) {
         print_node_attributes(state, data_set, mon_ops);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (show & (mon_show_operations | mon_show_failcounts)) {
         print_node_summary(state, data_set,
                            ((show & mon_show_operations)? TRUE : FALSE), mon_ops);
     }
 
     /* If there were any failed actions, print them */
     if (xml_has_children(data_set->failed)) {
         print_failed_actions(state, data_set);
     }
 
     /* Print failed stonith actions */
     if (is_set(mon_ops, mon_op_fence_history)) {
         print_failed_stonith_actions(state, stonith_history, mon_ops);
     }
 
     /* Print tickets if requested */
     if (show & mon_show_tickets) {
         print_cluster_tickets(state, data_set);
     }
 
     /* Print negative location constraints if requested */
     if (show & mon_show_bans) {
         print_neg_locations(state, data_set, mon_ops, prefix);
     }
 
     /* Print stonith history */
     if (is_set(mon_ops, mon_op_fence_history)) {
         if (show & mon_show_fence_history) {
             print_stonith_history(state, stonith_history, mon_ops);
         } else {
             print_stonith_pending(state, stonith_history, mon_ops);
         }
     }
 
 #if CURSES_ENABLED
     if (state->output_format == mon_output_console) {
         refresh();
     }
 #endif
 }
 
 void
 print_xml_status(mon_state_t *state, pe_working_set_t *data_set,
                  stonith_history_t *stonith_history, unsigned int mon_ops,
                  unsigned int show, const char *prefix)
 {
     GListPtr gIter = NULL;
     int print_opts = get_resource_display_options(mon_ops, state->output_format);
 
     fprintf(state->stream, "\n");
     fprintf(state->stream, "\n", VERSION);
 
     print_cluster_summary(state, data_set, mon_ops, show);
 
     /*** NODES ***/
     fprintf(state->stream, "    \n");
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
         const char *node_type = "unknown";
 
         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;
         }
 
         fprintf(state->stream, "        details->uname);
         fprintf(state->stream, "id=\"%s\" ", node->details->id);
         fprintf(state->stream, "online=\"%s\" ", node->details->online ? "true" : "false");
         fprintf(state->stream, "standby=\"%s\" ", node->details->standby ? "true" : "false");
         fprintf(state->stream, "standby_onfail=\"%s\" ", node->details->standby_onfail ? "true" : "false");
         fprintf(state->stream, "maintenance=\"%s\" ", node->details->maintenance ? "true" : "false");
         fprintf(state->stream, "pending=\"%s\" ", node->details->pending ? "true" : "false");
         fprintf(state->stream, "unclean=\"%s\" ", node->details->unclean ? "true" : "false");
         fprintf(state->stream, "shutdown=\"%s\" ", node->details->shutdown ? "true" : "false");
         fprintf(state->stream, "expected_up=\"%s\" ", node->details->expected_up ? "true" : "false");
         fprintf(state->stream, "is_dc=\"%s\" ", node->details->is_dc ? "true" : "false");
         fprintf(state->stream, "resources_running=\"%d\" ", g_list_length(node->details->running_rsc));
         fprintf(state->stream, "type=\"%s\" ", node_type);
         if (pe__is_guest_node(node)) {
             fprintf(state->stream, "id_as_resource=\"%s\" ", node->details->remote_rsc->container->id);
         }
 
         if (is_set(mon_ops, mon_op_group_by_node)) {
             GListPtr lpc2 = NULL;
 
             fprintf(state->stream, ">\n");
             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
                 resource_t *rsc = (resource_t *) lpc2->data;
 
                 rsc->fns->print(rsc, "            ", print_opts | pe_print_rsconly, state->stream);
             }
             fprintf(state->stream, "        \n");
         } else {
             fprintf(state->stream, "/>\n");
         }
     }
     fprintf(state->stream, "    \n");
 
     /* Print resources section, if needed */
     print_resources(state, data_set, print_opts, mon_ops);
 
     /* print Node Attributes section if requested */
     if (show & mon_show_attributes) {
         print_node_attributes(state, data_set, mon_ops);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (show & (mon_show_operations | mon_show_failcounts)) {
         print_node_summary(state, data_set,
                            ((show & mon_show_operations)? TRUE : FALSE), mon_ops);
     }
 
     /* If there were any failed actions, print them */
     if (xml_has_children(data_set->failed)) {
         print_failed_actions(state, data_set);
     }
 
     /* Print stonith history */
     if (is_set(mon_ops, mon_op_fence_history)) {
         print_stonith_history(state, stonith_history, mon_ops);
     }
 
     /* Print tickets if requested */
     if (show & mon_show_tickets) {
         print_cluster_tickets(state, data_set);
     }
 
     /* Print negative location constraints if requested */
     if (show & mon_show_bans) {
         print_neg_locations(state, data_set, mon_ops, prefix);
     }
 
     fprintf(state->stream, "\n");
     fflush(state->stream);
-    fclose(state->stream);
 }
 
 int
 print_html_status(mon_state_t *state, pe_working_set_t *data_set,
                   const char *filename, stonith_history_t *stonith_history,
                   unsigned int mon_ops, unsigned int show, const char *prefix,
                   unsigned int reconnect_msec)
 {
     GListPtr gIter = NULL;
     char *filename_tmp = NULL;
     int print_opts = get_resource_display_options(mon_ops, state->output_format);
 
     if (state->output_format == mon_output_cgi) {
         fprintf(state->stream, "Content-Type: text/html\n\n");
 
     } else {
         filename_tmp = crm_concat(filename, "tmp", '.');
         state->stream = fopen(filename_tmp, "w");
         if (state->stream == NULL) {
             crm_perror(LOG_ERR, "Cannot open %s for writing", filename_tmp);
             free(filename_tmp);
             return -1;
         }
     }
 
     fprintf(state->stream, "\n");
     fprintf(state->stream, " \n");
     fprintf(state->stream, "  Cluster status\n");
     fprintf(state->stream, "  \n", reconnect_msec / 1000);
     fprintf(state->stream, " \n");
     fprintf(state->stream, "\n");
 
     print_cluster_summary(state, data_set, mon_ops, show);
 
     /*** NODE LIST ***/
 
     fprintf(state->stream, "
\n Node List
\n");
     fprintf(state->stream, "\n");
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         node_t *node = (node_t *) gIter->data;
         char *node_name = get_node_display_name(node, mon_ops);
 
         fprintf(state->stream, "- Node: %s: ", node_name);
         if (node->details->standby_onfail && node->details->online) {
             fprintf(state->stream, "standby (on-fail)\n");
         } else if (node->details->standby && node->details->online) {
 
             fprintf(state->stream, "standby%s\n",
                 node->details->running_rsc?" (with active resources)":"");
         } else if (node->details->standby) {
             fprintf(state->stream, "OFFLINE (standby)\n");
         } else if (node->details->maintenance && node->details->online) {
             fprintf(state->stream, "maintenance\n");
         } else if (node->details->maintenance) {
             fprintf(state->stream, "OFFLINE (maintenance)\n");
         } else if (node->details->online) {
             fprintf(state->stream, "online\n");
         } else {
             fprintf(state->stream, "OFFLINE\n");
         }
         if (is_set(mon_ops, mon_op_print_brief) && is_set(mon_ops, mon_op_group_by_node)) {
             fprintf(state->stream, "\n");
             print_rscs_brief(node->details->running_rsc, NULL, print_opts | pe_print_rsconly,
                              state->stream, FALSE);
             fprintf(state->stream, " \n");
 
         } else if (is_set(mon_ops, mon_op_group_by_node)) {
             GListPtr lpc2 = NULL;
 
             fprintf(state->stream, "\n");
             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
                 resource_t *rsc = (resource_t *) lpc2->data;
 
                 fprintf(state->stream, "- ");
                 rsc->fns->print(rsc, NULL, print_opts | pe_print_rsconly, state->stream);
                 fprintf(state->stream, "\n");
             }
             fprintf(state->stream, "
 \n");
         }
         fprintf(state->stream, "
\n");
         free(node_name);
     }
     fprintf(state->stream, "
\n");
 
     /* Print resources section, if needed */
     print_resources(state, data_set, print_opts, mon_ops);
 
     /* print Node Attributes section if requested */
     if (show & mon_show_attributes) {
         print_node_attributes(state, data_set, mon_ops);
     }
 
     /* If requested, print resource operations (which includes failcounts)
      * or just failcounts
      */
     if (show & (mon_show_operations | mon_show_failcounts)) {
         print_node_summary(state, data_set,
                            ((show & mon_show_operations)? TRUE : FALSE), mon_ops);
     }
 
     /* If there were any failed actions, print them */
     if (xml_has_children(data_set->failed)) {
         print_failed_actions(state, data_set);
     }
 
     /* Print failed stonith actions */
     if (is_set(mon_ops, mon_op_fence_history)) {
         print_failed_stonith_actions(state, stonith_history, mon_ops);
     }
 
     /* Print stonith history */
     if (is_set(mon_ops, mon_op_fence_history)) {
         if (show & mon_show_fence_history) {
             print_stonith_history(state, stonith_history, mon_ops);
         } else {
             print_stonith_pending(state, stonith_history, mon_ops);
         }
     }
 
     /* Print tickets if requested */
     if (show & mon_show_tickets) {
         print_cluster_tickets(state, data_set);
     }
 
     /* Print negative location constraints if requested */
     if (show & mon_show_bans) {
         print_neg_locations(state, data_set, mon_ops, prefix);
     }
 
     fprintf(state->stream, "\n");
     fprintf(state->stream, "\n");
     fflush(state->stream);
-    fclose(state->stream);
 
     if (state->output_format != mon_output_cgi) {
         if (rename(filename_tmp, filename) != 0) {
             crm_perror(LOG_ERR, "Unable to rename %s->%s", filename_tmp, filename);
         }
         free(filename_tmp);
     }
     return 0;
 }