root/tools/crm_mon.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. PCMK__OUTPUT_ARGS
  2. PCMK__OUTPUT_ARGS
  3. PCMK__OUTPUT_ARGS
  4. PCMK__OUTPUT_ARGS
  5. all_includes
  6. default_includes
  7. find_section_bit
  8. apply_exclude
  9. apply_include
  10. apply_include_exclude
  11. user_include_exclude_cb
  12. include_exclude_cb
  13. as_cgi_cb
  14. as_html_cb
  15. as_simple_cb
  16. as_xml_cb
  17. fence_history_cb
  18. group_by_node_cb
  19. hide_headers_cb
  20. inactive_resources_cb
  21. no_curses_cb
  22. print_brief_cb
  23. print_detail_cb
  24. print_description_cb
  25. print_timing_cb
  26. reconnect_cb
  27. one_shot_cb
  28. daemonize_cb
  29. show_attributes_cb
  30. show_bans_cb
  31. show_failcounts_cb
  32. show_operations_cb
  33. show_tickets_cb
  34. use_cib_file_cb
  35. reconnect_after_timeout
  36. mon_cib_connection_destroy
  37. mon_shutdown
  38. mon_winresize
  39. setup_fencer_connection
  40. setup_cib_connection
  41. set_fencing_options
  42. setup_api_connections
  43. get_option_desc
  44. detect_user_input
  45. avoid_zombies
  46. build_arg_context
  47. add_output_args
  48. reconcile_output_format
  49. set_default_exec_mode
  50. clean_up_on_connection_failure
  51. one_shot
  52. exit_on_invalid_cib
  53. main
  54. send_custom_trap
  55. handle_rsc_op
  56. mon_trigger_refresh
  57. handle_op_for_node
  58. crm_diff_update_v2
  59. crm_diff_update_v1
  60. crm_diff_update
  61. mon_refresh_display
  62. mon_st_callback_event
  63. refresh_after_event
  64. mon_st_callback_display
  65. clean_up

   1 /*
   2  * Copyright 2004-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <sys/param.h>
  13 
  14 #include <crm/crm.h>
  15 
  16 #include <stdint.h>
  17 #include <stdio.h>
  18 #include <sys/types.h>
  19 #include <sys/stat.h>
  20 #include <unistd.h>
  21 
  22 #include <stdlib.h>
  23 #include <errno.h>
  24 #include <fcntl.h>
  25 #include <libgen.h>
  26 #include <signal.h>
  27 #include <sys/utsname.h>
  28 
  29 #include <crm/msg_xml.h>
  30 #include <crm/services.h>
  31 #include <crm/lrmd.h>
  32 #include <crm/common/cmdline_internal.h>
  33 #include <crm/common/internal.h>  // pcmk__ends_with_ext()
  34 #include <crm/common/ipc.h>
  35 #include <crm/common/mainloop.h>
  36 #include <crm/common/output.h>
  37 #include <crm/common/output_internal.h>
  38 #include <crm/common/results.h>
  39 #include <crm/common/util.h>
  40 #include <crm/common/xml.h>
  41 #include <crm/common/xml_internal.h>
  42 
  43 #include <crm/cib/internal.h>
  44 #include <crm/pengine/status.h>
  45 #include <crm/pengine/internal.h>
  46 #include <pacemaker-internal.h>
  47 #include <crm/stonith-ng.h>
  48 #include <crm/fencing/internal.h>
  49 
  50 #include "crm_mon.h"
  51 
  52 #define SUMMARY "Provides a summary of cluster's current state.\n\n" \
  53                 "Outputs varying levels of detail in a number of different formats."
  54 
  55 /*
  56  * Definitions indicating which items to print
  57  */
  58 
  59 static uint32_t show;
  60 static uint32_t show_opts = pcmk_show_pending;
  61 
  62 /*
  63  * Definitions indicating how to output
  64  */
  65 
  66 static mon_output_format_t output_format = mon_output_unset;
  67 
  68 /* other globals */
  69 static GIOChannel *io_channel = NULL;
  70 static GMainLoop *mainloop = NULL;
  71 static guint reconnect_timer = 0;
  72 static mainloop_timer_t *refresh_timer = NULL;
  73 
  74 static enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
  75 static cib_t *cib = NULL;
  76 static stonith_t *st = NULL;
  77 static xmlNode *current_cib = NULL;
  78 
  79 static GError *error = NULL;
  80 static pcmk__common_args_t *args = NULL;
  81 static pcmk__output_t *out = NULL;
  82 static GOptionContext *context = NULL;
  83 static gchar **processed_args = NULL;
  84 
  85 static time_t last_refresh = 0;
  86 volatile crm_trigger_t *refresh_trigger = NULL;
  87 
  88 static enum pcmk__fence_history fence_history = pcmk__fence_history_none;
  89 
  90 int interactive_fence_level = 0;
  91 
  92 static pcmk__supported_format_t formats[] = {
  93 #if CURSES_ENABLED
  94     CRM_MON_SUPPORTED_FORMAT_CURSES,
  95 #endif
  96     PCMK__SUPPORTED_FORMAT_HTML,
  97     PCMK__SUPPORTED_FORMAT_NONE,
  98     PCMK__SUPPORTED_FORMAT_TEXT,
  99     PCMK__SUPPORTED_FORMAT_XML,
 100     { NULL, NULL, NULL }
 101 };
 102 
 103 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 104                   "enum pcmk_pacemakerd_state")
 105 static int
 106 crm_mon_disconnected_default(pcmk__output_t *out, va_list args)
 107 {
 108     return pcmk_rc_no_output;
 109 }
 110 
 111 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 112                   "enum pcmk_pacemakerd_state")
 113 static int
 114 crm_mon_disconnected_html(pcmk__output_t *out, va_list args)
 115 {
 116     const char *desc = va_arg(args, const char *);
 117     enum pcmk_pacemakerd_state state =
 118         (enum pcmk_pacemakerd_state) va_arg(args, int);
 119 
 120     if (out->dest != stdout) {
 121         out->reset(out);
 122     }
 123 
 124     pcmk__output_create_xml_text_node(out, "span", "Not connected to CIB");
 125 
 126     if (desc != NULL) {
 127         pcmk__output_create_xml_text_node(out, "span", ": ");
 128         pcmk__output_create_xml_text_node(out, "span", desc);
 129     }
 130 
 131     if (state != pcmk_pacemakerd_state_invalid) {
 132         const char *state_s = pcmk__pcmkd_state_enum2friendly(state);
 133 
 134         pcmk__output_create_xml_text_node(out, "span", " (");
 135         pcmk__output_create_xml_text_node(out, "span", state_s);
 136         pcmk__output_create_xml_text_node(out, "span", ")");
 137     }
 138 
 139     out->finish(out, CRM_EX_DISCONNECT, true, NULL);
 140     return pcmk_rc_ok;
 141 }
 142 
 143 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 144                   "enum pcmk_pacemakerd_state")
 145 static int
 146 crm_mon_disconnected_text(pcmk__output_t *out, va_list args)
 147 {
 148     const char *desc = va_arg(args, const char *);
 149     enum pcmk_pacemakerd_state state =
 150         (enum pcmk_pacemakerd_state) va_arg(args, int);
 151     int rc = pcmk_rc_ok;
 152 
 153     if (out->dest != stdout) {
 154         out->reset(out);
 155     }
 156 
 157     if (state != pcmk_pacemakerd_state_invalid) {
 158         rc = out->info(out, "Not connected to CIB%s%s (%s)",
 159                        (desc != NULL)? ": " : "", pcmk__s(desc, ""),
 160                        pcmk__pcmkd_state_enum2friendly(state));
 161     } else {
 162         rc = out->info(out, "Not connected to CIB%s%s",
 163                        (desc != NULL)? ": " : "", pcmk__s(desc, ""));
 164     }
 165 
 166     out->finish(out, CRM_EX_DISCONNECT, true, NULL);
 167     return rc;
 168 }
 169 
 170 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 171                   "enum pcmk_pacemakerd_state")
 172 static int
 173 crm_mon_disconnected_xml(pcmk__output_t *out, va_list args)
 174 {
 175     const char *desc = va_arg(args, const char *);
 176     enum pcmk_pacemakerd_state state =
 177         (enum pcmk_pacemakerd_state) va_arg(args, int);
 178     const char *state_s = NULL;
 179 
 180     if (out->dest != stdout) {
 181         out->reset(out);
 182     }
 183 
 184     if (state != pcmk_pacemakerd_state_invalid) {
 185         state_s = pcmk_pacemakerd_api_daemon_state_enum2text(state);
 186     }
 187 
 188     pcmk__output_create_xml_node(out, "crm-mon-disconnected",
 189                                  XML_ATTR_DESC, desc,
 190                                  "pacemakerd-state", state_s,
 191                                  NULL);
 192 
 193     out->finish(out, CRM_EX_DISCONNECT, true, NULL);
 194     return pcmk_rc_ok;
 195 }
 196 
 197 static pcmk__message_entry_t fmt_functions[] = {
 198     { "crm-mon-disconnected", "default", crm_mon_disconnected_default },
 199     { "crm-mon-disconnected", "html", crm_mon_disconnected_html },
 200     { "crm-mon-disconnected", "text", crm_mon_disconnected_text },
 201     { "crm-mon-disconnected", "xml", crm_mon_disconnected_xml },
 202     { NULL, NULL, NULL },
 203 };
 204 
 205 /* Define exit codes for monitoring-compatible output
 206  * For nagios plugins, the possibilities are
 207  * OK=0, WARN=1, CRIT=2, and UNKNOWN=3
 208  */
 209 #define MON_STATUS_WARN    CRM_EX_ERROR
 210 #define MON_STATUS_CRIT    CRM_EX_INVALID_PARAM
 211 #define MON_STATUS_UNKNOWN CRM_EX_UNIMPLEMENT_FEATURE
 212 
 213 #define RECONNECT_MSECS 5000
 214 
 215 struct {
 216     guint reconnect_ms;
 217     enum mon_exec_mode exec_mode;
 218     gboolean fence_connect;
 219     gboolean print_pending;
 220     gboolean show_bans;
 221     gboolean watch_fencing;
 222     char *pid_file;
 223     char *external_agent;
 224     char *external_recipient;
 225     char *neg_location_prefix;
 226     char *only_node;
 227     char *only_rsc;
 228     GSList *user_includes_excludes;
 229     GSList *includes_excludes;
 230 } options = {
 231     .reconnect_ms = RECONNECT_MSECS,
 232     .exec_mode = mon_exec_unset,
 233     .fence_connect = TRUE,
 234 };
 235 
 236 static crm_exit_t clean_up(crm_exit_t exit_code);
 237 static void crm_diff_update(const char *event, xmlNode * msg);
 238 static void clean_up_on_connection_failure(int rc);
 239 static int mon_refresh_display(gpointer user_data);
 240 static int setup_cib_connection(void);
 241 static int setup_fencer_connection(void);
 242 static int setup_api_connections(void);
 243 static void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
 244 static void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
 245 static void refresh_after_event(gboolean data_updated, gboolean enforce);
 246 
 247 static uint32_t
 248 all_includes(mon_output_format_t fmt) {
     /* [previous][next][first][last][top][bottom][index][help] */
 249     if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
 250         return ~pcmk_section_options;
 251     } else {
 252         return pcmk_section_all;
 253     }
 254 }
 255 
 256 static uint32_t
 257 default_includes(mon_output_format_t fmt) {
     /* [previous][next][first][last][top][bottom][index][help] */
 258     switch (fmt) {
 259         case mon_output_monitor:
 260         case mon_output_plain:
 261         case mon_output_console:
 262         case mon_output_html:
 263         case mon_output_cgi:
 264             return pcmk_section_summary
 265                    |pcmk_section_nodes
 266                    |pcmk_section_resources
 267                    |pcmk_section_failures;
 268 
 269         case mon_output_xml:
 270             return all_includes(fmt);
 271 
 272         default:
 273             return 0;
 274     }
 275 }
 276 
 277 struct {
 278     const char *name;
 279     uint32_t bit;
 280 } sections[] = {
 281     { "attributes", pcmk_section_attributes },
 282     { "bans", pcmk_section_bans },
 283     { "counts", pcmk_section_counts },
 284     { "dc", pcmk_section_dc },
 285     { "failcounts", pcmk_section_failcounts },
 286     { "failures", pcmk_section_failures },
 287     { PCMK__VALUE_FENCING, pcmk_section_fencing_all },
 288     { "fencing-failed", pcmk_section_fence_failed },
 289     { "fencing-pending", pcmk_section_fence_pending },
 290     { "fencing-succeeded", pcmk_section_fence_worked },
 291     { "maint-mode", pcmk_section_maint_mode },
 292     { "nodes", pcmk_section_nodes },
 293     { "operations", pcmk_section_operations },
 294     { "options", pcmk_section_options },
 295     { "resources", pcmk_section_resources },
 296     { "stack", pcmk_section_stack },
 297     { "summary", pcmk_section_summary },
 298     { "tickets", pcmk_section_tickets },
 299     { "times", pcmk_section_times },
 300     { NULL }
 301 };
 302 
 303 static uint32_t
 304 find_section_bit(const char *name) {
     /* [previous][next][first][last][top][bottom][index][help] */
 305     for (int i = 0; sections[i].name != NULL; i++) {
 306         if (pcmk__str_eq(sections[i].name, name, pcmk__str_casei)) {
 307             return sections[i].bit;
 308         }
 309     }
 310 
 311     return 0;
 312 }
 313 
 314 static gboolean
 315 apply_exclude(const gchar *excludes, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 316     char **parts = NULL;
 317     gboolean result = TRUE;
 318 
 319     parts = g_strsplit(excludes, ",", 0);
 320     for (char **s = parts; *s != NULL; s++) {
 321         uint32_t bit = find_section_bit(*s);
 322 
 323         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
 324             show = 0;
 325         } else if (pcmk__str_eq(*s, PCMK__VALUE_NONE, pcmk__str_none)) {
 326             show = all_includes(output_format);
 327         } else if (bit != 0) {
 328             show &= ~bit;
 329         } else {
 330             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 331                         "--exclude options: all, attributes, bans, counts, dc, "
 332                         "failcounts, failures, fencing, fencing-failed, "
 333                         "fencing-pending, fencing-succeeded, maint-mode, nodes, "
 334                         PCMK__VALUE_NONE ", operations, options, resources, "
 335                         "stack, summary, tickets, times");
 336             result = FALSE;
 337             break;
 338         }
 339     }
 340     g_strfreev(parts);
 341     return result;
 342 }
 343 
 344 static gboolean
 345 apply_include(const gchar *includes, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 346     char **parts = NULL;
 347     gboolean result = TRUE;
 348 
 349     parts = g_strsplit(includes, ",", 0);
 350     for (char **s = parts; *s != NULL; s++) {
 351         uint32_t bit = find_section_bit(*s);
 352 
 353         if (pcmk__str_eq(*s, "all", pcmk__str_none)) {
 354             show = all_includes(output_format);
 355         } else if (pcmk__starts_with(*s, "bans")) {
 356             show |= pcmk_section_bans;
 357             if (options.neg_location_prefix != NULL) {
 358                 free(options.neg_location_prefix);
 359                 options.neg_location_prefix = NULL;
 360             }
 361 
 362             if (strlen(*s) > 4 && (*s)[4] == ':') {
 363                 options.neg_location_prefix = strdup(*s+5);
 364             }
 365         } else if (pcmk__str_any_of(*s, "default", "defaults", NULL)) {
 366             show |= default_includes(output_format);
 367         } else if (pcmk__str_eq(*s, PCMK__VALUE_NONE, pcmk__str_none)) {
 368             show = 0;
 369         } else if (bit != 0) {
 370             show |= bit;
 371         } else {
 372             g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
 373                         "--include options: all, attributes, bans[:PREFIX], counts, dc, "
 374                         "default, failcounts, failures, fencing, fencing-failed, "
 375                         "fencing-pending, fencing-succeeded, maint-mode, nodes, "
 376                         PCMK__VALUE_NONE ", operations, options, resources, "
 377                         "stack, summary, tickets, times");
 378             result = FALSE;
 379             break;
 380         }
 381     }
 382     g_strfreev(parts);
 383     return result;
 384 }
 385 
 386 static gboolean
 387 apply_include_exclude(GSList *lst, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
 388     gboolean rc = TRUE;
 389     GSList *node = lst;
 390 
 391     while (node != NULL) {
 392         char *s = node->data;
 393 
 394         if (pcmk__starts_with(s, "--include=")) {
 395             rc = apply_include(s+10, error);
 396         } else if (pcmk__starts_with(s, "-I=")) {
 397             rc = apply_include(s+3, error);
 398         } else if (pcmk__starts_with(s, "--exclude=")) {
 399             rc = apply_exclude(s+10, error);
 400         } else if (pcmk__starts_with(s, "-U=")) {
 401             rc = apply_exclude(s+3, error);
 402         }
 403 
 404         if (rc != TRUE) {
 405             break;
 406         }
 407 
 408         node = node->next;
 409     }
 410 
 411     return rc;
 412 }
 413 
 414 static gboolean
 415 user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 416     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 417 
 418     options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
 419     return TRUE;
 420 }
 421 
 422 static gboolean
 423 include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 424     char *s = crm_strdup_printf("%s=%s", option_name, optarg);
 425 
 426     options.includes_excludes = g_slist_append(options.includes_excludes, s);
 427     return TRUE;
 428 }
 429 
 430 static gboolean
 431 as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 432     pcmk__str_update(&args->output_ty, "html");
 433     output_format = mon_output_cgi;
 434     options.exec_mode = mon_exec_one_shot;
 435     return TRUE;
 436 }
 437 
 438 static gboolean
 439 as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 440     pcmk__str_update(&args->output_dest, optarg);
 441     pcmk__str_update(&args->output_ty, "html");
 442     output_format = mon_output_html;
 443     umask(S_IWGRP | S_IWOTH);   // World-readable HTML
 444     return TRUE;
 445 }
 446 
 447 static gboolean
 448 as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 449     pcmk__str_update(&args->output_ty, "text");
 450     output_format = mon_output_monitor;
 451     options.exec_mode = mon_exec_one_shot;
 452     return TRUE;
 453 }
 454 
 455 static gboolean
 456 as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 457     pcmk__str_update(&args->output_ty, "xml");
 458     output_format = mon_output_legacy_xml;
 459     return TRUE;
 460 }
 461 
 462 static gboolean
 463 fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 464     if (optarg == NULL) {
 465         interactive_fence_level = 2;
 466     } else {
 467         pcmk__scan_min_int(optarg, &interactive_fence_level, 0);
 468     }
 469 
 470     switch (interactive_fence_level) {
 471         case 3:
 472             options.fence_connect = TRUE;
 473             fence_history = pcmk__fence_history_full;
 474             return include_exclude_cb("--include", PCMK__VALUE_FENCING, data,
 475                                       err);
 476 
 477         case 2:
 478             options.fence_connect = TRUE;
 479             fence_history = pcmk__fence_history_full;
 480             return include_exclude_cb("--include", PCMK__VALUE_FENCING, data,
 481                                       err);
 482 
 483         case 1:
 484             options.fence_connect = TRUE;
 485             fence_history = pcmk__fence_history_full;
 486             return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
 487 
 488         case 0:
 489             options.fence_connect = FALSE;
 490             fence_history = pcmk__fence_history_none;
 491             return include_exclude_cb("--exclude", PCMK__VALUE_FENCING, data,
 492                                       err);
 493 
 494         default:
 495             g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
 496             return FALSE;
 497     }
 498 }
 499 
 500 static gboolean
 501 group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 502     show_opts |= pcmk_show_rscs_by_node;
 503     return TRUE;
 504 }
 505 
 506 static gboolean
 507 hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 508     return user_include_exclude_cb("--exclude", "summary", data, err);
 509 }
 510 
 511 static gboolean
 512 inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 513     show_opts |= pcmk_show_inactive_rscs;
 514     return TRUE;
 515 }
 516 
 517 static gboolean
 518 no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 519     pcmk__str_update(&args->output_ty, "text");
 520     output_format = mon_output_plain;
 521     return TRUE;
 522 }
 523 
 524 static gboolean
 525 print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 526     show_opts |= pcmk_show_brief;
 527     return TRUE;
 528 }
 529 
 530 static gboolean
 531 print_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 532     show_opts |= pcmk_show_details;
 533     return TRUE;
 534 }
 535 
 536 static gboolean
 537 print_description_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 538     show_opts |= pcmk_show_description;
 539     return TRUE;
 540 }
 541 
 542 static gboolean
 543 print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 544     show_opts |= pcmk_show_timing;
 545     return user_include_exclude_cb("--include", "operations", data, err);
 546 }
 547 
 548 static gboolean
 549 reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 550     int rc = crm_get_msec(optarg);
 551 
 552     if (rc == -1) {
 553         g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
 554         return FALSE;
 555     } else {
 556         options.reconnect_ms = crm_parse_interval_spec(optarg);
 557 
 558         if (options.exec_mode != mon_exec_daemonized) {
 559             // Reconnect interval applies to daemonized too, so don't override
 560             options.exec_mode = mon_exec_update;
 561         }
 562     }
 563 
 564     return TRUE;
 565 }
 566 
 567 /*!
 568  * \internal
 569  * \brief Enable one-shot mode
 570  *
 571  * \param[in]  option_name  Name of option being parsed (ignored)
 572  * \param[in]  optarg       Value to be parsed (ignored)
 573  * \param[in]  data         User data (ignored)
 574  * \param[out] err          Where to store error (ignored)
 575  */
 576 static gboolean
 577 one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 578             GError **err)
 579 {
 580     options.exec_mode = mon_exec_one_shot;
 581     return TRUE;
 582 }
 583 
 584 /*!
 585  * \internal
 586  * \brief Enable daemonized mode
 587  *
 588  * \param[in]  option_name  Name of option being parsed (ignored)
 589  * \param[in]  optarg       Value to be parsed (ignored)
 590  * \param[in]  data         User data (ignored)
 591  * \param[out] err          Where to store error (ignored)
 592  */
 593 static gboolean
 594 daemonize_cb(const gchar *option_name, const gchar *optarg, gpointer data,
     /* [previous][next][first][last][top][bottom][index][help] */
 595              GError **err)
 596 {
 597     options.exec_mode = mon_exec_daemonized;
 598     return TRUE;
 599 }
 600 
 601 static gboolean
 602 show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 603     return user_include_exclude_cb("--include", "attributes", data, err);
 604 }
 605 
 606 static gboolean
 607 show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 608     if (optarg != NULL) {
 609         char *s = crm_strdup_printf("bans:%s", optarg);
 610         gboolean rc = user_include_exclude_cb("--include", s, data, err);
 611         free(s);
 612         return rc;
 613     } else {
 614         return user_include_exclude_cb("--include", "bans", data, err);
 615     }
 616 }
 617 
 618 static gboolean
 619 show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 620     return user_include_exclude_cb("--include", "failcounts", data, err);
 621 }
 622 
 623 static gboolean
 624 show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 625     return user_include_exclude_cb("--include", "failcounts,operations", data, err);
 626 }
 627 
 628 static gboolean
 629 show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 630     return user_include_exclude_cb("--include", "tickets", data, err);
 631 }
 632 
 633 static gboolean
 634 use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
     /* [previous][next][first][last][top][bottom][index][help] */
 635     setenv("CIB_file", optarg, 1);
 636     options.exec_mode = mon_exec_one_shot;
 637     return TRUE;
 638 }
 639 
 640 #define INDENT "                                    "
 641 
 642 /* *INDENT-OFF* */
 643 static GOptionEntry addl_entries[] = {
 644     { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb,
 645       "Update frequency (default is 5 seconds)",
 646       "TIMESPEC" },
 647 
 648     { "one-shot", '1', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 649       one_shot_cb,
 650       "Display the cluster status once and exit",
 651       NULL },
 652 
 653     { "daemonize", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 654       daemonize_cb,
 655       "Run in the background as a daemon.\n"
 656       INDENT "Requires at least one of --output-to and --external-agent.",
 657       NULL },
 658 
 659     { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file,
 660       "(Advanced) Daemon pid file location",
 661       "FILE" },
 662 
 663     { "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent,
 664       "A program to run when resource operations take place",
 665       "FILE" },
 666 
 667     { "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient,
 668       "A recipient for your program (assuming you want the program to send something to someone).",
 669       "RCPT" },
 670 
 671     { "watch-fencing", 'W', 0, G_OPTION_ARG_NONE, &options.watch_fencing,
 672       "Listen for fencing events. For use with --external-agent.",
 673       NULL },
 674 
 675     { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
 676       NULL,
 677       NULL },
 678 
 679     { NULL }
 680 };
 681 
 682 static GOptionEntry display_entries[] = {
 683     { "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
 684       "A list of sections to include in the output.\n"
 685       INDENT "See `Output Control` help for more information.",
 686       "SECTION(s)" },
 687 
 688     { "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
 689       "A list of sections to exclude from the output.\n"
 690       INDENT "See `Output Control` help for more information.",
 691       "SECTION(s)" },
 692 
 693     { "node", 0, 0, G_OPTION_ARG_STRING, &options.only_node,
 694       "When displaying information about nodes, show only what's related to the given\n"
 695       INDENT "node, or to all nodes tagged with the given tag",
 696       "NODE" },
 697 
 698     { "resource", 0, 0, G_OPTION_ARG_STRING, &options.only_rsc,
 699       "When displaying information about resources, show only what's related to the given\n"
 700       INDENT "resource, or to all resources tagged with the given tag",
 701       "RSC" },
 702 
 703     { "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb,
 704       "Group resources by node",
 705       NULL },
 706 
 707     { "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb,
 708       "Display inactive resources",
 709       NULL },
 710 
 711     { "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb,
 712       "Display resource fail counts",
 713       NULL },
 714 
 715     { "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb,
 716       "Display resource operation history",
 717       NULL },
 718 
 719     { "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb,
 720       "Display resource operation history with timing details",
 721       NULL },
 722 
 723     { "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb,
 724       "Display cluster tickets",
 725       NULL },
 726 
 727     { "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb,
 728       "Show fence history:\n"
 729       INDENT "0=off, 1=failures and pending (default without option),\n"
 730       INDENT "2=add successes (default without value for option),\n"
 731       INDENT "3=show full history without reduction to most recent of each flavor",
 732       "LEVEL" },
 733 
 734     { "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb,
 735       "Display negative location constraints [optionally filtered by id prefix]",
 736       NULL },
 737 
 738     { "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb,
 739       "Display node attributes",
 740       NULL },
 741 
 742     { "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb,
 743       "Hide all headers",
 744       NULL },
 745 
 746     { "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_detail_cb,
 747       "Show more details (node IDs, individual clone instances)",
 748       NULL },
 749 
 750     { "show-description", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_description_cb,
 751       "Show resource descriptions",
 752       NULL },
 753 
 754     { "brief", 'b', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_brief_cb,
 755       "Brief output",
 756       NULL },
 757 
 758     { "pending", 'j', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.print_pending,
 759       "Display pending state if 'record-pending' is enabled",
 760       NULL },
 761 
 762     { NULL }
 763 };
 764 
 765 static GOptionEntry deprecated_entries[] = {
 766     { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
 767       "Write cluster status to the named HTML file.\n"
 768       INDENT "Use --output-as=html --output-to=FILE instead.",
 769       "FILE" },
 770 
 771     { "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb,
 772       "Write cluster status as XML to stdout. This will enable one-shot mode.\n"
 773       INDENT "Use --output-as=xml instead.",
 774       NULL },
 775 
 776     { "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
 777       as_simple_cb,
 778       "Display the cluster status once as a simple one line output\n"
 779       INDENT "(suitable for nagios)",
 780       NULL },
 781 
 782     { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
 783       "Disable the use of ncurses.\n"
 784       INDENT "Use --output-as=text instead.",
 785       NULL },
 786 
 787     { "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb,
 788       "Web mode with output suitable for CGI (preselected when run as *.cgi).\n"
 789       INDENT "Use --output-as=html --html-cgi instead.",
 790       NULL },
 791 
 792     { NULL }
 793 };
 794 /* *INDENT-ON* */
 795 
 796 /* Reconnect to the CIB and fencing agent after reconnect_ms has passed.  This sounds
 797  * like it would be more broadly useful, but only ever happens after a disconnect via
 798  * mon_cib_connection_destroy.
 799  */
 800 static gboolean
 801 reconnect_after_timeout(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 802 {
 803 #if CURSES_ENABLED
 804     if (output_format == mon_output_console) {
 805         clear();
 806         refresh();
 807     }
 808 #endif
 809 
 810     out->transient(out, "Reconnecting...");
 811     if (setup_api_connections() == pcmk_rc_ok) {
 812         // Trigger redrawing the screen (needs reconnect_timer == 0)
 813         reconnect_timer = 0;
 814         refresh_after_event(FALSE, TRUE);
 815         return G_SOURCE_REMOVE;
 816     }
 817 
 818     out->message(out, "crm-mon-disconnected",
 819                  "Latest connection attempt failed", pcmkd_state);
 820 
 821     reconnect_timer = g_timeout_add(options.reconnect_ms,
 822                                     reconnect_after_timeout, NULL);
 823     return G_SOURCE_REMOVE;
 824 }
 825 
 826 /* Called from various places when we are disconnected from the CIB or from the
 827  * fencing agent.  If the CIB connection is still valid, this function will also
 828  * attempt to sign off and reconnect.
 829  */
 830 static void
 831 mon_cib_connection_destroy(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 832 {
 833     const char *msg = "Connection to the cluster lost";
 834 
 835     pcmkd_state = pcmk_pacemakerd_state_invalid;
 836 
 837     /* No crm-mon-disconnected message for console; a working implementation
 838      * is not currently worth the effort
 839      */
 840     out->transient(out, "%s", msg);
 841 
 842     out->message(out, "crm-mon-disconnected", msg, pcmkd_state);
 843 
 844     if (refresh_timer != NULL) {
 845         /* we'll trigger a refresh after reconnect */
 846         mainloop_timer_stop(refresh_timer);
 847     }
 848     if (reconnect_timer) {
 849         /* we'll trigger a new reconnect-timeout at the end */
 850         g_source_remove(reconnect_timer);
 851         reconnect_timer = 0;
 852     }
 853 
 854     /* the client API won't properly reconnect notifications if they are still
 855      * in the table - so remove them
 856      */
 857     stonith_api_delete(st);
 858     st = NULL;
 859 
 860     if (cib) {
 861         cib->cmds->signoff(cib);
 862         reconnect_timer = g_timeout_add(options.reconnect_ms,
 863                                         reconnect_after_timeout, NULL);
 864     }
 865 }
 866 
 867 /* Signal handler installed into the mainloop for normal program shutdown */
 868 static void
 869 mon_shutdown(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 870 {
 871     clean_up(CRM_EX_OK);
 872 }
 873 
 874 #if CURSES_ENABLED
 875 static volatile sighandler_t ncurses_winch_handler;
 876 
 877 /* Signal handler installed the regular way (not into the main loop) for when
 878  * the screen is resized.  Commonly, this happens when running in an xterm and
 879  * the user changes its size.
 880  */
 881 static void
 882 mon_winresize(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 883 {
 884     static int not_done;
 885     int lines = 0, cols = 0;
 886 
 887     if (!not_done++) {
 888         if (ncurses_winch_handler)
 889             /* the original ncurses WINCH signal handler does the
 890              * magic of retrieving the new window size;
 891              * otherwise, we'd have to use ioctl or tgetent */
 892             (*ncurses_winch_handler) (SIGWINCH);
 893         getmaxyx(stdscr, lines, cols);
 894         resizeterm(lines, cols);
 895         /* Alert the mainloop code we'd like the refresh_trigger to run next
 896          * time the mainloop gets around to checking.
 897          */
 898         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
 899     }
 900     not_done--;
 901 }
 902 #endif
 903 
 904 static int
 905 setup_fencer_connection(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 906 {
 907     int rc = pcmk_ok;
 908 
 909     if (options.fence_connect && st == NULL) {
 910         st = stonith_api_new();
 911     }
 912 
 913     if (!options.fence_connect || st == NULL || st->state != stonith_disconnected) {
 914         return rc;
 915     }
 916 
 917     rc = st->cmds->connect(st, crm_system_name, NULL);
 918     if (rc == pcmk_ok) {
 919         crm_trace("Setting up stonith callbacks");
 920         if (options.watch_fencing) {
 921             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
 922                                             mon_st_callback_event);
 923             st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
 924         } else {
 925             st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
 926                                             mon_st_callback_display);
 927             st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
 928         }
 929     } else {
 930         stonith_api_delete(st);
 931         st = NULL;
 932     }
 933 
 934     return rc;
 935 }
 936 
 937 static int
 938 setup_cib_connection(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 939 {
 940     int rc = pcmk_rc_ok;
 941 
 942     CRM_CHECK(cib != NULL, return EINVAL);
 943 
 944     if (cib->state != cib_disconnected) {
 945         // Already connected with notifications registered for CIB updates
 946         return rc;
 947     }
 948 
 949     rc = cib__signon_query(out, &cib, &current_cib);
 950 
 951     if (rc == pcmk_rc_ok) {
 952         rc = pcmk_legacy2rc(cib->cmds->set_connection_dnotify(cib,
 953             mon_cib_connection_destroy));
 954         if (rc == EPROTONOSUPPORT) {
 955             out->err(out,
 956                      "CIB client does not support connection loss "
 957                      "notifications; crm_mon will be unable to reconnect after "
 958                      "connection loss");
 959             rc = pcmk_rc_ok;
 960         }
 961 
 962         if (rc == pcmk_rc_ok) {
 963             cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY,
 964                                            crm_diff_update);
 965             rc = pcmk_legacy2rc(cib->cmds->add_notify_callback(cib,
 966                                     T_CIB_DIFF_NOTIFY, crm_diff_update));
 967         }
 968 
 969         if (rc != pcmk_rc_ok) {
 970             if (rc == EPROTONOSUPPORT) {
 971                 out->err(out,
 972                          "CIB client does not support CIB diff "
 973                          "notifications");
 974             } else {
 975                 out->err(out, "CIB diff notification setup failed");
 976             }
 977 
 978             out->err(out, "Cannot monitor CIB changes; exiting");
 979             cib__clean_up_connection(&cib);
 980             stonith_api_delete(st);
 981             st = NULL;
 982         }
 983     }
 984     return rc;
 985 }
 986 
 987 /* This is used to set up the fencing options after the interactive UI has been stared.
 988  * fence_history_cb can't be used because it builds up a list of includes/excludes that
 989  * then have to be processed with apply_include_exclude and that could affect other
 990  * things.
 991  */
 992 static void
 993 set_fencing_options(int level)
     /* [previous][next][first][last][top][bottom][index][help] */
 994 {
 995     switch (level) {
 996         case 3:
 997             options.fence_connect = TRUE;
 998             fence_history = pcmk__fence_history_full;
 999             show |= pcmk_section_fencing_all;
1000             break;
1001 
1002         case 2:
1003             options.fence_connect = TRUE;
1004             fence_history = pcmk__fence_history_full;
1005             show |= pcmk_section_fencing_all;
1006             break;
1007 
1008         case 1:
1009             options.fence_connect = TRUE;
1010             fence_history = pcmk__fence_history_full;
1011             show |= pcmk_section_fence_failed | pcmk_section_fence_pending;
1012             break;
1013 
1014         default:
1015             interactive_fence_level = 0;
1016             options.fence_connect = FALSE;
1017             fence_history = pcmk__fence_history_none;
1018             show &= ~pcmk_section_fencing_all;
1019             break;
1020     }
1021 }
1022 
1023 static int
1024 setup_api_connections(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1025 {
1026     int rc = pcmk_rc_ok;
1027 
1028     CRM_CHECK(cib != NULL, return EINVAL);
1029 
1030     if (cib->state != cib_disconnected) {
1031         return rc;
1032     }
1033 
1034     if (cib->variant == cib_native) {
1035         rc = pcmk__pacemakerd_status(out, crm_system_name,
1036                                      options.reconnect_ms / 2, false,
1037                                      &pcmkd_state);
1038         if (rc != pcmk_rc_ok) {
1039             return rc;
1040         }
1041 
1042         switch (pcmkd_state) {
1043             case pcmk_pacemakerd_state_running:
1044             case pcmk_pacemakerd_state_remote:
1045             case pcmk_pacemakerd_state_shutting_down:
1046                 /* Fencer and CIB may still be available while shutting down or
1047                  * running on a Pacemaker Remote node
1048                  */
1049                 break;
1050             default:
1051                 // Fencer and CIB are definitely unavailable
1052                 return ENOTCONN;
1053         }
1054 
1055         setup_fencer_connection();
1056     }
1057 
1058     rc = setup_cib_connection();
1059     return rc;
1060 }
1061 
1062 #if CURSES_ENABLED
1063 static const char *
1064 get_option_desc(char c)
     /* [previous][next][first][last][top][bottom][index][help] */
1065 {
1066     const char *desc = "No help available";
1067 
1068     for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
1069         if (entry->short_name == c) {
1070             desc = entry->description;
1071             break;
1072         }
1073     }
1074     return desc;
1075 }
1076 
1077 #define print_option_help(out, option, condition) \
1078     curses_formatted_printf(out, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
1079 
1080 /* This function is called from the main loop when there is something to be read
1081  * on stdin, like an interactive user's keystroke.  All it does is read the keystroke,
1082  * set flags (or show the page showing which keystrokes are valid), and redraw the
1083  * screen.  It does not do anything with connections to the CIB or fencing agent
1084  * agent what would happen in mon_refresh_display.
1085  */
1086 static gboolean
1087 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1088 {
1089     int c;
1090     gboolean config_mode = FALSE;
1091 
1092     while (1) {
1093 
1094         /* Get user input */
1095         c = getchar();
1096 
1097         switch (c) {
1098             case 'm':
1099                 interactive_fence_level++;
1100                 if (interactive_fence_level > 3) {
1101                     interactive_fence_level = 0;
1102                 }
1103 
1104                 set_fencing_options(interactive_fence_level);
1105                 break;
1106             case 'c':
1107                 show ^= pcmk_section_tickets;
1108                 break;
1109             case 'f':
1110                 show ^= pcmk_section_failcounts;
1111                 break;
1112             case 'n':
1113                 show_opts ^= pcmk_show_rscs_by_node;
1114                 break;
1115             case 'o':
1116                 show ^= pcmk_section_operations;
1117                 if (!pcmk_is_set(show, pcmk_section_operations)) {
1118                     show_opts &= ~pcmk_show_timing;
1119                 }
1120                 break;
1121             case 'r':
1122                 show_opts ^= pcmk_show_inactive_rscs;
1123                 break;
1124             case 'R':
1125                 show_opts ^= pcmk_show_details;
1126 #ifdef PCMK__COMPAT_2_0
1127                 // Keep failed action output the same as 2.0.x
1128                 show_opts |= pcmk_show_failed_detail;
1129 #endif
1130                 break;
1131             case 't':
1132                 show_opts ^= pcmk_show_timing;
1133                 if (pcmk_is_set(show_opts, pcmk_show_timing)) {
1134                     show |= pcmk_section_operations;
1135                 }
1136                 break;
1137             case 'A':
1138                 show ^= pcmk_section_attributes;
1139                 break;
1140             case 'L':
1141                 show ^= pcmk_section_bans;
1142                 break;
1143             case 'D':
1144                 /* If any header is shown, clear them all, otherwise set them all */
1145                 if (pcmk_any_flags_set(show, pcmk_section_summary)) {
1146                     show &= ~pcmk_section_summary;
1147                 } else {
1148                     show |= pcmk_section_summary;
1149                 }
1150                 /* Regardless, we don't show options in console mode. */
1151                 show &= ~pcmk_section_options;
1152                 break;
1153             case 'b':
1154                 show_opts ^= pcmk_show_brief;
1155                 break;
1156             case 'j':
1157                 show_opts ^= pcmk_show_pending;
1158                 break;
1159             case '?':
1160                 config_mode = TRUE;
1161                 break;
1162             default:
1163                 /* All other keys just redraw the screen. */
1164                 goto refresh;
1165         }
1166 
1167         if (!config_mode)
1168             goto refresh;
1169 
1170         clear();
1171         refresh();
1172 
1173         curses_formatted_printf(out, "%s", "Display option change mode\n");
1174         print_option_help(out, 'c', pcmk_is_set(show, pcmk_section_tickets));
1175         print_option_help(out, 'f', pcmk_is_set(show, pcmk_section_failcounts));
1176         print_option_help(out, 'n', pcmk_is_set(show_opts, pcmk_show_rscs_by_node));
1177         print_option_help(out, 'o', pcmk_is_set(show, pcmk_section_operations));
1178         print_option_help(out, 'r', pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
1179         print_option_help(out, 't', pcmk_is_set(show_opts, pcmk_show_timing));
1180         print_option_help(out, 'A', pcmk_is_set(show, pcmk_section_attributes));
1181         print_option_help(out, 'L', pcmk_is_set(show, pcmk_section_bans));
1182         print_option_help(out, 'D', !pcmk_is_set(show, pcmk_section_summary));
1183 #ifdef PCMK__COMPAT_2_0
1184         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details & ~pcmk_show_failed_detail));
1185 #else
1186         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details));
1187 #endif
1188         print_option_help(out, 'b', pcmk_is_set(show_opts, pcmk_show_brief));
1189         print_option_help(out, 'j', pcmk_is_set(show_opts, pcmk_show_pending));
1190         curses_formatted_printf(out, "%d m: \t%s\n", interactive_fence_level, get_option_desc('m'));
1191         curses_formatted_printf(out, "%s", "\nToggle fields via field letter, type any other key to return\n");
1192     }
1193 
1194 refresh:
1195     refresh_after_event(FALSE, TRUE);
1196 
1197     return TRUE;
1198 }
1199 #endif  // CURSES_ENABLED
1200 
1201 // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
1202 static void
1203 avoid_zombies(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1204 {
1205     struct sigaction sa;
1206 
1207     memset(&sa, 0, sizeof(struct sigaction));
1208     if (sigemptyset(&sa.sa_mask) < 0) {
1209         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
1210         return;
1211     }
1212     sa.sa_handler = SIG_IGN;
1213     sa.sa_flags = SA_RESTART|SA_NOCLDWAIT;
1214     if (sigaction(SIGCHLD, &sa, NULL) < 0) {
1215         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
1216     }
1217 }
1218 
1219 static GOptionContext *
1220 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
1221     GOptionContext *context = NULL;
1222 
1223     GOptionEntry extra_prog_entries[] = {
1224         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
1225           "Be less descriptive in output.",
1226           NULL },
1227 
1228         { NULL }
1229     };
1230 
1231 #if CURSES_ENABLED
1232     const char *fmts = "console (default), html, text, xml, none";
1233 #else
1234     const char *fmts = "text (default), html, xml, none";
1235 #endif // CURSES_ENABLED
1236     const char *desc = NULL;
1237 
1238     desc = "Notes:\n\n"
1239            "If this program is called as crm_mon.cgi, --output-as=html and\n"
1240            "--html-cgi are automatically added to the command line\n"
1241            "arguments.\n\n"
1242 
1243            "Time Specification:\n\n"
1244            "The TIMESPEC in any command line option can be specified in many\n"
1245            "different formats. It can be an integer number of seconds, a\n"
1246            "number plus units (us/usec/ms/msec/s/sec/m/min/h/hr), or an ISO\n"
1247            "8601 period specification.\n\n"
1248 
1249            "Output Control:\n\n"
1250            "By default, a particular set of sections are written to the\n"
1251            "output destination. The default varies based on the output\n"
1252            "format: XML includes all sections by default, while other output\n"
1253            "formats include less. This set can be modified with the --include\n"
1254            "and --exclude command line options. Each option may be passed\n"
1255            "multiple times, and each can specify a comma-separated list of\n"
1256            "sections. The options are applied to the default set, in order\n"
1257            "from left to right as they are passed on the command line. For a\n"
1258            "list of valid sections, pass --include=list or --exclude=list.\n\n"
1259 
1260            "Interactive Use:\n\n"
1261 #if CURSES_ENABLED
1262            "When run interactively, crm_mon can be told to hide and show\n"
1263            "various sections of output. To see a help screen explaining the\n"
1264            "options, press '?'. Any key stroke aside from those listed will\n"
1265            "cause the screen to refresh.\n\n"
1266 #else
1267            "The local installation of Pacemaker was built without support for\n"
1268            "interactive (console) mode. A curses library must be available at\n"
1269            "build time to support interactive mode.\n\n"
1270 #endif // CURSES_ENABLED
1271 
1272            "Examples:\n\n"
1273 #if CURSES_ENABLED
1274            "Display the cluster status on the console with updates as they\n"
1275            "occur:\n\n"
1276            "\tcrm_mon\n\n"
1277 #endif // CURSES_ENABLED
1278 
1279            "Display the cluster status once and exit:\n\n"
1280            "\tcrm_mon -1\n\n"
1281 
1282            "Display the cluster status, group resources by node, and include\n"
1283            "inactive resources in the list:\n\n"
1284            "\tcrm_mon --group-by-node --inactive\n\n"
1285 
1286            "Start crm_mon as a background daemon and have it write the\n"
1287            "cluster status to an HTML file:\n\n"
1288            "\tcrm_mon --daemonize --output-as html "
1289            "--output-to /path/to/docroot/filename.html\n\n"
1290 
1291            "Display the cluster status as XML:\n\n"
1292            "\tcrm_mon --output-as xml\n\n";
1293 
1294     context = pcmk__build_arg_context(args, fmts, group, NULL);
1295     pcmk__add_main_args(context, extra_prog_entries);
1296     g_option_context_set_description(context, desc);
1297 
1298     pcmk__add_arg_group(context, "display", "Display Options:",
1299                         "Show display options", display_entries);
1300     pcmk__add_arg_group(context, "additional", "Additional Options:",
1301                         "Show additional options", addl_entries);
1302     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
1303                         "Show deprecated options", deprecated_entries);
1304 
1305     return context;
1306 }
1307 
1308 /* If certain format options were specified, we want to set some extra
1309  * options.  We can just process these like they were given on the
1310  * command line.
1311  */
1312 static void
1313 add_output_args(void) {
     /* [previous][next][first][last][top][bottom][index][help] */
1314     GError *err = NULL;
1315 
1316     if (output_format == mon_output_plain) {
1317         if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
1318             g_propagate_error(&error, err);
1319             clean_up(CRM_EX_USAGE);
1320         }
1321     } else if (output_format == mon_output_cgi) {
1322         if (!pcmk__force_args(context, &err, "%s --html-cgi", g_get_prgname())) {
1323             g_propagate_error(&error, err);
1324             clean_up(CRM_EX_USAGE);
1325         }
1326     } else if (output_format == mon_output_xml) {
1327         if (!pcmk__force_args(context, &err, "%s --xml-simple-list --xml-substitute", g_get_prgname())) {
1328             g_propagate_error(&error, err);
1329             clean_up(CRM_EX_USAGE);
1330         }
1331     } else if (output_format == mon_output_legacy_xml) {
1332         output_format = mon_output_xml;
1333         if (!pcmk__force_args(context, &err, "%s --xml-legacy --xml-substitute", g_get_prgname())) {
1334             g_propagate_error(&error, err);
1335             clean_up(CRM_EX_USAGE);
1336         }
1337     }
1338 }
1339 
1340 /*!
1341  * \internal
1342  * \brief Set output format based on \p --output-as arguments and mode arguments
1343  *
1344  * When the deprecated output format arguments (\p --as-cgi, \p --as-html,
1345  * \p --simple-status, \p --as-xml) are parsed, callback functions set
1346  * \p output_format (and the umask if appropriate). If none of the deprecated
1347  * arguments were specified, this function does the same based on the current
1348  * \p --output-as arguments and the \p --one-shot and \p --daemonize arguments.
1349  *
1350  * \param[in,out] args  Command line arguments
1351  */
1352 static void
1353 reconcile_output_format(pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
1354 {
1355     if (output_format != mon_output_unset) {
1356         /* One of the deprecated arguments was used, and we're finished. Note
1357          * that this means the deprecated arguments take precedence.
1358          */
1359         return;
1360     }
1361 
1362     if (pcmk__str_eq(args->output_ty, "none", pcmk__str_none)) {
1363         output_format = mon_output_none;
1364 
1365     } else if (pcmk__str_eq(args->output_ty, "html", pcmk__str_none)) {
1366         output_format = mon_output_html;
1367         umask(S_IWGRP | S_IWOTH);   // World-readable HTML
1368 
1369     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
1370         output_format = mon_output_xml;
1371 
1372 #if CURSES_ENABLED
1373     } else if (pcmk__str_eq(args->output_ty, "console",
1374                             pcmk__str_null_matches)) {
1375         /* Console is the default format if no conflicting options are given.
1376          *
1377          * Use text output instead if one of the following conditions is met:
1378          * * We've requested daemonized or one-shot mode (console output is
1379          *   incompatible with modes other than mon_exec_update)
1380          * * We requested the version, which is effectively one-shot
1381          * * We specified a non-stdout output destination (console mode is
1382          *   compatible only with stdout)
1383          */
1384         if ((options.exec_mode == mon_exec_daemonized)
1385             || (options.exec_mode == mon_exec_one_shot)
1386             || args->version
1387             || !pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches)) {
1388 
1389             pcmk__str_update(&args->output_ty, "text");
1390             output_format = mon_output_plain;
1391         } else {
1392             pcmk__str_update(&args->output_ty, "console");
1393             output_format = mon_output_console;
1394             crm_enable_stderr(FALSE);
1395         }
1396 #endif // CURSES_ENABLED
1397 
1398     } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) {
1399         /* Text output was explicitly requested, or it's the default because
1400          * curses is not enabled
1401          */
1402         pcmk__str_update(&args->output_ty, "text");
1403         output_format = mon_output_plain;
1404     }
1405 
1406     // Otherwise, invalid format. Let pcmk__output_new() throw an error.
1407 }
1408 
1409 /*!
1410  * \internal
1411  * \brief Set execution mode to the output format's default if appropriate
1412  *
1413  * \param[in,out] args  Command line arguments
1414  */
1415 static void
1416 set_default_exec_mode(const pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
1417 {
1418     if (output_format == mon_output_console) {
1419         /* Update is the only valid mode for console, but set here instead of
1420          * reconcile_output_format() for isolation and consistency
1421          */
1422         options.exec_mode = mon_exec_update;
1423 
1424     } else if (options.exec_mode == mon_exec_unset) {
1425         // Default to one-shot mode for all other formats
1426         options.exec_mode = mon_exec_one_shot;
1427 
1428     } else if ((options.exec_mode == mon_exec_update)
1429                && pcmk__str_eq(args->output_dest, "-",
1430                                pcmk__str_null_matches)) {
1431         // If not using console format, update mode cannot be used with stdout
1432         options.exec_mode = mon_exec_one_shot;
1433     }
1434 }
1435 
1436 static void
1437 clean_up_on_connection_failure(int rc)
     /* [previous][next][first][last][top][bottom][index][help] */
1438 {
1439     if (output_format == mon_output_monitor) {
1440         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
1441                     pcmk_rc_str(rc));
1442         clean_up(MON_STATUS_CRIT);
1443     } else if (rc == ENOTCONN) {
1444         if (pcmkd_state == pcmk_pacemakerd_state_remote) {
1445             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: remote-node not connected to cluster");
1446         } else {
1447             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: cluster is not available on this node");
1448         }
1449     } else {
1450         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Connection to cluster failed: %s", pcmk_rc_str(rc));
1451     }
1452 
1453     clean_up(pcmk_rc2exitc(rc));
1454 }
1455 
1456 static void
1457 one_shot(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1458 {
1459     int rc = pcmk__status(out, cib, fence_history, show, show_opts,
1460                           options.only_node, options.only_rsc,
1461                           options.neg_location_prefix,
1462                           output_format == mon_output_monitor, 0);
1463 
1464     if (rc == pcmk_rc_ok) {
1465         clean_up(pcmk_rc2exitc(rc));
1466     } else {
1467         clean_up_on_connection_failure(rc);
1468     }
1469 }
1470 
1471 static void
1472 exit_on_invalid_cib(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1473 {
1474     if (cib != NULL) {
1475         return;
1476     }
1477 
1478     // Shouldn't really be possible
1479     g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Invalid CIB source");
1480     clean_up(CRM_EX_ERROR);
1481 }
1482 
1483 int
1484 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
1485 {
1486     int rc = pcmk_rc_ok;
1487     GOptionGroup *output_group = NULL;
1488 
1489     args = pcmk__new_common_args(SUMMARY);
1490     context = build_arg_context(args, &output_group);
1491     pcmk__register_formats(output_group, formats);
1492 
1493     options.pid_file = strdup("/tmp/ClusterMon.pid");
1494     pcmk__cli_init_logging("crm_mon", 0);
1495 
1496     // Avoid needing to wait for subprocesses forked for -E/--external-agent
1497     avoid_zombies();
1498 
1499     if (pcmk__ends_with_ext(argv[0], ".cgi")) {
1500         output_format = mon_output_cgi;
1501         options.exec_mode = mon_exec_one_shot;
1502     }
1503 
1504     processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
1505 
1506     fence_history_cb("--fence-history", "1", NULL, NULL);
1507 
1508     /* Set an HTML title regardless of what format we will eventually use.  This can't
1509      * be done in add_output_args.  That function is called after command line
1510      * arguments are processed in the next block, which means it'll override whatever
1511      * title the user provides.  Doing this here means the user can give their own
1512      * title on the command line.
1513      */
1514     if (!pcmk__force_args(context, &error, "%s --html-title \"Cluster Status\"",
1515                           g_get_prgname())) {
1516         return clean_up(CRM_EX_USAGE);
1517     }
1518 
1519     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1520         return clean_up(CRM_EX_USAGE);
1521     }
1522 
1523     for (int i = 0; i < args->verbosity; i++) {
1524         crm_bump_log_level(argc, argv);
1525     }
1526 
1527     if (!args->version) {
1528         if (args->quiet) {
1529             include_exclude_cb("--exclude", "times", NULL, NULL);
1530         }
1531 
1532         if (options.watch_fencing) {
1533             fence_history_cb("--fence-history", "0", NULL, NULL);
1534             options.fence_connect = TRUE;
1535         }
1536 
1537         /* create the cib-object early to be able to do further
1538          * decisions based on the cib-source
1539          */
1540         cib = cib_new();
1541 
1542         exit_on_invalid_cib();
1543 
1544         switch (cib->variant) {
1545             case cib_native:
1546                 // Everything (fencer, CIB, pcmkd status) should be available
1547                 break;
1548 
1549             case cib_file:
1550                 // Live fence history is not meaningful
1551                 fence_history_cb("--fence-history", "0", NULL, NULL);
1552 
1553                 /* Notifications are unsupported; nothing to monitor
1554                  * @COMPAT: Let setup_cib_connection() handle this by exiting?
1555                  */
1556                 options.exec_mode = mon_exec_one_shot;
1557                 break;
1558 
1559             case cib_remote:
1560                 // We won't receive any fencing updates
1561                 fence_history_cb("--fence-history", "0", NULL, NULL);
1562                 break;
1563 
1564             default:
1565                 /* something is odd */
1566                 exit_on_invalid_cib();
1567                 break;
1568         }
1569 
1570         if ((options.exec_mode == mon_exec_daemonized)
1571             && !options.external_agent
1572             && pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches)) {
1573 
1574             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
1575                         "--daemonize requires at least one of --output-to "
1576                         "(with value not set to '-') and --external-agent");
1577             return clean_up(CRM_EX_USAGE);
1578         }
1579     }
1580 
1581     reconcile_output_format(args);
1582     set_default_exec_mode(args);
1583     add_output_args();
1584 
1585     /* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
1586 
1587     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1588     if (rc != pcmk_rc_ok) {
1589         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
1590                     args->output_ty, pcmk_rc_str(rc));
1591         return clean_up(CRM_EX_ERROR);
1592     }
1593 
1594     /* If we had a valid format for pcmk__output_new(), output_format should be
1595      * set by now.
1596      */
1597     CRM_ASSERT(output_format != mon_output_unset);
1598 
1599     if (options.exec_mode == mon_exec_daemonized) {
1600         if (!options.external_agent && (output_format == mon_output_none)) {
1601             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
1602                         "--daemonize requires --external-agent if used with "
1603                         "--output-as=none");
1604             return clean_up(CRM_EX_USAGE);
1605         }
1606         crm_enable_stderr(FALSE);
1607         cib_delete(cib);
1608         cib = NULL;
1609         pcmk__daemonize(crm_system_name, options.pid_file);
1610         cib = cib_new();
1611         exit_on_invalid_cib();
1612     }
1613 
1614     show = default_includes(output_format);
1615 
1616     /* Apply --include/--exclude flags we used internally.  There's no error reporting
1617      * here because this would be a programming error.
1618      */
1619     apply_include_exclude(options.includes_excludes, &error);
1620 
1621     /* And now apply any --include/--exclude flags the user gave on the command line.
1622      * These are done in a separate pass from the internal ones because we want to
1623      * make sure whatever the user specifies overrides whatever we do.
1624      */
1625     if (!apply_include_exclude(options.user_includes_excludes, &error)) {
1626         return clean_up(CRM_EX_USAGE);
1627     }
1628 
1629     /* Sync up the initial value of interactive_fence_level with whatever was set with
1630      * --include/--exclude= options.
1631      */
1632     if (pcmk_all_flags_set(show, pcmk_section_fencing_all)) {
1633         interactive_fence_level = 3;
1634     } else if (pcmk_is_set(show, pcmk_section_fence_worked)) {
1635         interactive_fence_level = 2;
1636     } else if (pcmk_any_flags_set(show, pcmk_section_fence_failed | pcmk_section_fence_pending)) {
1637         interactive_fence_level = 1;
1638     } else {
1639         interactive_fence_level = 0;
1640     }
1641 
1642     pcmk__register_lib_messages(out);
1643     crm_mon_register_messages(out);
1644     pe__register_messages(out);
1645     stonith__register_messages(out);
1646 
1647     // Messages internal to this file, nothing curses-specific
1648     pcmk__register_messages(out, fmt_functions);
1649 
1650     if (args->version) {
1651         out->version(out, false);
1652         return clean_up(CRM_EX_OK);
1653     }
1654 
1655     /* Extra sanity checks when in CGI mode */
1656     if (output_format == mon_output_cgi) {
1657         if (cib->variant == cib_file) {
1658             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
1659             return clean_up(CRM_EX_USAGE);
1660         } else if (options.external_agent != NULL) {
1661             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
1662             return clean_up(CRM_EX_USAGE);
1663         } else if (options.exec_mode == mon_exec_daemonized) {
1664             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
1665             return clean_up(CRM_EX_USAGE);
1666         }
1667     }
1668 
1669     if (output_format == mon_output_xml) {
1670         show_opts |= pcmk_show_inactive_rscs | pcmk_show_timing;
1671     }
1672 
1673     if ((output_format == mon_output_html || output_format == mon_output_cgi) &&
1674         out->dest != stdout) {
1675         pcmk__html_add_header("meta", "http-equiv", "refresh", "content",
1676                               pcmk__itoa(options.reconnect_ms / 1000), NULL);
1677     }
1678 
1679 #ifdef PCMK__COMPAT_2_0
1680     // Keep failed action output the same as 2.0.x
1681     show_opts |= pcmk_show_failed_detail;
1682 #endif
1683 
1684     crm_info("Starting %s", crm_system_name);
1685 
1686     cib__set_output(cib, out);
1687 
1688     if (options.exec_mode == mon_exec_one_shot) {
1689         one_shot();
1690     }
1691 
1692     out->message(out, "crm-mon-disconnected",
1693                  "Waiting for initial connection", pcmkd_state);
1694     do {
1695         out->transient(out, "Connecting to cluster...");
1696         rc = setup_api_connections();
1697 
1698         if (rc != pcmk_rc_ok) {
1699             if ((rc == ENOTCONN) || (rc == ECONNREFUSED)) {
1700                 out->transient(out, "Connection failed. Retrying in %ums...",
1701                                options.reconnect_ms);
1702             }
1703 
1704             // Give some time to view all output even if we won't retry
1705             pcmk__sleep_ms(options.reconnect_ms);
1706 #if CURSES_ENABLED
1707             if (output_format == mon_output_console) {
1708                 clear();
1709                 refresh();
1710             }
1711 #endif
1712         }
1713     } while ((rc == ENOTCONN) || (rc == ECONNREFUSED));
1714 
1715     if (rc != pcmk_rc_ok) {
1716         clean_up_on_connection_failure(rc);
1717     }
1718 
1719     set_fencing_options(interactive_fence_level);
1720     mon_refresh_display(NULL);
1721 
1722     mainloop = g_main_loop_new(NULL, FALSE);
1723 
1724     mainloop_add_signal(SIGTERM, mon_shutdown);
1725     mainloop_add_signal(SIGINT, mon_shutdown);
1726 #if CURSES_ENABLED
1727     if (output_format == mon_output_console) {
1728         ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize);
1729         if (ncurses_winch_handler == SIG_DFL ||
1730             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
1731             ncurses_winch_handler = NULL;
1732 
1733         io_channel = g_io_channel_unix_new(STDIN_FILENO);
1734         g_io_add_watch(io_channel, G_IO_IN, detect_user_input, NULL);
1735     }
1736 #endif
1737 
1738     /* When refresh_trigger->trigger is set to TRUE, call mon_refresh_display.  In
1739      * this file, that is anywhere mainloop_set_trigger is called.
1740      */
1741     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
1742 
1743     g_main_loop_run(mainloop);
1744     g_main_loop_unref(mainloop);
1745 
1746     if (io_channel != NULL) {
1747         g_io_channel_shutdown(io_channel, TRUE, NULL);
1748     }
1749 
1750     crm_info("Exiting %s", crm_system_name);
1751 
1752     return clean_up(CRM_EX_OK);
1753 }
1754 
1755 static int
1756 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
     /* [previous][next][first][last][top][bottom][index][help] */
1757                  int status, const char *desc)
1758 {
1759     pid_t pid;
1760 
1761     /*setenv needs chars, these are ints */
1762     char *rc_s = pcmk__itoa(rc);
1763     char *status_s = pcmk__itoa(status);
1764     char *target_rc_s = pcmk__itoa(target_rc);
1765 
1766     crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent);
1767 
1768     if(rsc) {
1769         setenv("CRM_notify_rsc", rsc, 1);
1770     }
1771     if (options.external_recipient) {
1772         setenv("CRM_notify_recipient", options.external_recipient, 1);
1773     }
1774     setenv("CRM_notify_node", node, 1);
1775     setenv("CRM_notify_task", task, 1);
1776     setenv("CRM_notify_desc", desc, 1);
1777     setenv("CRM_notify_rc", rc_s, 1);
1778     setenv("CRM_notify_target_rc", target_rc_s, 1);
1779     setenv("CRM_notify_status", status_s, 1);
1780 
1781     pid = fork();
1782     if (pid == -1) {
1783         crm_perror(LOG_ERR, "notification fork() failed.");
1784     }
1785     if (pid == 0) {
1786         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
1787         execl(options.external_agent, options.external_agent, NULL);
1788         exit(CRM_EX_ERROR);
1789     }
1790 
1791     crm_trace("Finished running custom notification program '%s'.", options.external_agent);
1792     free(target_rc_s);
1793     free(status_s);
1794     free(rc_s);
1795     return 0;
1796 }
1797 
1798 static int
1799 handle_rsc_op(xmlNode *xml, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1800 {
1801     const char *node_id = (const char *) userdata;
1802     int rc = -1;
1803     int status = -1;
1804     int target_rc = -1;
1805     gboolean notify = TRUE;
1806 
1807     char *rsc = NULL;
1808     char *task = NULL;
1809     const char *desc = NULL;
1810     const char *magic = NULL;
1811     const char *id = NULL;
1812     const char *node = NULL;
1813 
1814     xmlNode *n = xml;
1815     xmlNode * rsc_op = xml;
1816 
1817     if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
1818         pcmk__xe_foreach_child(xml, NULL, handle_rsc_op, (void *) node_id);
1819         return pcmk_rc_ok;
1820     }
1821 
1822     id = pe__xe_history_key(rsc_op);
1823 
1824     magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
1825     if (magic == NULL) {
1826         /* non-change */
1827         return pcmk_rc_ok;
1828     }
1829 
1830     if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
1831                                  &target_rc)) {
1832         crm_err("Invalid event %s detected for %s", magic, id);
1833         return pcmk_rc_ok;
1834     }
1835 
1836     if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
1837         crm_err("Invalid event detected for %s", id);
1838         goto bail;
1839     }
1840 
1841     node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
1842 
1843     while (n != NULL && !pcmk__str_eq(XML_CIB_TAG_STATE, TYPE(n), pcmk__str_casei)) {
1844         n = n->parent;
1845     }
1846 
1847     if(node == NULL && n) {
1848         node = crm_element_value(n, XML_ATTR_UNAME);
1849     }
1850 
1851     if (node == NULL && n) {
1852         node = ID(n);
1853     }
1854 
1855     if (node == NULL) {
1856         node = node_id;
1857     }
1858 
1859     if (node == NULL) {
1860         crm_err("No node detected for event %s (%s)", magic, id);
1861         goto bail;
1862     }
1863 
1864     /* look up where we expected it to be? */
1865     desc = pcmk_rc_str(pcmk_rc_ok);
1866     if ((status == PCMK_EXEC_DONE) && (target_rc == rc)) {
1867         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
1868         if (rc == PCMK_OCF_NOT_RUNNING) {
1869             notify = FALSE;
1870         }
1871 
1872     } else if (status == PCMK_EXEC_DONE) {
1873         desc = services_ocf_exitcode_str(rc);
1874         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
1875 
1876     } else {
1877         desc = pcmk_exec_status_str(status);
1878         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
1879     }
1880 
1881     if (notify && options.external_agent) {
1882         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
1883     }
1884 
1885   bail:
1886     free(rsc);
1887     free(task);
1888     return pcmk_rc_ok;
1889 }
1890 
1891 /* This function is just a wrapper around mainloop_set_trigger so that it can be
1892  * called from a mainloop directly.  It's simply another way of ensuring the screen
1893  * gets redrawn.
1894  */
1895 static gboolean
1896 mon_trigger_refresh(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1897 {
1898     mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
1899     return FALSE;
1900 }
1901 
1902 static int
1903 handle_op_for_node(xmlNode *xml, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1904 {
1905     const char *node = crm_element_value(xml, XML_ATTR_UNAME);
1906 
1907     if (node == NULL) {
1908         node = ID(xml);
1909     }
1910 
1911     handle_rsc_op(xml, (void *) node);
1912     return pcmk_rc_ok;
1913 }
1914 
1915 static void
1916 crm_diff_update_v2(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
1917 {
1918     xmlNode *change = NULL;
1919     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
1920 
1921     for (change = pcmk__xml_first_child(diff); change != NULL;
1922          change = pcmk__xml_next(change)) {
1923         const char *name = NULL;
1924         const char *op = crm_element_value(change, XML_DIFF_OP);
1925         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
1926         xmlNode *match = NULL;
1927         const char *node = NULL;
1928 
1929         if(op == NULL) {
1930             continue;
1931 
1932         } else if(strcmp(op, "create") == 0) {
1933             match = change->children;
1934 
1935         } else if(strcmp(op, "move") == 0) {
1936             continue;
1937 
1938         } else if(strcmp(op, "delete") == 0) {
1939             continue;
1940 
1941         } else if(strcmp(op, "modify") == 0) {
1942             match = first_named_child(change, XML_DIFF_RESULT);
1943             if(match) {
1944                 match = match->children;
1945             }
1946         }
1947 
1948         if(match) {
1949             name = (const char *)match->name;
1950         }
1951 
1952         crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
1953         if(xpath == NULL) {
1954             /* Version field, ignore */
1955 
1956         } else if(name == NULL) {
1957             crm_debug("No result for %s operation to %s", op, xpath);
1958             CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
1959 
1960         } else if(strcmp(name, XML_TAG_CIB) == 0) {
1961             pcmk__xe_foreach_child(first_named_child(match, XML_CIB_TAG_STATUS),
1962                                    NULL, handle_op_for_node, NULL);
1963 
1964         } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
1965             pcmk__xe_foreach_child(match, NULL, handle_op_for_node, NULL);
1966 
1967         } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
1968             node = crm_element_value(match, XML_ATTR_UNAME);
1969             if (node == NULL) {
1970                 node = ID(match);
1971             }
1972             handle_rsc_op(match, (void *) node);
1973 
1974         } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
1975             node = ID(match);
1976             handle_rsc_op(match, (void *) node);
1977 
1978         } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
1979             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
1980 
1981             handle_rsc_op(match, local_node);
1982             free(local_node);
1983 
1984         } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
1985             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
1986 
1987             handle_rsc_op(match, local_node);
1988             free(local_node);
1989 
1990         } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
1991             char *local_node = pcmk__xpath_node_id(xpath, "lrm");
1992 
1993             handle_rsc_op(match, local_node);
1994             free(local_node);
1995 
1996         } else {
1997             crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
1998         }
1999     }
2000 }
2001 
2002 static void
2003 crm_diff_update_v1(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
2004 {
2005     /* Process operation updates */
2006     xmlXPathObject *xpathObj = xpath_search(msg,
2007                                             "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
2008                                             "//" XML_LRM_TAG_RSC_OP);
2009     int lpc = 0, max = numXpathResults(xpathObj);
2010 
2011     for (lpc = 0; lpc < max; lpc++) {
2012         xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
2013 
2014         handle_rsc_op(rsc_op, NULL);
2015     }
2016     freeXpathObject(xpathObj);
2017 }
2018 
2019 static void
2020 crm_diff_update(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
2021 {
2022     int rc = -1;
2023     static bool stale = FALSE;
2024     gboolean cib_updated = FALSE;
2025     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
2026 
2027     out->progress(out, false);
2028 
2029     if (current_cib != NULL) {
2030         rc = xml_apply_patchset(current_cib, diff, TRUE);
2031 
2032         switch (rc) {
2033             case -pcmk_err_diff_resync:
2034             case -pcmk_err_diff_failed:
2035                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
2036                 free_xml(current_cib); current_cib = NULL;
2037                 break;
2038             case pcmk_ok:
2039                 cib_updated = TRUE;
2040                 break;
2041             default:
2042                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
2043                 free_xml(current_cib); current_cib = NULL;
2044         }
2045     }
2046 
2047     if (current_cib == NULL) {
2048         crm_trace("Re-requesting the full cib");
2049         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
2050     }
2051 
2052     if (options.external_agent) {
2053         int format = 0;
2054         crm_element_value_int(diff, "format", &format);
2055         switch(format) {
2056             case 1:
2057                 crm_diff_update_v1(event, msg);
2058                 break;
2059             case 2:
2060                 crm_diff_update_v2(event, msg);
2061                 break;
2062             default:
2063                 crm_err("Unknown patch format: %d", format);
2064         }
2065     }
2066 
2067     if (current_cib == NULL) {
2068         if(!stale) {
2069             out->info(out, "--- Stale data ---");
2070         }
2071         stale = TRUE;
2072         return;
2073     }
2074 
2075     stale = FALSE;
2076     refresh_after_event(cib_updated, FALSE);
2077 }
2078 
2079 static int
2080 mon_refresh_display(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2081 {
2082     int rc = pcmk_rc_ok;
2083 
2084     last_refresh = time(NULL);
2085 
2086     if (output_format == mon_output_none) {
2087         return G_SOURCE_REMOVE;
2088     }
2089 
2090     if (fence_history == pcmk__fence_history_full &&
2091         !pcmk_all_flags_set(show, pcmk_section_fencing_all) &&
2092         output_format != mon_output_xml) {
2093         fence_history = pcmk__fence_history_reduced;
2094     }
2095 
2096     // Get an up-to-date pacemakerd status for the cluster summary
2097     if (cib->variant == cib_native) {
2098         pcmk__pacemakerd_status(out, crm_system_name, options.reconnect_ms / 2,
2099                                 false, &pcmkd_state);
2100     }
2101 
2102     if (out->dest != stdout) {
2103         out->reset(out);
2104     }
2105 
2106     rc = pcmk__output_cluster_status(out, st, cib, current_cib, pcmkd_state,
2107                                      fence_history, show, show_opts,
2108                                      options.only_node,options.only_rsc,
2109                                      options.neg_location_prefix,
2110                                      output_format == mon_output_monitor);
2111 
2112     if (output_format == mon_output_monitor && rc != pcmk_rc_ok) {
2113         clean_up(MON_STATUS_WARN);
2114         return G_SOURCE_REMOVE;
2115     } else if (rc == pcmk_rc_schema_validation) {
2116         clean_up(CRM_EX_CONFIG);
2117         return G_SOURCE_REMOVE;
2118     }
2119 
2120     if (out->dest != stdout) {
2121         out->finish(out, CRM_EX_OK, true, NULL);
2122     }
2123 
2124     return G_SOURCE_CONTINUE;
2125 }
2126 
2127 /* This function is called for fencing events (see setup_fencer_connection() for
2128  * which ones) when --watch-fencing is used on the command line
2129  */
2130 static void
2131 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
     /* [previous][next][first][last][top][bottom][index][help] */
2132 {
2133     if (st->state == stonith_disconnected) {
2134         /* disconnect cib as well and have everything reconnect */
2135         mon_cib_connection_destroy(NULL);
2136     } else if (options.external_agent) {
2137         char *desc = stonith__event_description(e);
2138 
2139         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
2140         free(desc);
2141     }
2142 }
2143 
2144 /* Cause the screen to be redrawn (via mainloop_set_trigger) when various conditions are met:
2145  *
2146  * - If the last update occurred more than reconnect_ms ago (defaults to 5s, but
2147  *   can be changed via the -i command line option), or
2148  * - After every 10 CIB updates, or
2149  * - If it's been 2s since the last update
2150  *
2151  * This function sounds like it would be more broadly useful, but it is only called when a
2152  * fencing event is received or a CIB diff occurrs.
2153  */
2154 static void
2155 refresh_after_event(gboolean data_updated, gboolean enforce)
     /* [previous][next][first][last][top][bottom][index][help] */
2156 {
2157     static int updates = 0;
2158     time_t now = time(NULL);
2159 
2160     if (data_updated) {
2161         updates++;
2162     }
2163 
2164     if(refresh_timer == NULL) {
2165         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
2166     }
2167 
2168     if (reconnect_timer > 0) {
2169         /* we will receive a refresh request after successful reconnect */
2170         mainloop_timer_stop(refresh_timer);
2171         return;
2172     }
2173 
2174     /* as we're not handling initial failure of fencer-connection as
2175      * fatal give it a retry here
2176      * not getting here if cib-reconnection is already on the way
2177      */
2178     setup_fencer_connection();
2179 
2180     if (enforce ||
2181         ((now - last_refresh) > (options.reconnect_ms / 1000)) ||
2182         updates >= 10) {
2183         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
2184         mainloop_timer_stop(refresh_timer);
2185         updates = 0;
2186 
2187     } else {
2188         mainloop_timer_start(refresh_timer);
2189     }
2190 }
2191 
2192 /* This function is called for fencing events (see setup_fencer_connection() for
2193  * which ones) when --watch-fencing is NOT used on the command line
2194  */
2195 static void
2196 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
     /* [previous][next][first][last][top][bottom][index][help] */
2197 {
2198     if (st->state == stonith_disconnected) {
2199         /* disconnect cib as well and have everything reconnect */
2200         mon_cib_connection_destroy(NULL);
2201     } else {
2202         out->progress(out, false);
2203         refresh_after_event(TRUE, FALSE);
2204     }
2205 }
2206 
2207 /*
2208  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
2209  * deallocate memory and show usage-message if requested.
2210  *
2211  * We don't actually return, but nominally returning crm_exit_t allows a usage
2212  * like "return clean_up(exit_code);" which helps static analysis understand the
2213  * code flow.
2214  */
2215 static crm_exit_t
2216 clean_up(crm_exit_t exit_code)
     /* [previous][next][first][last][top][bottom][index][help] */
2217 {
2218     /* Quitting crm_mon is much more complicated than it ought to be. */
2219 
2220     /* (1) Close connections, free things, etc. */
2221     cib__clean_up_connection(&cib);
2222     stonith_api_delete(st);
2223     free(options.neg_location_prefix);
2224     free(options.only_node);
2225     free(options.only_rsc);
2226     free(options.pid_file);
2227     g_slist_free_full(options.includes_excludes, free);
2228 
2229     g_strfreev(processed_args);
2230 
2231     /* (2) If this is abnormal termination and we're in curses mode, shut down
2232      * curses first.  Any messages displayed to the screen before curses is shut
2233      * down will be lost because doing the shut down will also restore the
2234      * screen to whatever it looked like before crm_mon was started.
2235      */
2236     if (((error != NULL) || (exit_code == CRM_EX_USAGE))
2237         && (output_format == mon_output_console)
2238         && (out != NULL)) {
2239 
2240         out->finish(out, exit_code, false, NULL);
2241         pcmk__output_free(out);
2242         out = NULL;
2243     }
2244 
2245     /* (3) If this is a command line usage related failure, print the usage
2246      * message.
2247      */
2248     if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
2249         char *help = g_option_context_get_help(context, TRUE, NULL);
2250 
2251         fprintf(stderr, "%s", help);
2252         g_free(help);
2253     }
2254 
2255     pcmk__free_arg_context(context);
2256 
2257     /* (4) If this is any kind of error, print the error out and exit.  Make
2258      * sure to handle situations both before and after formatted output is
2259      * set up.  We want errors to appear formatted if at all possible.
2260      */
2261     if (error != NULL) {
2262         if (out != NULL) {
2263             out->err(out, "%s: %s", g_get_prgname(), error->message);
2264             out->finish(out, exit_code, true, NULL);
2265             pcmk__output_free(out);
2266         } else {
2267             fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
2268         }
2269 
2270         g_clear_error(&error);
2271         crm_exit(exit_code);
2272     }
2273 
2274     /* (5) Print formatted output to the screen if we made it far enough in
2275      * crm_mon to be able to do so.
2276      */
2277     if (out != NULL) {
2278         if (options.exec_mode != mon_exec_daemonized) {
2279             out->finish(out, exit_code, true, NULL);
2280         }
2281 
2282         pcmk__output_free(out);
2283         pcmk__unregister_formats();
2284     }
2285 
2286     crm_exit(exit_code);
2287 }

/* [previous][next][first][last][top][bottom][index][help] */