diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index f57f3d52b3..104e6403c9 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,396 +1,399 @@
 /*
  * Copyright 2005-2018 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/param.h>
 #include <sys/types.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/ipc.h>
 #include <crm/cib.h>
 
 #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
                 "or apply such an output as a patch"
 
 struct {
     gboolean apply;
     gboolean as_cib;
     gboolean no_version;
     gboolean raw_1;
     gboolean raw_2;
     gboolean use_stdin;
     char *xml_file_1;
     char *xml_file_2;
 } options;
 
 gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 static GOptionEntry original_xml_entries[] = {
     { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
       "XML is contained in the named file",
       "FILE" },
     { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
       "XML is contained in the supplied string",
       "STRING" },
 
     { NULL }
 };
 
 static GOptionEntry operation_entries[] = {
     { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
       "Compare the original XML to the contents of the named file",
       "FILE" },
     { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
       "Compare the original XML with the contents of the supplied string",
       "STRING" },
     { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
       "Patch the original XML with the contents of the named file",
       "FILE" },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
       "Compare/patch the inputs as a CIB (includes versions details)",
       NULL },
     { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
       "",
       NULL },
     { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
       "Generate the difference without versions details",
       NULL },
 
     { NULL }
 };
 
 gboolean
 new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.raw_2 = TRUE;
 
     if (options.xml_file_2 != NULL) {
         free(options.xml_file_2);
     }
 
     options.xml_file_2 = strdup(optarg);
     return TRUE;
 }
 
 gboolean
 original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.raw_1 = TRUE;
 
     if (options.xml_file_1 != NULL) {
         free(options.xml_file_1);
     }
 
     options.xml_file_1 = strdup(optarg);
     return TRUE;
 }
 
 gboolean
 patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.apply = TRUE;
 
     if (options.xml_file_2 != NULL) {
         free(options.xml_file_2);
     }
 
     options.xml_file_2 = strdup(optarg);
     return TRUE;
 }
 
 static void
 print_patch(xmlNode *patch)
 {
     char *buffer = dump_xml_formatted(patch);
 
     printf("%s\n", crm_str(buffer));
     free(buffer);
     fflush(stdout);
 }
 
 static int
 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
 {
     int rc;
     xmlNode *output = copy_xml(input);
 
     rc = xml_apply_patchset(output, patch, as_cib);
     if (rc != pcmk_ok) {
         fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc));
         free_xml(output);
         return rc;
     }
 
     if (output != NULL) {
         const char *version;
         char *buffer;
 
         print_patch(output);
 
         version = crm_element_value(output, XML_ATTR_CRM_VERSION);
         buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
         crm_trace("Digest: %s\n", crm_str(buffer));
         free(buffer);
         free_xml(output);
     }
     return pcmk_ok;
 }
 
 static void
 log_patch_cib_versions(xmlNode *patch)
 {
     int add[] = { 0, 0, 0 };
     int del[] = { 0, 0, 0 };
 
     const char *fmt = NULL;
     const char *digest = NULL;
 
     xml_patch_versions(patch, add, del);
     fmt = crm_element_value(patch, "format");
     digest = crm_element_value(patch, XML_ATTR_DIGEST);
 
     if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
         crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
         crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
     }
 }
 
 static void
 strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields)
 {
     int format = 1;
 
     crm_element_value_int(patch, "format", &format);
     if (format == 2) {
         xmlNode *version_xml = find_xml_node(patch, "version", FALSE);
 
         if (version_xml) {
             free_xml(version_xml);
         }
 
     } else {
         int i = 0;
 
         const char *tags[] = {
             XML_TAG_DIFF_REMOVED,
             XML_TAG_DIFF_ADDED,
         };
 
         for (i = 0; i < DIMOF(tags); i++) {
             xmlNode *tmp = NULL;
             int lpc;
 
             tmp = find_xml_node(patch, tags[i], FALSE);
             if (tmp) {
                 for (lpc = 0; lpc < nvfields; lpc++) {
                     xml_remove_prop(tmp, vfields[lpc]);
                 }
 
                 tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
                 if (tmp) {
                     for (lpc = 0; lpc < nvfields; lpc++) {
                         xml_remove_prop(tmp, vfields[lpc]);
                     }
                 }
             }
         }
     }
 }
 
 static int
 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
                gboolean as_cib, gboolean no_version)
 {
     xmlNode *output = NULL;
 
     const char *vfields[] = {
         XML_ATTR_GENERATION_ADMIN,
         XML_ATTR_GENERATION,
         XML_ATTR_NUMUPDATES,
     };
 
     /* If we're ignoring the version, make the version information
      * identical, so it isn't detected as a change. */
     if (no_version) {
         int lpc;
 
         for (lpc = 0; lpc < DIMOF(vfields); lpc++) {
             crm_copy_xml_element(object_1, object_2, vfields[lpc]);
         }
     }
 
     xml_track_changes(object_2, NULL, object_2, FALSE);
     if(as_cib) {
         xml_calculate_significant_changes(object_1, object_2);
     } else {
         xml_calculate_changes(object_1, object_2);
     }
     crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
 
     output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
 
     xml_log_changes(LOG_INFO, __FUNCTION__, object_2);
     xml_accept_changes(object_2);
 
     if (output == NULL) {
         return pcmk_ok;
     }
 
     patchset_process_digest(output, object_1, object_2, as_cib);
 
     if (as_cib) {
         log_patch_cib_versions(output);
 
     } else if (no_version) {
         strip_patch_cib_version(output, vfields, DIMOF(vfields));
     }
 
     xml_log_patchset(LOG_NOTICE, __FUNCTION__, output);
     print_patch(output);
     free_xml(output);
     return -pcmk_err_generic;
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args) {
     GOptionContext *context = NULL;
 
     const char *description = "*Examples*\n\n"
                               "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
                               "\tcibadmin --query > cib-old.xml\n"
                               "\tcibadmin --query > cib-new.xml\n\n"
                               "Calculate and save the difference between the two files:\n\n"
                               "\tcrm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
                               "Apply the patch to the original file:\n\n"
                               "\tcrm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
                               "Apply the patch to the running cluster:\n\n"
                               "\tcibadmin --patch patch.xml\n";
 
     context = pcmk__build_arg_context(args, NULL, NULL);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "xml", "Original XML:",
                         "Show original XML options", original_xml_entries);
     pcmk__add_arg_group(context, "operation", "Operation:",
                         "Show operation options", operation_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_ok;
     xmlNode *object_1 = NULL;
     xmlNode *object_2 = NULL;
 
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 
     GError *error = NULL;
     GOptionContext *context = NULL;
     gchar **processed_args = NULL;
 
     context = build_arg_context(args);
 
     crm_log_cli_init("crm_diff");
 
     processed_args = pcmk__cmdline_preproc(argv, "nopNO");
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         rc = CRM_EX_USAGE;
         goto done;
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (args->version) {
         /* FIXME:  When crm_diff is converted to use formatted output, this can go. */
         pcmk__cli_help('v', CRM_EX_USAGE);
     }
 
     if (optind > argc) {
-        fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
+        fprintf(stderr, "%s", help);
+        free(help);
         rc = CRM_EX_USAGE;
         goto done;
     }
 
     if (options.apply && options.no_version) {
         fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
     } else if (options.as_cib && options.no_version) {
         fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
         rc = CRM_EX_USAGE;
         goto done;
     }
 
     if (options.raw_1) {
         object_1 = string2xml(options.xml_file_1);
 
     } else if (options.use_stdin) {
         fprintf(stderr, "Input first XML fragment:");
         object_1 = stdin2xml();
 
     } else if (options.xml_file_1 != NULL) {
         object_1 = filename2xml(options.xml_file_1);
     }
 
     if (options.raw_2) {
         object_2 = string2xml(options.xml_file_2);
 
     } else if (options.use_stdin) {
         fprintf(stderr, "Input second XML fragment:");
         object_2 = stdin2xml();
 
     } else if (options.xml_file_2 != NULL) {
         object_2 = filename2xml(options.xml_file_2);
     }
 
     if (object_1 == NULL) {
         fprintf(stderr, "Could not parse the first XML fragment\n");
         rc = CRM_EX_DATAERR;
         goto done;
     }
     if (object_2 == NULL) {
         fprintf(stderr, "Could not parse the second XML fragment\n");
         rc = CRM_EX_DATAERR;
         goto done;
     }
 
     if (options.apply) {
         rc = apply_patch(object_1, object_2, options.as_cib);
     } else {
         rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
     }
 
 done:
     g_strfreev(processed_args);
     g_clear_error(&error);
     pcmk__free_arg_context(context);
     free(options.xml_file_1);
     free(options.xml_file_2);
     free_xml(object_1);
     free_xml(object_2);
     return crm_errno2exit(rc);
 }
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index c4b4707bc1..510765a409 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2164 +1,2167 @@
 /*
  * Copyright 2004-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 
 #include <crm/crm.h>
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <signal.h>
 #include <sys/utsname.h>
 
 #include <crm/msg_xml.h>
 #include <crm/services.h>
 #include <crm/lrmd.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/curses_internal.h>
 #include <crm/common/internal.h>  // pcmk__ends_with_ext()
 #include <crm/common/ipc.h>
 #include <crm/common/iso8601_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/output.h>
 #include <crm/common/util.h>
 #include <crm/common/xml.h>
 
 #include <crm/cib/internal.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <pacemaker-internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 
 #include "crm_mon.h"
 
 #define SUMMARY "Provides a summary of cluster's current state.\n\n" \
                 "Outputs varying levels of detail in a number of different formats."
 
 /*
  * Definitions indicating which items to print
  */
 
 static unsigned int show;
 
 /*
  * Definitions indicating how to output
  */
 
 static mon_output_format_t output_format = mon_output_unset;
 
 /* 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 GError *error = NULL;
 static pcmk__common_args_t *args = NULL;
 static pcmk__output_t *out = NULL;
 static GOptionContext *context = NULL;
 static gchar **processed_args = NULL;
 
 static time_t last_refresh = 0;
 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,
     CRM_MON_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
 
 #define RECONNECT_MSECS 5000
 
 struct {
     int reconnect_msec;
     gboolean daemonize;
     gboolean show_bans;
     char *pid_file;
     char *external_agent;
     char *external_recipient;
     char *neg_location_prefix;
     unsigned int mon_ops;
     GSList *user_includes_excludes;
     GSList *includes_excludes;
 } options = {
     .reconnect_msec = RECONNECT_MSECS,
     .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 unsigned int
 all_includes(mon_output_format_t fmt) {
     if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
         return ~mon_show_options;
     } else {
         return mon_show_all;
     }
 }
 
 static unsigned int
 default_includes(mon_output_format_t fmt) {
     switch (fmt) {
         case mon_output_monitor:
         case mon_output_plain:
         case mon_output_console:
             return mon_show_stack | mon_show_dc | mon_show_times | mon_show_counts |
                    mon_show_nodes | mon_show_resources | mon_show_failures;
             break;
 
         case mon_output_xml:
         case mon_output_legacy_xml:
             return all_includes(fmt);
             break;
 
         case mon_output_html:
         case mon_output_cgi:
             return mon_show_summary | mon_show_nodes | mon_show_resources |
                    mon_show_failures;
             break;
 
         default:
             return 0;
             break;
     }
 }
 
 struct {
     const char *name;
     unsigned int bit;
 } sections[] = {
     { "attributes", mon_show_attributes },
     { "bans", mon_show_bans },
     { "counts", mon_show_counts },
     { "dc", mon_show_dc },
     { "failcounts", mon_show_failcounts },
     { "failures", mon_show_failures },
     { "fencing", mon_show_fencing_all },
     { "fencing-failed", mon_show_fence_failed },
     { "fencing-pending", mon_show_fence_pending },
     { "fencing-succeeded", mon_show_fence_worked },
     { "nodes", mon_show_nodes },
     { "operations", mon_show_operations },
     { "options", mon_show_options },
     { "resources", mon_show_resources },
     { "stack", mon_show_stack },
     { "summary", mon_show_summary },
     { "tickets", mon_show_tickets },
     { "times", mon_show_times },
     { NULL }
 };
 
 static unsigned int
 find_section_bit(const char *name) {
     for (int i = 0; sections[i].name != NULL; i++) {
         if (crm_str_eq(sections[i].name, name, FALSE)) {
             return sections[i].bit;
         }
     }
 
     return 0;
 }
 
 static gboolean
 apply_exclude(const gchar *excludes, GError **error) {
     char **parts = NULL;
 
     parts = g_strsplit(excludes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         unsigned int bit = find_section_bit(*s);
 
         if (crm_str_eq(*s, "all", TRUE)) {
             show = 0;
         } else if (crm_str_eq(*s, "none", TRUE)) {
             show = all_includes(output_format);
         } else if (bit != 0) {
             show &= ~bit;
         } else {
             g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
                         "--exclude options: all, attributes, bans, counts, dc, "
                         "failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, nodes, none, "
                         "operations, options, resources, stack, summary, "
                         "tickets, times");
             return FALSE;
         }
     }
     g_strfreev(parts);
 
     return TRUE;
 }
 
 static gboolean
 apply_include(const gchar *includes, GError **error) {
     char **parts = NULL;
 
     parts = g_strsplit(includes, ",", 0);
     for (char **s = parts; *s != NULL; s++) {
         unsigned int bit = find_section_bit(*s);
 
         if (crm_str_eq(*s, "all", TRUE)) {
             show = all_includes(output_format);
         } else if (pcmk__starts_with(*s, "bans")) {
             show |= mon_show_bans;
             if (options.neg_location_prefix != NULL) {
                 free(options.neg_location_prefix);
                 options.neg_location_prefix = NULL;
             }
 
             if (strlen(*s) > 4 && (*s)[4] == ':') {
                 options.neg_location_prefix = strdup(*s+5);
             }
         } else if (crm_str_eq(*s, "default", TRUE) || crm_str_eq(*s, "defaults", TRUE)) {
             show |= default_includes(output_format);
         } else if (crm_str_eq(*s, "none", TRUE)) {
             show = 0;
         } else if (bit != 0) {
             show |= bit;
         } else {
             g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
                         "--include options: all, attributes, bans[:PREFIX], counts, dc, "
                         "default, failcounts, failures, fencing, fencing-failed, "
                         "fencing-pending, fencing-succeeded, nodes, none, operations, "
                         "options, resources, stack, summary, tickets, times");
             return FALSE;
         }
     }
     g_strfreev(parts);
 
     return TRUE;
 }
 
 static gboolean
 apply_include_exclude(GSList *lst, mon_output_format_t fmt, GError **error) {
     gboolean rc = TRUE;
     GSList *node = lst;
 
     /* Set the default of what to display here.  Note that we OR everything to
      * show instead of set show directly because it could have already had some
      * settings applied to it in main.
      */
     show |= default_includes(fmt);
 
     while (node != NULL) {
         char *s = node->data;
 
         if (pcmk__starts_with(s, "--include=")) {
             rc = apply_include(s+10, error);
         } else if (pcmk__starts_with(s, "-I=")) {
             rc = apply_include(s+3, error);
         } else if (pcmk__starts_with(s, "--exclude=")) {
             rc = apply_exclude(s+10, error);
         } else if (pcmk__starts_with(s, "-U=")) {
             rc = apply_exclude(s+3, error);
         }
 
         if (rc != TRUE) {
             break;
         }
 
         node = node->next;
     }
 
     return rc;
 }
 
 static gboolean
 user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 
     options.includes_excludes = g_slist_append(options.includes_excludes, s);
     return TRUE;
 }
 
 static gboolean
 as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     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 **err) {
     if (args->output_ty != NULL) {
         free(args->output_ty);
     }
 
     if (args->output_dest != NULL) {
         free(args->output_dest);
     }
 
     if (optarg != NULL) {
         args->output_dest = strdup(optarg);
     }
 
     args->output_ty = strdup("html");
     output_format = mon_output_html;
     umask(S_IWGRP | S_IWOTH);
     return TRUE;
 }
 
 static gboolean
 as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     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 **err) {
     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 **err) {
     int rc = crm_atoi(optarg, "2");
 
     switch (rc) {
         case 3:
             options.mon_ops |= mon_op_fence_full_history | mon_op_fence_history | mon_op_fence_connect;
             return include_exclude_cb("--include", "fencing", data, err);
 
         case 2:
             options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
             return include_exclude_cb("--include", "fencing", data, err);
 
         case 1:
             options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
             return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
 
         case 0:
             options.mon_ops &= ~(mon_op_fence_history | mon_op_fence_connect);
             return include_exclude_cb("--exclude", "fencing", data, err);
 
         default:
             g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
             return FALSE;
     }
 }
 
 static gboolean
 group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     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 **err) {
     return include_exclude_cb("--exclude", "summary", data, err);
 }
 
 static gboolean
 inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     options.mon_ops |= mon_op_inactive_resources;
     return TRUE;
 }
 
 static gboolean
 no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     output_format = mon_output_plain;
     return TRUE;
 }
 
 static gboolean
 one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     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 **err) {
     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 **err) {
     options.mon_ops |= mon_op_print_pending;
     return TRUE;
 }
 
 static gboolean
 print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     options.mon_ops |= mon_op_print_timing;
     return include_exclude_cb("--include", "operations", data, err);
 }
 
 static gboolean
 reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     int rc = crm_get_msec(optarg);
 
     if (rc == -1) {
         g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
         return FALSE;
     } else {
         options.reconnect_msec = crm_parse_interval_spec(optarg);
     }
 
     return TRUE;
 }
 
 static gboolean
 show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "attributes", data, err);
 }
 
 static gboolean
 show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     if (optarg != NULL) {
         char *s = crm_strdup_printf("bans:%s", optarg);
         gboolean rc = include_exclude_cb("--include", s, data, err);
         free(s);
         return rc;
     } else {
         return include_exclude_cb("--include", "bans", data, err);
     }
 }
 
 static gboolean
 show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "failcounts", data, err);
 }
 
 static gboolean
 show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "failcounts,operations", data, err);
 }
 
 static gboolean
 show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     return include_exclude_cb("--include", "tickets", data, err);
 }
 
 static gboolean
 use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     setenv("CIB_file", optarg, 1);
     options.mon_ops |= mon_op_one_shot;
     return TRUE;
 }
 
 static gboolean
 watch_fencing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     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 (default is 5 seconds)",
       "TIMESPEC" },
 
     { "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 },
 
     { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
       "Run in the background as a daemon.\n"
       INDENT "Requires at least one of --output-to and --external-agent.",
       NULL },
 
     { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file,
       "(Advanced) Daemon pid file location",
       "FILE" },
 
     { "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent,
       "A program to run when resource operations take place",
       "FILE" },
 
     { "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient,
       "A recipient for your program (assuming you want the program to send something to someone).",
       "RCPT" },
 
     { "watch-fencing", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, watch_fencing_cb,
       "Listen for fencing events. For use with --external-agent.",
       NULL },
 
     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
       NULL,
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry display_entries[] = {
     { "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to include in the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
       "A list of sections to exclude from the output.\n"
       INDENT "See `Output Control` help for more information.",
       "SECTION(s)" },
 
     { "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb,
       "Group resources by node",
       NULL },
 
     { "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb,
       "Display inactive resources",
       NULL },
 
     { "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb,
       "Display resource fail counts",
       NULL },
 
     { "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb,
       "Display resource operation history",
       NULL },
 
     { "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb,
       "Display resource operation history with timing details",
       NULL },
 
     { "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb,
       "Display cluster tickets",
       NULL },
 
     { "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb,
       "Show fence history:\n"
       INDENT "0=off, 1=failures and pending (default without option),\n"
       INDENT "2=add successes (default without value for option),\n"
       INDENT "3=show full history without reduction to most recent of each flavor",
       "LEVEL" },
 
     { "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb,
       "Display negative location constraints [optionally filtered by id prefix]",
       NULL },
 
     { "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb,
       "Display node attributes",
       NULL },
 
     { "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb,
       "Hide all headers",
       NULL },
 
     { "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_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 },
 
     { "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_simple_cb,
       "Display the cluster status once as a simple one line output (suitable for nagios)",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry deprecated_entries[] = {
     { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
       "Write cluster status to the named HTML file.\n"
       INDENT "Use --output-as=html --output-to=FILE instead.",
       "FILE" },
 
     { "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb,
       "Write cluster status as XML to stdout. This will enable one-shot mode.\n"
       INDENT "Use --output-as=xml instead.",
       NULL },
 
     { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
       "Disable the use of ncurses.\n"
       INDENT "Use --output-as=text instead.",
       NULL },
 
     { "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb,
       "Web mode with output suitable for CGI (preselected when run as *.cgi).\n"
       INDENT "Use --output-as=html --html-cgi instead.",
       NULL },
 
     { NULL }
 };
 /* *INDENT-ON* */
 
 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)
 {
     out->err(out, "Connection to the cluster-daemons terminated");
     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, &current_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) {
                 out->err(out, "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)
 {
     const char *desc = "No help available";
 
     for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
         if (entry->short_name == c) {
             desc = entry->description;
             break;
         }
     }
     return desc;
 }
 
 #define print_option_help(output_format, option, condition) \
     out->info(out, "%c %c: \t%s", ((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 (is_not_set(show, mon_show_fencing_all)) {
                     options.mon_ops |= mon_op_fence_history;
                     options.mon_ops |= mon_op_fence_connect;
                     if (st == NULL) {
                         mon_cib_connection_destroy(NULL);
                     }
                 }
 
                 if (is_set(show, mon_show_fence_failed) || is_set(show, mon_show_fence_pending) ||
                     is_set(show, mon_show_fence_worked)) {
                     show &= ~mon_show_fencing_all;
                 } else {
                     show |= mon_show_fencing_all;
                 }
 
                 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 (is_not_set(show, mon_show_operations)) {
                     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 (is_set(show, mon_show_stack) || is_set(show, mon_show_dc) ||
                     is_set(show, mon_show_times) || is_set(show, mon_show_counts)) {
                     show &= ~mon_show_summary;
                 } else {
                     show |= mon_show_summary;
                 }
                 /* Regardless, we don't show options in console mode. */
                 show &= ~mon_show_options;
                 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();
 
         out->info(out, "%s", "Display option change mode\n");
         print_option_help(out, 'c', is_set(show, mon_show_tickets));
         print_option_help(out, 'f', is_set(show, mon_show_failcounts));
         print_option_help(out, 'n', is_set(options.mon_ops, mon_op_group_by_node));
         print_option_help(out, 'o', is_set(show, mon_show_operations));
         print_option_help(out, 'r', is_set(options.mon_ops, mon_op_inactive_resources));
         print_option_help(out, 't', is_set(options.mon_ops, mon_op_print_timing));
         print_option_help(out, 'A', is_set(show, mon_show_attributes));
         print_option_help(out, 'L', is_set(show,mon_show_bans));
         print_option_help(out, 'D', is_not_set(show, mon_show_summary));
         print_option_help(out, 'R', is_set(options.mon_ops, mon_op_print_clone_detail));
         print_option_help(out, 'b', is_set(options.mon_ops, mon_op_print_brief));
         print_option_help(out, 'j', is_set(options.mon_ops, mon_op_print_pending));
         print_option_help(out, 'm', is_set(show, mon_show_fencing_all));
         out->info(out, "%s", "\nToggle 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, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     const char *description = "Notes:\n\n"
                               "If this program is called as crm_mon.cgi, --output-as=html --html-cgi will\n"
                               "automatically be added to the command line arguments.\n\n"
                               "Time Specification:\n\n"
                               "The TIMESPEC in any command line option can be specified in many different\n"
                               "formats.  It can be just an integer number of seconds, a number plus units\n"
                               "(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n"
                               "Output Control:\n\n"
                               "By default, a certain list of sections are written to the output destination.\n"
                               "The default varies based on the output format - XML includes everything, while\n"
                               "other output formats will display less.  This list can be modified with the\n"
                               "--include and --exclude command line options.  Each option may be given multiple\n"
                               "times on the command line, and each can give a comma-separated list of sections.\n"
                               "The options are applied to the default set, from left to right as seen on the\n"
                               "command line.  For a list of valid sections, pass --include=list or --exclude=list.\n\n"
                               "Examples:\n\n"
                               "Display the cluster status on the console with updates as they occur:\n\n"
                               "\tcrm_mon\n\n"
                               "Display the cluster status on the console just once then exit:\n\n"
                               "\tcrm_mon -1\n\n"
                               "Display your cluster status, group resources by node, and include inactive resources in the list:\n\n"
                               "\tcrm_mon --group-by-node --inactive\n\n"
                               "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:\n\n"
                               "\tcrm_mon --daemonize --output-as html --output-to /path/to/docroot/filename.html\n\n"
                               "Start crm_mon and export the current cluster status as XML to stdout, then exit:\n\n"
                               "\tcrm_mon --output-as xml\n\n";
 
     context = pcmk__build_arg_context(args, "console (default), html, text, xml", group);
     pcmk__add_main_args(context, extra_prog_entries);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "display", "Display Options:",
                         "Show display options", display_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
                         "Show deprecated options", deprecated_entries);
 
     return context;
 }
 
 /* If certain format options were specified, we want to set some extra
  * options.  We can just process these like they were given on the
  * command line.
  */
 static void
 add_output_args() {
     GError *err = NULL;
 
     if (output_format == mon_output_plain) {
         if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_html) {
         if (!pcmk__force_args(context, &err, "%s --html-title \"Cluster Status\"",
                               g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_cgi) {
         if (!pcmk__force_args(context, &err, "%s --html-cgi --html-title \"Cluster Status\"", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_xml) {
         if (!pcmk__force_args(context, &err, "%s --xml-simple-list", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     } else if (output_format == mon_output_legacy_xml) {
         output_format = mon_output_xml;
         if (!pcmk__force_args(context, &err, "%s --xml-legacy", g_get_prgname())) {
             g_propagate_error(&error, err);
             clean_up(CRM_EX_USAGE);
         }
     }
 }
 
 /* Which output format to use could come from two places:  The --as-xml
  * style arguments we gave in deprecated_entries above, or the formatted output
  * arguments added by pcmk__register_formats.  If the latter were used,
  * output_format will be mon_output_unset.
  *
  * Call the callbacks as if those older style arguments were provided so
  * the various things they do get done.
  */
 static void
 reconcile_output_format(pcmk__common_args_t *args) {
     gboolean retval = TRUE;
     GError *err = NULL;
 
     if (output_format != mon_output_unset) {
         return;
     }
 
     if (safe_str_eq(args->output_ty, "html")) {
         char *dest = NULL;
 
         if (args->output_dest != NULL) {
             dest = strdup(args->output_dest);
         }
 
         retval = as_html_cb("h", dest, NULL, &err);
         free(dest);
     } else if (safe_str_eq(args->output_ty, "text")) {
         retval = no_curses_cb("N", NULL, NULL, &err);
     } 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 if (is_set(options.mon_ops, mon_op_one_shot)) {
         if (args->output_ty != NULL) {
             free(args->output_ty);
         }
 
         args->output_ty = strdup("text");
         output_format = mon_output_plain;
     } 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) {
         g_propagate_error(&error, err);
         clean_up(CRM_EX_USAGE);
     }
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = pcmk_ok;
     GOptionGroup *output_group = NULL;
 
     args = pcmk__new_common_args(SUMMARY);
     context = build_arg_context(args, &output_group);
     pcmk__register_formats(output_group, formats);
 
     options.pid_file = strdup("/tmp/ClusterMon.pid");
     crm_log_cli_init("crm_mon");
 
     // Avoid needing to wait for subprocesses forked for -E/--external-agent
     avoid_zombies();
 
     if (pcmk__ends_with_ext(argv[0], ".cgi")) {
         output_format = mon_output_cgi;
         options.mon_ops |= mon_op_one_shot;
     }
 
     processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
 
     fence_history_cb("--fence-history", "1", NULL, NULL);
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (!args->version) {
         if (args->quiet) {
             include_exclude_cb("--exclude", "times", NULL, NULL);
         }
 
         if (is_set(options.mon_ops, mon_op_watch_fencing)) {
             fence_history_cb("--fence-history", "0", NULL, NULL);
             options.mon_ops |= mon_op_fence_connect;
         }
 
         /* 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. */
                     fence_history_cb("--fence-history", "0", NULL, NULL);
                     options.mon_ops |= mon_op_one_shot;
                     break;
 
                 case cib_remote:
                     /* updates coming in but no fencing */
                     fence_history_cb("--fence-history", "0", NULL, NULL);
                     break;
 
                 case cib_undefined:
                 case cib_database:
                 default:
                     /* something is odd */
                     rc = -EINVAL;
                     break;
             }
         }
 
         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 ((args->output_dest == NULL || safe_str_eq(args->output_dest, "-")) && !options.external_agent) {
                 g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "--daemonize requires at least one of --output-to and --external-agent");
                 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
             crm_enable_stderr(FALSE);
 #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
         }
     }
 
     if (rc != pcmk_ok) {
         // Shouldn't really be possible
         g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "Invalid CIB source");
         return clean_up(CRM_EX_ERROR);
     }
 
     reconcile_output_format(args);
     add_output_args();
 
     if (args->version && output_format == mon_output_console) {
         /* Use the text output format here if we are in curses mode but were given
          * --version.  Displaying version information uses printf, and then we
          *  immediately exit.  We don't want to initialize curses for that.
          */
         rc = pcmk__output_new(&out, "text", args->output_dest, argv);
     } else {
         rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     }
 
     if (rc != pcmk_rc_ok) {
         g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
                     args->output_ty, pcmk_rc_str(rc));
         return clean_up(CRM_EX_ERROR);
     }
 
     /* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
 
     /* Apply --include/--exclude flags we used internally.  There's no error reporting
      * here because this would be a programming error.
      */
     apply_include_exclude(options.includes_excludes, output_format, &error);
 
     /* And now apply any --include/--exclude flags the user gave on the command line.
      * These are done in a separate pass from the internal ones because we want to
      * make sure whatever the user specifies overrides whatever we do.
      */
     if (!apply_include_exclude(options.user_includes_excludes, output_format, &error)) {
         return clean_up(CRM_EX_USAGE);
     }
 
     crm_mon_register_messages(out);
     pe__register_messages(out);
     stonith__register_messages(out);
 
     if (args->version) {
         out->version(out, false);
         return clean_up(CRM_EX_OK);
     }
 
     /* Extra sanity checks when in CGI mode */
     if (output_format == mon_output_cgi) {
         if (cib && cib->variant == cib_file) {
             g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
             return clean_up(CRM_EX_USAGE);
         } else if (options.external_agent != NULL) {
             g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
             return clean_up(CRM_EX_USAGE);
         } else if (options.daemonize == TRUE) {
             g_set_error(&error, G_OPTION_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
             return clean_up(CRM_EX_USAGE);
         }
     }
 
     if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) {
         options.mon_ops |= mon_op_print_timing;
     }
 
     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 && out->dest != stdout) {
                     printf("Writing html to %s ...\n", args->output_dest);
                 }
             }
 
         } while (rc == -ENOTCONN);
     }
 
     if (rc != pcmk_ok) {
         if (output_format == mon_output_monitor) {
             g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
                         pcmk_strerror(rc));
             return clean_up(MON_STATUS_CRIT);
         } else {
             if (rc == -ENOTCONN) {
                 g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "\nError: cluster is not available on this node");
             } else {
                 g_set_error(&error, G_OPTION_ERROR, CRM_EX_ERROR, "\nConnection to cluster failed: %s", 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);
 }
 
 /*!
  * \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(pcmk__output_t *out, pe_working_set_t * data_set,
                     stonith_history_t *history, unsigned int mon_ops)
 {
     GListPtr gIter = NULL;
     int nodes_online = 0;
     int nodes_standby = 0;
     int nodes_maintenance = 0;
     char *offline_nodes = NULL;
     gboolean no_dc = FALSE;
     gboolean offline = FALSE;
 
     if (data_set->dc_node == NULL) {
         mon_ops |= mon_op_has_warnings;
         no_dc = TRUE;
     }
 
     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
         pe_node_t *node = (pe_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 {
             char *s = crm_strdup_printf("offline node: %s", node->details->uname);
             offline_nodes = pcmk__add_word(offline_nodes, s);
             free(s);
             mon_ops |= mon_op_has_warnings;
             offline = TRUE;
         }
     }
 
     if (is_set(mon_ops, mon_op_has_warnings)) {
         out->info(out, "CLUSTER WARN:%s%s%s",
                   no_dc ? " No DC" : "",
                   no_dc && offline ? "," : "",
                   offline ? offline_nodes : "");
         free(offline_nodes);
     } else {
         char *nodes_standby_s = NULL;
         char *nodes_maint_s = NULL;
 
         if (nodes_standby > 0) {
             nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
                                                 pcmk__plural_s(nodes_standby));
         }
 
         if (nodes_maintenance > 0) {
             nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
                                               nodes_maintenance,
                                               pcmk__plural_s(nodes_maintenance));
         }
 
         out->info(out, "CLUSTER OK: %d node%s online%s%s, "
                        "%d resource instance%s configured",
                   nodes_online, pcmk__plural_s(nodes_online),
                   nodes_standby_s != NULL ? nodes_standby_s : "",
                   nodes_maint_s != NULL ? nodes_maint_s : "",
                   data_set->ninstances, pcmk__plural_s(data_set->ninstances));
 
         free(nodes_standby_s);
         free(nodes_maint_s);
     }
 }
 
 /*!
  * \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;
 }
 
 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_element(status); state != NULL;
                  state = __xml_next_element(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_element(match); state != NULL;
                  state = __xml_next_element(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, &current_cib, cib_scope_local | cib_sync_call);
     }
 
     if (options.external_agent) {
         int format = 0;
         crm_element_value_int(diff, "format", &format);
         switch(format) {
             case 1:
                 crm_diff_update_v1(event, msg);
                 break;
             case 2:
                 crm_diff_update_v2(event, msg);
                 break;
             default:
                 crm_err("Unknown patch format: %d", format);
         }
     }
 
     if (current_cib == NULL) {
         if(!stale) {
             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;
     int history_rc = 0;
 
     last_refresh = time(NULL);
 
     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
         if (cib) {
             cib->cmds->signoff(cib);
         }
         out->err(out, "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) {
             history_rc = st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120);
 
             if (history_rc != 0) {
                 out->err(out, "Critical: Unable to get stonith-history");
                 mon_cib_connection_destroy(NULL);
             } else {
                 stonith_history = stonith__sort_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 {
             out->err(out, "Critical: No stonith-API");
         }
         free_xml(cib_copy);
         out->err(out, "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);
     }
     set_bit(mon_data_set->flags, pe_flag_no_compat);
 
     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 (is_set(show, mon_show_bans) || is_set(show, 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(out, mon_data_set, stonith_history, options.mon_ops,
                                   show, options.neg_location_prefix) != 0) {
                 g_set_error(&error, G_OPTION_ERROR, CRM_EX_CANTCREAT, "Critical: Unable to output html file");
                 clean_up(CRM_EX_CANTCREAT);
                 return FALSE;
             }
             break;
 
         case mon_output_legacy_xml:
         case mon_output_xml:
             print_xml_status(out, mon_data_set, crm_errno2exit(history_rc),
                              stonith_history, options.mon_ops, show,
                              options.neg_location_prefix);
             break;
 
         case mon_output_monitor:
             print_simple_status(out, mon_data_set, stonith_history, options.mon_ops);
             if (is_set(options.mon_ops, mon_op_has_warnings)) {
                 clean_up(MON_STATUS_WARN);
                 return FALSE;
             }
             break;
 
         case mon_output_console:
             /* If curses is not enabled, this will just fall through to the plain
              * text case.
              */
 #if CURSES_ENABLED
             blank_screen();
             print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
                          options.neg_location_prefix);
             refresh();
             break;
 #endif
 
         case mon_output_plain:
             print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
                          options.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;
     time_t now = time(NULL);
 
     if (data_updated) {
         updates++;
     }
 
     if(refresh_timer == NULL) {
         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
     }
 
     /* 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;
     }
 }
 
 static void
 handle_html_output(crm_exit_t exit_code) {
     xmlNodePtr html = NULL;
 
     pcmk__html_add_header(html, "meta", "http-equiv", "refresh", "content",
                           crm_itoa(options.reconnect_msec/1000), NULL);
     out->finish(out, exit_code, true, (void **) &html);
 }
 
 /*
  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
  * deallocate memory and show usage-message if requested.
  *
  * We don't actually return, but nominally returning crm_exit_t allows a usage
  * like "return clean_up(exit_code);" which helps static analysis understand the
  * code flow.
  */
 static crm_exit_t
 clean_up(crm_exit_t exit_code)
 {
     /* Quitting crm_mon is much more complicated than it ought to be. */
 
     /* (1) Close connections, free things, etc. */
     clean_up_connections();
     free(options.pid_file);
     free(options.neg_location_prefix);
     g_slist_free_full(options.includes_excludes, free);
 
     pe_free_working_set(mon_data_set);
     mon_data_set = NULL;
 
     g_strfreev(processed_args);
 
     /* (2) If this is abnormal termination and we're in curses mode, shut down
      * curses first.  Any messages displayed to the screen before curses is shut
      * down will be lost because doing the shut down will also restore the
      * screen to whatever it looked like before crm_mon was started.
      */
     if ((error != NULL || exit_code == CRM_EX_USAGE) && output_format == mon_output_console) {
         out->finish(out, exit_code, false, NULL);
         pcmk__output_free(out);
         out = NULL;
     }
 
     /* (3) If this is a command line usage related failure, print the usage
      * message.
      */
     if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
-        fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
+        fprintf(stderr, "%s", help);
+        free(help);
     }
 
     pcmk__free_arg_context(context);
 
     /* (4) If this is any kind of error, print the error out and exit.  Make
      * sure to handle situations both before and after formatted output is
      * set up.  We want errors to appear formatted if at all possible.
      */
     if (error != NULL) {
         if (out != NULL) {
             out->err(out, "%s: %s\n", g_get_prgname(), error->message);
             out->finish(out, exit_code, true, NULL);
             pcmk__output_free(out);
         } else {
             fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         }
 
         g_clear_error(&error);
         crm_exit(exit_code);
     }
 
     /* (5) Print formatted output to the screen if we made it far enough in
      * crm_mon to be able to do so.
      */
     if (out != NULL) {
         switch (output_format) {
             case mon_output_cgi:
             case mon_output_html:
                 handle_html_output(exit_code);
                 break;
 
             default:
                 out->finish(out, exit_code, true, NULL);
                 break;
         }
 
         pcmk__output_free(out);
         pcmk__unregister_formats();
     }
 
     crm_exit(exit_code);
 }
diff --git a/tools/crm_node.c b/tools/crm_node.c
index 3fb9856fac..ca8372c7b9 100644
--- a/tools/crm_node.c
+++ b/tools/crm_node.c
@@ -1,665 +1,668 @@
 /*
  * Copyright 2004-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/types.h>
 
 #include <crm/crm.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/msg_xml.h>
 #include <crm/cib.h>
 #include <crm/common/attrd_internal.h>
 
 #define SUMMARY "crm_node - Tool for displaying low-level node information"
 
 struct {
     gboolean corosync;
     gboolean dangerous_cmd;
     gboolean force_flag;
     char command;
     int nodeid;
     char *target_uname;
 } options = {
     .command = '\0',
     .force_flag = FALSE
 };
 
 gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 static char *pid_s = NULL;
 static GMainLoop *mainloop = NULL;
 static crm_exit_t exit_code = CRM_EX_OK;
 
 #define INDENT "                           "
 
 static GOptionEntry command_entries[] = {
     { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Display this node's cluster id",
       NULL },
     { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Display all known members (past and present) of this cluster",
       NULL },
     { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Display the name used by the cluster for this node",
       NULL },
     { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Display the members of this partition",
       NULL },
     { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
       "Display a 1 if our partition has quorum, 0 if not",
       NULL },
     { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb,
       "Display the name used by the cluster for the node with the specified ID",
       "ID" },
     { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb,
       "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n"
       INDENT "configuration and caches (the node must already have been removed from\n"
       INDENT "the underlying cluster stack configuration",
       "NAME" },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag,
       NULL,
       NULL },
 #if SUPPORT_COROSYNC
     /* Unused and deprecated */
     { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync,
       NULL,
       NULL },
 #endif
 
     // @TODO add timeout option for when IPC replies are needed
 
     { NULL }
 };
 
 gboolean
 command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (safe_str_eq("-i", option_name) || safe_str_eq("--cluster-id", option_name)) {
         options.command = 'i';
     } else if (safe_str_eq("-l", option_name) || safe_str_eq("--list", option_name)) {
         options.command = 'l';
     } else if (safe_str_eq("-n", option_name) || safe_str_eq("--name", option_name)) {
         options.command = 'n';
     } else if (safe_str_eq("-p", option_name) || safe_str_eq("--partition", option_name)) {
         options.command = 'p';
     } else if (safe_str_eq("-q", option_name) || safe_str_eq("--quorum", option_name)) {
         options.command = 'q';
     } else {
         g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s\n", option_name);
         return FALSE;
     }
 
     return TRUE;
 }
 
 gboolean
 name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.command = 'N';
     options.nodeid = crm_parse_int(optarg, NULL);
     return TRUE;
 }
 
 gboolean
 remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (optarg == NULL) {
         crm_err("-R option requires an argument");
         g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument");
         return FALSE;
     }
 
     options.command = 'R';
     options.dangerous_cmd = TRUE;
     options.target_uname = strdup(optarg);
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Exit crm_node
  * Clean up memory, and either quit mainloop (if running) or exit
  *
  * \param[in] value  Exit status
  */
 static void
 crm_node_exit(crm_exit_t value)
 {
     if (pid_s) {
         free(pid_s);
         pid_s = NULL;
     }
 
     exit_code = value;
 
     if (mainloop && g_main_loop_is_running(mainloop)) {
         g_main_loop_quit(mainloop);
     } else {
         crm_exit(exit_code);
     }
 }
 
 static void
 exit_disconnect(gpointer user_data)
 {
     fprintf(stderr, "error: Lost connection to cluster\n");
     crm_node_exit(CRM_EX_DISCONNECT);
 }
 
 typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length,
                                 gpointer userdata);
 
 static crm_ipc_t *
 new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch)
 {
     mainloop_io_t *source = NULL;
     crm_ipc_t *ipc = NULL;
 
     struct ipc_client_callbacks ipc_callbacks = {
         .dispatch = dispatch,
         .destroy = exit_disconnect
     };
 
     mainloop = g_main_loop_new(NULL, FALSE);
     source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0,
                                      NULL, &ipc_callbacks);
     ipc = mainloop_get_ipc_client(source);
     if (ipc == NULL) {
         fprintf(stderr,
                 "error: Could not connect to cluster (is it running?)\n");
         crm_node_exit(CRM_EX_DISCONNECT);
     }
     return ipc;
 }
 
 static void
 run_mainloop_and_exit()
 {
     g_main_loop_run(mainloop);
     g_main_loop_unref(mainloop);
     mainloop = NULL;
     crm_node_exit(exit_code);
 }
 
 static int
 send_controller_hello(crm_ipc_t *controller)
 {
     xmlNode *hello = NULL;
     int rc;
 
     pid_s = pcmk__getpid_s();
     hello = create_hello_message(pid_s, crm_system_name, "1", "0");
     rc = crm_ipc_send(controller, hello, 0, 0, NULL);
     free_xml(hello);
     return (rc < 0)? rc : 0;
 }
 
 static int
 send_node_info_request(crm_ipc_t *controller, uint32_t nodeid)
 {
     xmlNode *ping = NULL;
     int rc;
 
     ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD,
                           crm_system_name, pid_s);
     if (nodeid > 0) {
         crm_xml_add_int(ping, XML_ATTR_ID, nodeid);
     }
     rc = crm_ipc_send(controller, ping, 0, 0, NULL);
     free_xml(ping);
     return (rc < 0)? rc : 0;
 }
 
 static int
 dispatch_controller(const char *buffer, ssize_t length, gpointer userdata)
 {
     xmlNode *message = string2xml(buffer);
     xmlNode *data = NULL;
     const char *value = NULL;
 
     if (message == NULL) {
         fprintf(stderr, "error: Could not understand reply from controller\n");
         crm_node_exit(CRM_EX_PROTOCOL);
         return 0;
     }
     crm_log_xml_trace(message, "controller reply");
 
     exit_code = CRM_EX_PROTOCOL;
 
     // Validate reply
     value = crm_element_value(message, F_CRM_MSG_TYPE);
     if (safe_str_neq(value, XML_ATTR_RESPONSE)) {
         fprintf(stderr, "error: Message from controller was not a reply\n");
         goto done;
     }
     value = crm_element_value(message, XML_ATTR_REFERENCE);
     if (value == NULL) {
         fprintf(stderr, "error: Controller reply did not specify original message\n");
         goto done;
     }
     data = get_message_xml(message, F_CRM_DATA);
     if (data == NULL) {
         fprintf(stderr, "error: Controller reply did not contain any data\n");
         goto done;
     }
 
     switch (options.command) {
         case 'i':
             value = crm_element_value(data, XML_ATTR_ID);
             if (value == NULL) {
                 fprintf(stderr, "error: Controller reply did not contain node ID\n");
             } else {
                 printf("%s\n", value);
                 exit_code = CRM_EX_OK;
             }
             break;
 
         case 'n':
         case 'N':
             value = crm_element_value(data, XML_ATTR_UNAME);
             if (value == NULL) {
                 fprintf(stderr, "Node is not known to cluster\n");
                 exit_code = CRM_EX_NOHOST;
             } else {
                 printf("%s\n", value);
                 exit_code = CRM_EX_OK;
             }
             break;
 
         case 'q':
             value = crm_element_value(data, XML_ATTR_HAVE_QUORUM);
             if (value == NULL) {
                 fprintf(stderr, "error: Controller reply did not contain quorum status\n");
             } else {
                 bool quorum = crm_is_true(value);
 
                 printf("%d\n", quorum);
                 exit_code = quorum? CRM_EX_OK : CRM_EX_QUORUM;
             }
             break;
 
         default:
             fprintf(stderr, "internal error: Controller reply not expected\n");
             exit_code = CRM_EX_SOFTWARE;
             break;
     }
 
 done:
     free_xml(message);
     crm_node_exit(exit_code);
     return 0;
 }
 
 static void
 run_controller_mainloop(uint32_t nodeid)
 {
     crm_ipc_t *controller = NULL;
     int rc;
 
     controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller);
 
     rc = send_controller_hello(controller);
     if (rc < 0) {
         fprintf(stderr, "error: Could not register with controller: %s\n",
                 pcmk_strerror(rc));
         crm_node_exit(crm_errno2exit(rc));
     }
 
     rc = send_node_info_request(controller, nodeid);
     if (rc < 0) {
         fprintf(stderr, "error: Could not ping controller: %s\n",
                 pcmk_strerror(rc));
         crm_node_exit(crm_errno2exit(rc));
     }
 
     // Run main loop to get controller reply via dispatch_controller()
     run_mainloop_and_exit();
 }
 
 static void
 print_node_name()
 {
     // Check environment first (i.e. when called by resource agent)
     const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET);
 
     if (name != NULL) {
         printf("%s\n", name);
         crm_node_exit(CRM_EX_OK);
 
     } else {
         // Otherwise ask the controller
         run_controller_mainloop(0);
     }
 }
 
 static int
 cib_remove_node(long id, const char *name)
 {
     int rc;
     cib_t *cib = NULL;
     xmlNode *node = NULL;
     xmlNode *node_state = NULL;
 
     crm_trace("Removing %s from the CIB", name);
 
     if(name == NULL && id == 0) {
         return -ENOTUNIQ;
     }
 
     node = create_xml_node(NULL, XML_CIB_TAG_NODE);
     node_state = create_xml_node(NULL, XML_CIB_TAG_STATE);
 
     crm_xml_add(node, XML_ATTR_UNAME, name);
     crm_xml_add(node_state, XML_ATTR_UNAME, name);
     if (id > 0) {
         crm_xml_set_id(node, "%ld", id);
         crm_xml_add(node_state, XML_ATTR_ID, ID(node));
     }
 
     cib = cib_new();
     cib->cmds->signon(cib, crm_system_name, cib_command);
 
     rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call);
     if (rc != pcmk_ok) {
         printf("Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s",
                 name, id, pcmk_strerror(rc));
     }
     rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call);
     if (rc != pcmk_ok) {
         printf("Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s",
                 name, id, pcmk_strerror(rc));
     }
 
     cib->cmds->signoff(cib);
     cib_delete(cib);
     return rc;
 }
 
 static int
 tools_remove_node_cache(const char *node_name, long nodeid, const char *target)
 {
     int rc = -1;
     crm_ipc_t *conn = crm_ipc_new(target, 0);
     xmlNode *cmd = NULL;
 
     if (!conn) {
         return -ENOTCONN;
     }
 
     if (!crm_ipc_connect(conn)) {
         crm_perror(LOG_ERR, "Connection to %s failed", target);
         crm_ipc_destroy(conn);
         return -ENOTCONN;
     }
 
     if(safe_str_eq(target, CRM_SYSTEM_CRMD)) {
         // The controller requires a hello message before sending a request
         rc = send_controller_hello(conn);
         if (rc < 0) {
             fprintf(stderr, "error: Could not register with controller: %s\n",
                     pcmk_strerror(rc));
             return rc;
         }
     }
 
     crm_trace("Removing %s[%ld] from the %s membership cache",
               node_name, nodeid, target);
 
     if(safe_str_eq(target, T_ATTRD)) {
         cmd = create_xml_node(NULL, __FUNCTION__);
 
         crm_xml_add(cmd, F_TYPE, T_ATTRD);
         crm_xml_add(cmd, F_ORIG, crm_system_name);
 
         crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE);
         crm_xml_add(cmd, PCMK__XA_ATTR_NODE_NAME, node_name);
 
         if (nodeid > 0) {
             crm_xml_add_int(cmd, PCMK__XA_ATTR_NODE_ID, (int) nodeid);
         }
 
     } else {
         cmd = create_request(CRM_OP_RM_NODE_CACHE,
                              NULL, NULL, target, crm_system_name, pid_s);
         if (nodeid > 0) {
             crm_xml_set_id(cmd, "%ld", nodeid);
         }
         crm_xml_add(cmd, XML_ATTR_UNAME, node_name);
     }
 
     rc = crm_ipc_send(conn, cmd, 0, 0, NULL);
     crm_debug("%s peer cache cleanup for %s (%ld): %d",
               target, node_name, nodeid, rc);
 
     if (rc > 0) {
         rc = cib_remove_node(nodeid, node_name);
     }
 
     if (conn) {
         crm_ipc_close(conn);
         crm_ipc_destroy(conn);
     }
     free_xml(cmd);
     return rc > 0 ? 0 : rc;
 }
 
 static void
 remove_node(const char *target_uname)
 {
     int d = 0;
     long nodeid = 0;
     const char *node_name = NULL;
     char *endptr = NULL;
     const char *daemons[] = {
         CRM_SYSTEM_CRMD,
         "stonith-ng",
         T_ATTRD,
         CRM_SYSTEM_MCP,
     };
 
     // Check whether node was specified by name or numeric ID
     errno = 0;
     nodeid = strtol(target_uname, &endptr, 10);
     if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0')
         || (nodeid <= 0)) {
         // It's not a positive integer, so assume it's a node name
         nodeid = 0;
         node_name = target_uname;
     }
 
     for (d = 0; d < DIMOF(daemons); d++) {
         if (tools_remove_node_cache(node_name, nodeid, daemons[d])) {
             crm_err("Failed to connect to %s to remove node '%s'",
                     daemons[d], target_uname);
             crm_node_exit(CRM_EX_ERROR);
             return;
         }
     }
     crm_node_exit(CRM_EX_OK);
 }
 
 static gint
 compare_node_xml(gconstpointer a, gconstpointer b)
 {
     const char *a_name = crm_element_value((xmlNode*) a, "uname");
     const char *b_name = crm_element_value((xmlNode*) b, "uname");
 
     return strcmp((a_name? a_name : ""), (b_name? b_name : ""));
 }
 
 static int
 node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata)
 {
     GList *nodes = NULL;
     xmlNode *node = NULL;
     xmlNode *msg = string2xml(buffer);
     const char *uname;
     const char *state;
 
     if (msg == NULL) {
         fprintf(stderr, "error: Could not understand pacemakerd response\n");
         crm_node_exit(CRM_EX_PROTOCOL);
         return 0;
     }
 
     crm_log_xml_trace(msg, "message");
 
     for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) {
         nodes = g_list_insert_sorted(nodes, node, compare_node_xml);
     }
 
     for (GList *iter = nodes; iter; iter = iter->next) {
         node = (xmlNode*) iter->data;
         uname = crm_element_value(node, "uname");
         state = crm_element_value(node, "state");
 
         if (options.command == 'l') {
             int id = 0;
 
             crm_element_value_int(node, "id", &id);
             printf("%d %s %s\n", id, (uname? uname : ""), (state? state : ""));
 
         // This is CRM_NODE_MEMBER but we don't want to include cluster header
         } else if ((options.command == 'p') && safe_str_eq(state, "member")) {
             printf("%s ", (uname? uname : ""));
         }
     }
     if (options.command == 'p') {
         fprintf(stdout, "\n");
     }
 
     free_xml(msg);
     crm_node_exit(CRM_EX_OK);
     return 0;
 }
 
 static void
 run_pacemakerd_mainloop()
 {
     crm_ipc_t *ipc = NULL;
     xmlNode *poke = NULL;
 
     ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch);
 
     // Sending anything will get us a list of nodes
     poke = create_xml_node(NULL, "poke");
     crm_ipc_send(ipc, poke, 0, 0, NULL);
     free_xml(poke);
 
     // Handle reply via node_mcp_dispatch()
     run_mainloop_and_exit();
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup *group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     context = pcmk__build_arg_context(args, NULL, &group);
 
     /* Add the -q option, which cannot be part of the globally supported options
      * because some tools use that flag for something else.
      */
     pcmk__add_main_args(context, extra_prog_entries);
 
     pcmk__add_arg_group(context, "commands", "Commands:",
                         "Show command help", command_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 
     GError *error = NULL;
     GOptionContext *context = NULL;
     GOptionGroup *output_group = NULL;
     gchar **processed_args = NULL;
 
     context = build_arg_context(args, output_group);
 
     crm_log_cli_init("crm_node");
 
     processed_args = pcmk__cmdline_preproc(argv, "NR");
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (args->version) {
         /* FIXME:  When crm_node is converted to use formatted output, this can go. */
         pcmk__cli_help('v', CRM_EX_USAGE);
     }
 
     if (optind > argc || options.command == 0) {
-        fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
+        fprintf(stderr, "%s", help);
+        free(help);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     if (options.dangerous_cmd && options.force_flag == FALSE) {
         fprintf(stderr, "The supplied command is considered dangerous."
                 "  To prevent accidental destruction of the cluster,"
                 " the --force flag is required in order to proceed.\n");
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     switch (options.command) {
         case 'n':
             print_node_name();
             break;
         case 'R':
             remove_node(options.target_uname);
             break;
         case 'i':
         case 'q':
         case 'N':
             run_controller_mainloop(options.nodeid);
             break;
         case 'l':
         case 'p':
             run_pacemakerd_mainloop();
             break;
         default:
             break;
     }
 
 done:
     g_strfreev(processed_args);
     g_clear_error(&error);
     pcmk__free_arg_context(context);
     crm_node_exit(exit_code);
     return exit_code;
 }
diff --git a/tools/crm_rule.c b/tools/crm_rule.c
index f80fd95f0e..b5e4b5ba46 100644
--- a/tools/crm_rule.c
+++ b/tools/crm_rule.c
@@ -1,359 +1,362 @@
 /*
  * Copyright 2019-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/cib.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/iso8601.h>
 #include <crm/msg_xml.h>
 #include <crm/pengine/rules_internal.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include <sys/stat.h>
 
 #define SUMMARY "evaluate rules from the Pacemaker configuration"
 
 enum crm_rule_mode {
     crm_rule_mode_none,
     crm_rule_mode_check
 };
 
 struct {
     char *date;
     char *input_xml;
     enum crm_rule_mode mode;
     char *rule;
 } options = {
     .mode = crm_rule_mode_none
 };
 
 static int crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date);
 
 static gboolean mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 static GOptionEntry mode_entries[] = {
     { "check", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
       "Check whether a rule is in effect",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry data_entries[] = {
     { "xml-text", 'X', 0, G_OPTION_ARG_STRING, &options.input_xml,
       "Use argument for XML (or stdin if '-')",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "date", 'd', 0, G_OPTION_ARG_STRING, &options.date,
       "Whether the rule is in effect on a given date",
       NULL },
     { "rule", 'r', 0, G_OPTION_ARG_STRING, &options.rule,
       "The ID of the rule to check",
       NULL },
 
     { NULL }
 };
 
 static gboolean
 mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     if (strcmp(option_name, "c")) {
         options.mode = crm_rule_mode_check;
     }
 
     return TRUE;
 }
 
 static int
 crm_rule_check(pe_working_set_t *data_set, const char *rule_id, crm_time_t *effective_date)
 {
     xmlNode *cib_constraints = NULL;
     xmlNode *match = NULL;
     xmlXPathObjectPtr xpathObj = NULL;
     char *xpath = NULL;
     int rc = pcmk_rc_ok;
     int max = 0;
 
     /* Rules are under the constraints node in the XML, so first find that. */
     cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
 
     /* Get all rules matching the given ID which are also simple enough for us to check.
      * For the moment, these rules must only have a single date_expression child and:
      * - Do not have a date_spec operation, or
      * - Have a date_spec operation that contains years= but does not contain moon=.
      *
      * We do this in steps to provide better error messages.  First, check that there's
      * any rule with the given ID.
      */
     xpath = crm_strdup_printf("//rule[@id='%s']", rule_id);
     xpathObj = xpath_search(cib_constraints, xpath);
     max = numXpathResults(xpathObj);
 
     if (max == 0) {
         CMD_ERR("No rule found with ID=%s", rule_id);
         rc = ENXIO;
         goto bail;
     } else if (max > 1) {
         CMD_ERR("More than one rule with ID=%s found", rule_id);
         rc = ENXIO;
         goto bail;
     }
 
     free(xpath);
     freeXpathObject(xpathObj);
 
     /* Next, make sure it has exactly one date_expression. */
     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression", rule_id);
     xpathObj = xpath_search(cib_constraints, xpath);
     max = numXpathResults(xpathObj);
 
     if (max != 1) {
         CMD_ERR("Can't check rule %s because it does not have exactly one date_expression", rule_id);
         rc = EOPNOTSUPP;
         goto bail;
     }
 
     free(xpath);
     freeXpathObject(xpathObj);
 
     /* Then, check that it's something we actually support. */
     xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation!='date_spec']", rule_id);
     xpathObj = xpath_search(cib_constraints, xpath);
     max = numXpathResults(xpathObj);
 
     if (max == 0) {
         free(xpath);
         freeXpathObject(xpathObj);
 
         xpath = crm_strdup_printf("//rule[@id='%s']//date_expression[@operation='date_spec' and date_spec/@years and not(date_spec/@moon)]",
                                   rule_id);
         xpathObj = xpath_search(cib_constraints, xpath);
         max = numXpathResults(xpathObj);
 
         if (max == 0) {
             CMD_ERR("Rule either must not use date_spec, or use date_spec with years= but not moon=");
             rc = ENXIO;
             goto bail;
         }
     }
 
     match = getXpathResult(xpathObj, 0);
 
     /* We should have ensured both of these pass with the xpath query above, but
      * double checking can't hurt.
      */
     CRM_ASSERT(match != NULL);
     CRM_ASSERT(find_expression_type(match) == time_expr);
 
     rc = pe_eval_date_expression(match, effective_date, NULL);
 
     if (rc == pcmk_rc_within_range) {
         printf("Rule %s is still in effect\n", rule_id);
         rc = pcmk_rc_ok;
     } else if (rc == pcmk_rc_ok) {
         printf("Rule %s satisfies conditions\n", rule_id);
     } else if (rc == pcmk_rc_after_range) {
         printf("Rule %s is expired\n", rule_id);
     } else if (rc == pcmk_rc_before_range) {
         printf("Rule %s has not yet taken effect\n", rule_id);
     } else if (rc == pcmk_rc_op_unsatisfied) {
         printf("Rule %s does not satisfy conditions\n", rule_id);
     } else {
         printf("Could not determine whether rule %s is expired\n", rule_id);
     }
 
 bail:
     free(xpath);
     freeXpathObject(xpathObj);
     return rc;
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args) {
     GOptionContext *context = NULL;
 
     const char *description = "This tool is currently experimental.\n"
                               "The interface, behavior, and output may change with any version of pacemaker.";
 
     context = pcmk__build_arg_context(args, NULL, NULL);
     g_option_context_set_description(context, description);
 
     pcmk__add_arg_group(context, "modes", "Modes (mutually exclusive):",
                         "Show modes of operation", mode_entries);
     pcmk__add_arg_group(context, "data", "Data:",
                         "Show data options", data_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     cib_t *cib_conn = NULL;
     pe_working_set_t *data_set = NULL;
 
     crm_time_t *rule_date = NULL;
     xmlNode *input = NULL;
 
     int rc = pcmk_ok;
     crm_exit_t exit_code = CRM_EX_OK;
 
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 
     GError *error = NULL;
     GOptionContext *context = NULL;
     gchar **processed_args = NULL;
 
     context = build_arg_context(args);
 
     crm_log_cli_init("crm_rule");
 
     processed_args = pcmk__cmdline_preproc(argv, "nopNO");
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         CMD_ERR("%s: %s\n", g_get_prgname(), error->message);
         exit_code = CRM_EX_USAGE;
         goto bail;
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     if (args->version) {
         /* FIXME:  When crm_rule is converted to use formatted output, this can go. */
         pcmk__cli_help('v', CRM_EX_USAGE);
     }
 
     if (optind > argc) {
-        CMD_ERR("%s", g_option_context_get_help(context, TRUE, NULL));
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
+        CMD_ERR("%s", help);
+        free(help);
         exit_code = CRM_EX_USAGE;
         goto bail;
     }
 
     /* Check command line arguments before opening a connection to
      * the CIB manager or doing anything else important.
      */
     switch(options.mode) {
         case crm_rule_mode_check:
             if (options.rule == NULL) {
                 CMD_ERR("--check requires use of --rule=");
                 exit_code = CRM_EX_USAGE;
                 goto bail;
             }
 
             break;
 
         default:
             CMD_ERR("No mode operation given");
             exit_code = CRM_EX_USAGE;
             goto bail;
             break;
     }
 
     /* Set up some defaults. */
     rule_date = crm_time_new(options.date);
     if (rule_date == NULL) {
         CMD_ERR("No --date given and can't determine current date");
         exit_code = CRM_EX_DATAERR;
         goto bail;
     }
 
     /* Where does the XML come from?  If one of various command line options were
      * given, use those.  Otherwise, connect to the CIB and use that.
      */
     if (safe_str_eq(options.input_xml, "-")) {
         input = stdin2xml();
 
         if (input == NULL) {
             CMD_ERR("Couldn't parse input from STDIN\n");
             exit_code = CRM_EX_DATAERR;
             goto bail;
         }
     } else if (options.input_xml != NULL) {
         input = string2xml(options.input_xml);
 
         if (input == NULL) {
             CMD_ERR("Couldn't parse input string: %s\n", options.input_xml);
 
             exit_code = CRM_EX_DATAERR;
             goto bail;
         }
     } else {
         /* Establish a connection to the CIB manager */
         cib_conn = cib_new();
         rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
         if (rc != pcmk_ok) {
             CMD_ERR("Error connecting to the CIB manager: %s", pcmk_strerror(rc));
             exit_code = crm_errno2exit(rc);
             goto bail;
         }
     }
 
     /* Populate working set from CIB query */
     if (input == NULL) {
         rc = cib_conn->cmds->query(cib_conn, NULL, &input, cib_scope_local | cib_sync_call);
         if (rc != pcmk_ok) {
             exit_code = crm_errno2exit(rc);
             goto bail;
         }
     }
 
     /* Populate the working set instance */
     data_set = pe_new_working_set();
     if (data_set == NULL) {
         exit_code = crm_errno2exit(ENOMEM);
         goto bail;
     }
     set_bit(data_set->flags, pe_flag_no_counts);
     set_bit(data_set->flags, pe_flag_no_compat);
 
     data_set->input = input;
     data_set->now = rule_date;
 
     /* Unpack everything. */
     cluster_status(data_set);
 
     /* Now do whichever operation mode was asked for.  There's only one at the
      * moment so this looks a little silly, but I expect there will be more
      * modes in the future.
      */
     switch(options.mode) {
         case crm_rule_mode_check:
             rc = crm_rule_check(data_set, options.rule, rule_date);
 
             if (rc > 0) {
                 CMD_ERR("Error checking rule: %s", pcmk_rc_str(rc));
             }
 
             exit_code = pcmk_rc2exitc(rc);
             break;
 
         default:
             break;
     }
 
 bail:
     if (cib_conn != NULL) {
         cib_conn->cmds->signoff(cib_conn);
         cib_delete(cib_conn);
     }
 
     g_strfreev(processed_args);
     g_clear_error(&error);
     pcmk__free_arg_context(context);
     pe_free_working_set(data_set);
     crm_exit(exit_code);
 }
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index 7dac4246ba..87b538ac94 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,613 +1,619 @@
 /*
  * Copyright 2009-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/utsname.h>
 
 #include <errno.h>
 #include <fcntl.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/ipc.h>
 #include <crm/cluster/internal.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/output.h>
 
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/cib.h>
 #include <crm/pengine/status.h>
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
 
 char action = 0;
 
 struct {
     gboolean as_nodeid;
     gboolean broadcast;
     gboolean cleanup;
     gboolean installed;
     gboolean metadata;
     gboolean registered;
     gboolean validate_cfg;
     stonith_key_value_t *devices;
     stonith_key_value_t *params;
     int fence_level;
     int timeout ;
     int tolerance;
     char *agent;
     char *confirm_host;
     char *fence_host;
     char *history;
     char *last_fenced;
     char *query;
     char *reboot_host;
     char *register_dev;
     char *register_level;
     char *targets;
     char *terminate;
     char *unfence_host;
     char *unregister_dev;
     char *unregister_level;
 } options = {
     .timeout = 120
 };
 
 gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 #define INDENT "                                    "
 
 /* *INDENT-OFF* */
 static GOptionEntry defn_entries[] = {
     { "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
       "Register the named stonith device. Requires: --agent.\n"
       INDENT "Optional: --option, --env-option.",
       "DEVICE" },
     { "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
       "De-register the named stonith device.",
       "DEVICE" },
     { "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
       "Register a stonith level for the named target,\n"
       INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
       INDENT "Requires: --index and one or more --device entries.",
       "TARGET" },
     { "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
       "Unregister a stonith level for the named target,\n"
       INDENT "specified as for --register-level. Requires: --index",
       "TARGET" },
 
     { NULL }
 };
 
 static GOptionEntry query_entries[] = {
     { "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
       "List devices that can terminate the specified host.\n"
       INDENT "Optional: --timeout",
       "HOST" },
     { "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
       "List all registered devices. Optional: --timeout.",
       NULL },
     { "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
       "List all installed devices. Optional: --timeout.",
       NULL },
     { "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
       "List the targets that can be fenced by the\n"
       INDENT "named device. Optional: --timeout.",
       "DEVICE" },
     { "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
       "Show agent metadata. Requires: --agent.\n"
       INDENT "Optional: --timeout.",
       NULL },
     { "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
       "Check the named device's status. Optional: --timeout.",
       "DEVICE" },
     { "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
       "Show last successful fencing operation for named node\n"
       INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
       INDENT "--quiet (show only the operation's epoch timestamp),\n"
       INDENT "--verbose (show all recorded and pending operations),\n"
       INDENT "--broadcast (update history from all nodes available).",
       "NODE" },
     { "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
       "Indicate when the named node was last fenced.\n"
       INDENT "Optional: --as-node-id.",
       "NODE" },
     { "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
       "Validate a fence device configuration.\n"
       INDENT "Requires: --agent. Optional: --option, --env-option,\n"
       INDENT "--quiet (print no output, only return status).",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry fence_entries[] = {
     { "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
       "Fence named host. Optional: --timeout, --tolerance.",
       "HOST" },
     { "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
       "Unfence named host. Optional: --timeout, --tolerance.",
       "HOST" },
     { "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
       "Reboot named host. Optional: --timeout, --tolerance.",
       "HOST" },
     { "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
       "Tell cluster that named host is now safely down.",
       "HOST", },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
       "Cleanup wherever appropriate. Requires --history.",
       NULL },
     { "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
       "Broadcast wherever appropriate.",
       NULL },
     { "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
       "The agent to use (for example, fence_xvm;\n"
       INDENT "with --register, --metadata, --validate).",
       "AGENT" },
     { "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
       "Specify a device configuration parameter as NAME=VALUE\n"
       INDENT "(may be specified multiple times; with --register,\n"
       INDENT "--validate).",
       "PARAM" },
     { "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
       "Specify a device configuration parameter with the\n"
       INDENT "specified name, using the value of the\n"
       INDENT "environment variable of the same name prefixed with\n"
       INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
       INDENT "with --register, --validate).",
       "PARAM" },
     { "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
       "Identify fencing operations in logs with the specified\n"
       INDENT "tag; useful when multiple entities might invoke\n"
       INDENT "stonith_admin (used with most commands).",
       "TAG" },
     { "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
       "Device ID (with --register-level, device to associate with\n"
       INDENT "a given host and level; may be specified multiple times)"
 #if SUPPORT_CIBSECRETS
       "\n" INDENT "(with --validate, name to use to load CIB secrets)"
 #endif
       ".",
       "DEVICE" },
     { "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
       "The stonith level (1-9) (with --register-level,\n"
       INDENT "--deregister-level).",
       "LEVEL" },
     { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
       "Operation timeout in seconds (default 120;\n"
       INDENT "used with most commands).",
       "SECONDS" },
     { "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
       "(Advanced) The supplied node is the corosync node ID\n"
       INDENT "(with --last).",
       NULL },
     { "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
       "(Advanced) Do nothing if an equivalent --fence request\n"
       INDENT "succeeded less than this many seconds earlier\n"
       INDENT "(with --fence, --unfence, --reboot).",
       "SECONDS" },
 
     { NULL }
 };
 /* *INDENT-ON* */
 
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_HTML,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 static int st_opts = st_opt_sync_call | st_opt_allow_suicide;
 
 static char *name = NULL;
 
 gboolean
 add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
     const char *env = getenv(key);
     gboolean retval = TRUE;
 
     if (env == NULL) {
         crm_err("Invalid option: -e %s", optarg);
         g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
         retval = FALSE;
     } else {
         crm_info("Got: '%s'='%s'", optarg, env);
         options.params = stonith_key_value_add(options.params, optarg, env);
     }
 
     free(key);
     return retval;
 }
 
 gboolean
 add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.devices = stonith_key_value_add(options.devices, NULL, optarg);
     return TRUE;
 }
 
 gboolean
 add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.tolerance = crm_get_msec(optarg) / 1000;
     return TRUE;
 }
 
 gboolean
 add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     char *name = NULL;
     char *value = NULL;
     int rc = 0;
     gboolean retval = TRUE;
 
     crm_info("Scanning: -o %s", optarg);
 
     rc = pcmk_scan_nvpair(optarg, &name, &value);
 
     if (rc != 2) {
         crm_err("Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
         g_set_error(error, G_OPTION_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_strerror(rc));
         retval = FALSE;
     } else {
         crm_info("Got: '%s'='%s'", name, value);
         options.params = stonith_key_value_add(options.params, name, value);
     }
 
     free(name);
     free(value);
     return retval;
 }
 
 gboolean
 set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     free(name);
     name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
     return TRUE;
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     context = pcmk__build_arg_context(args, "text (default), html, xml", group);
 
     /* Add the -q option, which cannot be part of the globally supported options
      * because some tools use that flag for something else.
      */
     pcmk__add_main_args(context, extra_prog_entries);
 
     pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
                         "Show device definition help", defn_entries);
     pcmk__add_arg_group(context, "queries", "Queries:",
                         "Show query help", query_entries);
     pcmk__add_arg_group(context, "fence", "Fencing Commands:",
                         "Show fence help", fence_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = 0;
     bool no_connect = false;
     bool required_agent = false;
 
     char *target = NULL;
     const char *device = NULL;
 
     crm_exit_t exit_code = CRM_EX_OK;
     stonith_t *st = NULL;
 
     pcmk__output_t *out = NULL;
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
 
     GError *error = NULL;
     GOptionContext *context = NULL;
     GOptionGroup *output_group = NULL;
     gchar **processed_args = NULL;
 
     context = build_arg_context(args, &output_group);
     pcmk__register_formats(output_group, formats);
 
     crm_log_cli_init("stonith_admin");
 
     name = strdup(crm_system_name);
 
     processed_args = pcmk__cmdline_preproc(argv, "adehilorstvBCDFHQRTU");
 
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     for (int i = 0; i < args->verbosity; i++) {
         crm_bump_log_level(argc, argv);
     }
 
     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
     if (rc != pcmk_rc_ok) {
         fprintf(stderr, "Error creating output format %s: %s\n",
                 args->output_ty, pcmk_rc_str(rc));
         exit_code = CRM_EX_ERROR;
         goto done;
     }
 
     stonith__register_messages(out);
 
     if (args->version) {
         out->version(out, false);
         goto done;
     }
 
     if (options.validate_cfg) {
         required_agent = true;
         no_connect = true;
         action = 'K';
     }
 
     if (options.installed) {
         no_connect = true;
         action = 'I';
     }
 
     if (options.registered) {
         action = 'L';
     }
 
     if (options.register_dev != NULL) {
         required_agent = true;
         action = 'R';
         device = options.register_dev;
     }
 
     if (options.query != NULL) {
         action = 'Q';
         device = options.query;
     }
 
     if (options.unregister_dev != NULL) {
         action = 'D';
         device = options.unregister_dev;
     }
 
     if (options.targets != NULL) {
         action = 's';
         device = options.targets;
     }
 
     if (options.terminate != NULL) {
         action = 'L';
         target = options.terminate;
     }
 
     if (options.metadata) {
         no_connect = true;
         required_agent = true;
         action = 'M';
     }
 
     if (options.reboot_host != NULL) {
         no_connect = true;
         action = 'B';
         target = options.reboot_host;
         crm_log_args(argc, argv);
     }
 
     if (options.fence_host != NULL) {
         no_connect = true;
         action = 'F';
         target = options.fence_host;
         crm_log_args(argc, argv);
     }
 
     if (options.unfence_host != NULL) {
         no_connect = true;
         action = 'U';
         target = options.unfence_host;
         crm_log_args(argc, argv);
     }
 
     if (options.confirm_host != NULL) {
         action = 'C';
         target = options.confirm_host;
         crm_log_args(argc, argv);
     }
 
     if (options.last_fenced != NULL) {
         action = 'h';
         target = options.last_fenced;
     }
 
     if (options.history != NULL) {
         action = 'H';
         target = options.history;
     }
 
     if (options.register_level != NULL) {
         action = 'r';
         target = options.register_level;
     }
 
     if (options.unregister_level != NULL) {
         action = 'd';
         target = options.unregister_level;
     }
 
     if (optind > argc || action == 0) {
-        out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
+        out->err(out, "%s", help);
+        free(help);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     if (required_agent && options.agent == NULL) {
+        char *help = g_option_context_get_help(context, TRUE, NULL);
+
         out->err(out, "Please specify an agent to query using -a,--agent [value]");
-        out->err(out, "%s", g_option_context_get_help(context, TRUE, NULL));
+        out->err(out, "%s", help);
+        free(help);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     st = stonith_api_new();
     if (st == NULL) {
         rc = -ENOMEM;
     } else if (!no_connect) {
         rc = st->cmds->connect(st, name, NULL);
     }
     if (rc < 0) {
         out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
         exit_code = CRM_EX_DISCONNECT;
         goto done;
     }
 
     switch (action) {
         case 'I':
             rc = pcmk__fence_installed(out, st, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Failed to list installed devices: %s", pcmk_strerror(rc));
             }
 
             break;
 
         case 'L':
             rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Failed to list registered devices: %s", pcmk_strerror(rc));
             }
 
             break;
 
         case 'Q':
             rc = st->cmds->monitor(st, st_opts, device, options.timeout);
             if (rc != pcmk_rc_ok) {
                 rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
             }
             break;
 
         case 's':
             rc = pcmk__fence_list_targets(out, st, target, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Couldn't list targets: %s", pcmk_strerror(rc));
             }
 
             break;
 
         case 'R':
             rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
                                            options.params);
             break;
 
         case 'D':
             rc = st->cmds->remove_device(st, st_opts, device);
             break;
 
         case 'd':
             rc = pcmk__fence_unregister_level(st, target, options.fence_level);
             break;
 
         case 'r':
             rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
             break;
 
         case 'M':
             rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't get fence agent meta-data: %s", pcmk_strerror(rc));
             }
 
             break;
 
         case 'C':
             rc = st->cmds->confirm(st, st_opts, target);
             break;
 
         case 'B':
             rc = pcmk__fence_action(st, target, "reboot", name, options.timeout*1000,
                                       options.tolerance*1000);
             break;
 
         case 'F':
             rc = pcmk__fence_action(st, target, "off", name, options.timeout*1000,
                                       options.tolerance*1000);
             break;
 
         case 'U':
             rc = pcmk__fence_action(st, target, "on", name, options.timeout*1000,
                                       options.tolerance*1000);
             break;
 
         case 'h':
             rc = pcmk__fence_last(out, target, options.as_nodeid);
             break;
 
         case 'H':
             rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->quiet,
                                        args->verbosity, options.broadcast, options.cleanup);
             break;
 
         case 'K':
             device = options.devices ? options.devices->key : NULL;
             rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
                                         options.timeout*1000);
             break;
     }
 
     crm_info("Command returned: %s (%d)", pcmk_strerror(rc), rc);
     exit_code = crm_errno2exit(rc);
 
   done:
     g_strfreev(processed_args);
     g_clear_error(&error);
     pcmk__free_arg_context(context);
 
     if (out != NULL) {
         out->finish(out, exit_code, true, NULL);
         pcmk__output_free(out);
     }
     free(name);
     stonith_key_value_freeall(options.params, 1, 1);
 
     if (st != NULL) {
         st->cmds->disconnect(st);
         stonith_api_delete(st);
     }
 
     return exit_code;
 }