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_element_v2
  59. crm_diff_update_v2
  60. crm_diff_update_v1
  61. crm_diff_update
  62. mon_refresh_display
  63. mon_st_callback_event
  64. refresh_after_event
  65. mon_st_callback_display
  66. clean_up

   1 /*
   2  * Copyright 2004-2024 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/services.h>
  30 #include <crm/lrmd.h>
  31 #include <crm/common/cmdline_internal.h>
  32 #include <crm/common/internal.h>  // pcmk__ends_with_ext()
  33 #include <crm/common/ipc.h>
  34 #include <crm/common/mainloop.h>
  35 #include <crm/common/output.h>
  36 #include <crm/common/output_internal.h>
  37 #include <crm/common/results.h>
  38 #include <crm/common/util.h>
  39 #include <crm/common/xml.h>
  40 #include <crm/common/xml_internal.h>
  41 
  42 #include <crm/cib/internal.h>
  43 #include <crm/pengine/status.h>
  44 #include <crm/pengine/internal.h>
  45 #include <pacemaker-internal.h>
  46 #include <crm/stonith-ng.h>
  47 #include <crm/fencing/internal.h>   // stonith__*
  48 
  49 #include "crm_mon.h"
  50 
  51 #define SUMMARY "Provides a summary of cluster's current state.\n\n" \
  52                 "Outputs varying levels of detail in a number of different formats."
  53 
  54 /*
  55  * Definitions indicating which items to print
  56  */
  57 
  58 static uint32_t show;
  59 static uint32_t show_opts = pcmk_show_pending;
  60 
  61 /*
  62  * Definitions indicating how to output
  63  */
  64 
  65 static mon_output_format_t output_format = mon_output_unset;
  66 
  67 /* other globals */
  68 static GIOChannel *io_channel = NULL;
  69 static GMainLoop *mainloop = NULL;
  70 static guint reconnect_timer = 0;
  71 static mainloop_timer_t *refresh_timer = NULL;
  72 
  73 static enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
  74 static cib_t *cib = NULL;
  75 static stonith_t *st = NULL;
  76 static xmlNode *current_cib = NULL;
  77 
  78 static GError *error = NULL;
  79 static pcmk__common_args_t *args = NULL;
  80 static pcmk__output_t *out = NULL;
  81 static GOptionContext *context = NULL;
  82 static gchar **processed_args = NULL;
  83 
  84 static time_t last_refresh = 0;
  85 volatile crm_trigger_t *refresh_trigger = NULL;
  86 
  87 static enum pcmk__fence_history fence_history = pcmk__fence_history_none;
  88 
  89 int interactive_fence_level = 0;
  90 
  91 static pcmk__supported_format_t formats[] = {
  92 #if CURSES_ENABLED
  93     CRM_MON_SUPPORTED_FORMAT_CURSES,
  94 #endif
  95     PCMK__SUPPORTED_FORMAT_HTML,
  96     PCMK__SUPPORTED_FORMAT_NONE,
  97     PCMK__SUPPORTED_FORMAT_TEXT,
  98     PCMK__SUPPORTED_FORMAT_XML,
  99     { NULL, NULL, NULL }
 100 };
 101 
 102 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 103                   "enum pcmk_pacemakerd_state")
 104 static int
 105 crm_mon_disconnected_default(pcmk__output_t *out, va_list args)
 106 {
 107     return pcmk_rc_no_output;
 108 }
 109 
 110 PCMK__OUTPUT_ARGS("crm-mon-disconnected", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 111                   "enum pcmk_pacemakerd_state")
 112 static int
 113 crm_mon_disconnected_html(pcmk__output_t *out, va_list args)
 114 {
 115     const char *desc = va_arg(args, const char *);
 116     enum pcmk_pacemakerd_state state =
 117         (enum pcmk_pacemakerd_state) va_arg(args, int);
 118 
 119     if (out->dest != stdout) {
 120         out->reset(out);
 121     }
 122 
 123     pcmk__output_create_xml_text_node(out, PCMK__XE_SPAN,
 124                                       "Not connected to CIB");
 125 
 126     if (desc != NULL) {
 127         pcmk__output_create_xml_text_node(out, PCMK__XE_SPAN, ": ");
 128         pcmk__output_create_xml_text_node(out, PCMK__XE_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, PCMK__XE_SPAN, " (");
 135         pcmk__output_create_xml_text_node(out, PCMK__XE_SPAN, state_s);
 136         pcmk__output_create_xml_text_node(out, PCMK__XE_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, PCMK_XE_CRM_MON_DISCONNECTED,
 189                                  PCMK_XA_DESCRIPTION, desc,
 190                                  PCMK_XA_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, PCMK_VALUE_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                         PCMK_VALUE_DEFAULT ", failcounts, failures, fencing, "
 375                         "fencing-failed, fencing-pending, fencing-succeeded, "
 376                         "maint-mode, nodes, " PCMK_VALUE_NONE ", operations, "
 377                         "options, resources, 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         pcmk_parse_interval_spec(optarg, &options.reconnect_ms);
 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 '" PCMK_META_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     if (st != NULL) {
 858         if (st->state != stonith_disconnected) {
 859             st->cmds->disconnect(st);
 860         }
 861         st->cmds->remove_notification(st, NULL);
 862     }
 863 
 864     if (cib) {
 865         cib->cmds->signoff(cib);
 866         reconnect_timer = g_timeout_add(options.reconnect_ms,
 867                                         reconnect_after_timeout, NULL);
 868     }
 869 }
 870 
 871 /* Signal handler installed into the mainloop for normal program shutdown */
 872 static void
 873 mon_shutdown(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 874 {
 875     clean_up(CRM_EX_OK);
 876 }
 877 
 878 #if CURSES_ENABLED
 879 static volatile sighandler_t ncurses_winch_handler;
 880 
 881 /* Signal handler installed the regular way (not into the main loop) for when
 882  * the screen is resized.  Commonly, this happens when running in an xterm and
 883  * the user changes its size.
 884  */
 885 static void
 886 mon_winresize(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 887 {
 888     static int not_done;
 889     int lines = 0, cols = 0;
 890 
 891     if (!not_done++) {
 892         if (ncurses_winch_handler)
 893             /* the original ncurses WINCH signal handler does the
 894              * magic of retrieving the new window size;
 895              * otherwise, we'd have to use ioctl or tgetent */
 896             (*ncurses_winch_handler) (SIGWINCH);
 897         getmaxyx(stdscr, lines, cols);
 898         resizeterm(lines, cols);
 899         /* Alert the mainloop code we'd like the refresh_trigger to run next
 900          * time the mainloop gets around to checking.
 901          */
 902         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
 903     }
 904     not_done--;
 905 }
 906 #endif
 907 
 908 static int
 909 setup_fencer_connection(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 910 {
 911     int rc = pcmk_ok;
 912 
 913     if (options.fence_connect && st == NULL) {
 914         st = stonith_api_new();
 915     }
 916 
 917     if (!options.fence_connect || st == NULL || st->state != stonith_disconnected) {
 918         return rc;
 919     }
 920 
 921     rc = st->cmds->connect(st, crm_system_name, NULL);
 922     if (rc == pcmk_ok) {
 923         crm_trace("Setting up stonith callbacks");
 924         if (options.watch_fencing) {
 925             st->cmds->register_notification(st,
 926                                             PCMK__VALUE_ST_NOTIFY_DISCONNECT,
 927                                             mon_st_callback_event);
 928             st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_FENCE,
 929                                             mon_st_callback_event);
 930         } else {
 931             st->cmds->register_notification(st,
 932                                             PCMK__VALUE_ST_NOTIFY_DISCONNECT,
 933                                             mon_st_callback_display);
 934             st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_HISTORY,
 935                                             mon_st_callback_display);
 936         }
 937     } else {
 938         stonith_api_delete(st);
 939         st = NULL;
 940     }
 941 
 942     return rc;
 943 }
 944 
 945 static int
 946 setup_cib_connection(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 947 {
 948     int rc = pcmk_rc_ok;
 949 
 950     CRM_CHECK(cib != NULL, return EINVAL);
 951 
 952     if (cib->state != cib_disconnected) {
 953         // Already connected with notifications registered for CIB updates
 954         return rc;
 955     }
 956 
 957     rc = cib__signon_query(out, &cib, &current_cib);
 958 
 959     if (rc == pcmk_rc_ok) {
 960         rc = pcmk_legacy2rc(cib->cmds->set_connection_dnotify(cib,
 961             mon_cib_connection_destroy));
 962         if (rc == EPROTONOSUPPORT) {
 963             out->err(out,
 964                      "CIB client does not support connection loss "
 965                      "notifications; crm_mon will be unable to reconnect after "
 966                      "connection loss");
 967             rc = pcmk_rc_ok;
 968         }
 969 
 970         if (rc == pcmk_rc_ok) {
 971             cib->cmds->del_notify_callback(cib, PCMK__VALUE_CIB_DIFF_NOTIFY,
 972                                            crm_diff_update);
 973             rc = cib->cmds->add_notify_callback(cib, PCMK__VALUE_CIB_DIFF_NOTIFY,
 974                                                 crm_diff_update);
 975             rc = pcmk_legacy2rc(rc);
 976         }
 977 
 978         if (rc != pcmk_rc_ok) {
 979             if (rc == EPROTONOSUPPORT) {
 980                 out->err(out,
 981                          "CIB client does not support CIB diff "
 982                          "notifications");
 983             } else {
 984                 out->err(out, "CIB diff notification setup failed");
 985             }
 986 
 987             out->err(out, "Cannot monitor CIB changes; exiting");
 988             cib__clean_up_connection(&cib);
 989             stonith_api_delete(st);
 990             st = NULL;
 991         }
 992     }
 993     return rc;
 994 }
 995 
 996 /* This is used to set up the fencing options after the interactive UI has been stared.
 997  * fence_history_cb can't be used because it builds up a list of includes/excludes that
 998  * then have to be processed with apply_include_exclude and that could affect other
 999  * things.
1000  */
1001 static void
1002 set_fencing_options(int level)
     /* [previous][next][first][last][top][bottom][index][help] */
1003 {
1004     switch (level) {
1005         case 3:
1006             options.fence_connect = TRUE;
1007             fence_history = pcmk__fence_history_full;
1008             show |= pcmk_section_fencing_all;
1009             break;
1010 
1011         case 2:
1012             options.fence_connect = TRUE;
1013             fence_history = pcmk__fence_history_full;
1014             show |= pcmk_section_fencing_all;
1015             break;
1016 
1017         case 1:
1018             options.fence_connect = TRUE;
1019             fence_history = pcmk__fence_history_full;
1020             show |= pcmk_section_fence_failed | pcmk_section_fence_pending;
1021             break;
1022 
1023         default:
1024             interactive_fence_level = 0;
1025             options.fence_connect = FALSE;
1026             fence_history = pcmk__fence_history_none;
1027             show &= ~pcmk_section_fencing_all;
1028             break;
1029     }
1030 }
1031 
1032 static int
1033 setup_api_connections(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1034 {
1035     int rc = pcmk_rc_ok;
1036 
1037     CRM_CHECK(cib != NULL, return EINVAL);
1038 
1039     if (cib->state != cib_disconnected) {
1040         return rc;
1041     }
1042 
1043     if (cib->variant == cib_native) {
1044         rc = pcmk__pacemakerd_status(out, crm_system_name,
1045                                      options.reconnect_ms / 2, false,
1046                                      &pcmkd_state);
1047         if (rc != pcmk_rc_ok) {
1048             return rc;
1049         }
1050 
1051         switch (pcmkd_state) {
1052             case pcmk_pacemakerd_state_running:
1053             case pcmk_pacemakerd_state_remote:
1054             case pcmk_pacemakerd_state_shutting_down:
1055                 /* Fencer and CIB may still be available while shutting down or
1056                  * running on a Pacemaker Remote node
1057                  */
1058                 break;
1059             default:
1060                 // Fencer and CIB are definitely unavailable
1061                 return ENOTCONN;
1062         }
1063 
1064         setup_fencer_connection();
1065     }
1066 
1067     rc = setup_cib_connection();
1068     return rc;
1069 }
1070 
1071 #if CURSES_ENABLED
1072 static const char *
1073 get_option_desc(char c)
     /* [previous][next][first][last][top][bottom][index][help] */
1074 {
1075     const char *desc = "No help available";
1076 
1077     for (GOptionEntry *entry = display_entries; entry != NULL; entry++) {
1078         if (entry->short_name == c) {
1079             desc = entry->description;
1080             break;
1081         }
1082     }
1083     return desc;
1084 }
1085 
1086 #define print_option_help(out, option, condition) \
1087     curses_formatted_printf(out, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
1088 
1089 /* This function is called from the main loop when there is something to be read
1090  * on stdin, like an interactive user's keystroke.  All it does is read the keystroke,
1091  * set flags (or show the page showing which keystrokes are valid), and redraw the
1092  * screen.  It does not do anything with connections to the CIB or fencing agent
1093  * agent what would happen in mon_refresh_display.
1094  */
1095 static gboolean
1096 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1097 {
1098     int c;
1099     gboolean config_mode = FALSE;
1100     gboolean rc = G_SOURCE_CONTINUE;
1101 
1102     /* If the attached pty device (pseudo-terminal) has been closed/deleted,
1103      * the condition (G_IO_IN | G_IO_ERR | G_IO_HUP) occurs.
1104      * Exit with an error, otherwise the process would persist in the
1105      * background and significantly raise the CPU usage.
1106      */
1107     if ((condition & G_IO_ERR) && (condition & G_IO_HUP)) {
1108         rc = G_SOURCE_REMOVE;
1109         clean_up(CRM_EX_IOERR);
1110     }
1111 
1112     /* The connection/fd has been closed. Refresh the screen and remove this
1113      * event source hence ignore stdin.
1114      */
1115     if (condition & (G_IO_HUP | G_IO_NVAL)) {
1116         rc = G_SOURCE_REMOVE;
1117     }
1118 
1119     if ((condition & G_IO_IN) == 0) {
1120         return rc;
1121     }
1122 
1123     while (1) {
1124 
1125         /* Get user input */
1126         c = getchar();
1127 
1128         switch (c) {
1129             case 'm':
1130                 interactive_fence_level++;
1131                 if (interactive_fence_level > 3) {
1132                     interactive_fence_level = 0;
1133                 }
1134 
1135                 set_fencing_options(interactive_fence_level);
1136                 break;
1137             case 'c':
1138                 show ^= pcmk_section_tickets;
1139                 break;
1140             case 'f':
1141                 show ^= pcmk_section_failcounts;
1142                 break;
1143             case 'n':
1144                 show_opts ^= pcmk_show_rscs_by_node;
1145                 break;
1146             case 'o':
1147                 show ^= pcmk_section_operations;
1148                 if (!pcmk_is_set(show, pcmk_section_operations)) {
1149                     show_opts &= ~pcmk_show_timing;
1150                 }
1151                 break;
1152             case 'r':
1153                 show_opts ^= pcmk_show_inactive_rscs;
1154                 break;
1155             case 'R':
1156                 show_opts ^= pcmk_show_details;
1157 #ifdef PCMK__COMPAT_2_0
1158                 // Keep failed action output the same as 2.0.x
1159                 show_opts |= pcmk_show_failed_detail;
1160 #endif
1161                 break;
1162             case 't':
1163                 show_opts ^= pcmk_show_timing;
1164                 if (pcmk_is_set(show_opts, pcmk_show_timing)) {
1165                     show |= pcmk_section_operations;
1166                 }
1167                 break;
1168             case 'A':
1169                 show ^= pcmk_section_attributes;
1170                 break;
1171             case 'L':
1172                 show ^= pcmk_section_bans;
1173                 break;
1174             case 'D':
1175                 /* If any header is shown, clear them all, otherwise set them all */
1176                 if (pcmk_any_flags_set(show, pcmk_section_summary)) {
1177                     show &= ~pcmk_section_summary;
1178                 } else {
1179                     show |= pcmk_section_summary;
1180                 }
1181                 /* Regardless, we don't show options in console mode. */
1182                 show &= ~pcmk_section_options;
1183                 break;
1184             case 'b':
1185                 show_opts ^= pcmk_show_brief;
1186                 break;
1187             case 'j':
1188                 show_opts ^= pcmk_show_pending;
1189                 break;
1190             case '?':
1191                 config_mode = TRUE;
1192                 break;
1193             default:
1194                 /* All other keys just redraw the screen. */
1195                 goto refresh;
1196         }
1197 
1198         if (!config_mode)
1199             goto refresh;
1200 
1201         clear();
1202         refresh();
1203 
1204         curses_formatted_printf(out, "%s", "Display option change mode\n");
1205         print_option_help(out, 'c', pcmk_is_set(show, pcmk_section_tickets));
1206         print_option_help(out, 'f', pcmk_is_set(show, pcmk_section_failcounts));
1207         print_option_help(out, 'n', pcmk_is_set(show_opts, pcmk_show_rscs_by_node));
1208         print_option_help(out, 'o', pcmk_is_set(show, pcmk_section_operations));
1209         print_option_help(out, 'r', pcmk_is_set(show_opts, pcmk_show_inactive_rscs));
1210         print_option_help(out, 't', pcmk_is_set(show_opts, pcmk_show_timing));
1211         print_option_help(out, 'A', pcmk_is_set(show, pcmk_section_attributes));
1212         print_option_help(out, 'L', pcmk_is_set(show, pcmk_section_bans));
1213         print_option_help(out, 'D', !pcmk_is_set(show, pcmk_section_summary));
1214 #ifdef PCMK__COMPAT_2_0
1215         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details & ~pcmk_show_failed_detail));
1216 #else
1217         print_option_help(out, 'R', pcmk_any_flags_set(show_opts, pcmk_show_details));
1218 #endif
1219         print_option_help(out, 'b', pcmk_is_set(show_opts, pcmk_show_brief));
1220         print_option_help(out, 'j', pcmk_is_set(show_opts, pcmk_show_pending));
1221         curses_formatted_printf(out, "%d m: \t%s\n", interactive_fence_level, get_option_desc('m'));
1222         curses_formatted_printf(out, "%s", "\nToggle fields via field letter, type any other key to return\n");
1223     }
1224 
1225 refresh:
1226     refresh_after_event(FALSE, TRUE);
1227 
1228     return rc;
1229 }
1230 #endif  // CURSES_ENABLED
1231 
1232 // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag
1233 static void
1234 avoid_zombies(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1235 {
1236     struct sigaction sa;
1237 
1238     memset(&sa, 0, sizeof(struct sigaction));
1239     if (sigemptyset(&sa.sa_mask) < 0) {
1240         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
1241         return;
1242     }
1243     sa.sa_handler = SIG_IGN;
1244     sa.sa_flags = SA_RESTART|SA_NOCLDWAIT;
1245     if (sigaction(SIGCHLD, &sa, NULL) < 0) {
1246         crm_warn("Cannot avoid zombies: %s", pcmk_rc_str(errno));
1247     }
1248 }
1249 
1250 static GOptionContext *
1251 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     /* [previous][next][first][last][top][bottom][index][help] */
1252     GOptionContext *context = NULL;
1253 
1254     GOptionEntry extra_prog_entries[] = {
1255         { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet),
1256           "Be less descriptive in output.",
1257           NULL },
1258 
1259         { NULL }
1260     };
1261 
1262 #if CURSES_ENABLED
1263     const char *fmts = "console (default), html, text, xml, none";
1264 #else
1265     const char *fmts = "text (default), html, xml, none";
1266 #endif // CURSES_ENABLED
1267     const char *desc = NULL;
1268 
1269     desc = "Notes:\n\n"
1270            "If this program is called as crm_mon.cgi, --output-as=html and\n"
1271            "--html-cgi are automatically added to the command line\n"
1272            "arguments.\n\n"
1273 
1274            "Time Specification:\n\n"
1275            "The TIMESPEC in any command line option can be specified in many\n"
1276            "different formats. It can be an integer number of seconds, a\n"
1277            "number plus units (us/usec/ms/msec/s/sec/m/min/h/hr), or an ISO\n"
1278            "8601 period specification.\n\n"
1279 
1280            "Output Control:\n\n"
1281            "By default, a particular set of sections are written to the\n"
1282            "output destination. The default varies based on the output\n"
1283            "format: XML includes all sections by default, while other output\n"
1284            "formats include less. This set can be modified with the --include\n"
1285            "and --exclude command line options. Each option may be passed\n"
1286            "multiple times, and each can specify a comma-separated list of\n"
1287            "sections. The options are applied to the default set, in order\n"
1288            "from left to right as they are passed on the command line. For a\n"
1289            "list of valid sections, pass --include=list or --exclude=list.\n\n"
1290 
1291            "Interactive Use:\n\n"
1292 #if CURSES_ENABLED
1293            "When run interactively, crm_mon can be told to hide and show\n"
1294            "various sections of output. To see a help screen explaining the\n"
1295            "options, press '?'. Any key stroke aside from those listed will\n"
1296            "cause the screen to refresh.\n\n"
1297 #else
1298            "The local installation of Pacemaker was built without support for\n"
1299            "interactive (console) mode. A curses library must be available at\n"
1300            "build time to support interactive mode.\n\n"
1301 #endif // CURSES_ENABLED
1302 
1303            "Examples:\n\n"
1304 #if CURSES_ENABLED
1305            "Display the cluster status on the console with updates as they\n"
1306            "occur:\n\n"
1307            "\tcrm_mon\n\n"
1308 #endif // CURSES_ENABLED
1309 
1310            "Display the cluster status once and exit:\n\n"
1311            "\tcrm_mon -1\n\n"
1312 
1313            "Display the cluster status, group resources by node, and include\n"
1314            "inactive resources in the list:\n\n"
1315            "\tcrm_mon --group-by-node --inactive\n\n"
1316 
1317            "Start crm_mon as a background daemon and have it write the\n"
1318            "cluster status to an HTML file:\n\n"
1319            "\tcrm_mon --daemonize --output-as html "
1320            "--output-to /path/to/docroot/filename.html\n\n"
1321 
1322            "Display the cluster status as XML:\n\n"
1323            "\tcrm_mon --output-as xml\n\n";
1324 
1325     context = pcmk__build_arg_context(args, fmts, group, NULL);
1326     pcmk__add_main_args(context, extra_prog_entries);
1327     g_option_context_set_description(context, desc);
1328 
1329     pcmk__add_arg_group(context, "display", "Display Options:",
1330                         "Show display options", display_entries);
1331     pcmk__add_arg_group(context, "additional", "Additional Options:",
1332                         "Show additional options", addl_entries);
1333     pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
1334                         "Show deprecated options", deprecated_entries);
1335 
1336     return context;
1337 }
1338 
1339 /* If certain format options were specified, we want to set some extra
1340  * options.  We can just process these like they were given on the
1341  * command line.
1342  */
1343 static void
1344 add_output_args(void) {
     /* [previous][next][first][last][top][bottom][index][help] */
1345     GError *err = NULL;
1346 
1347     if (output_format == mon_output_cgi) {
1348         if (!pcmk__force_args(context, &err, "%s --html-cgi", g_get_prgname())) {
1349             g_propagate_error(&error, err);
1350             clean_up(CRM_EX_USAGE);
1351         }
1352     }
1353 }
1354 
1355 /*!
1356  * \internal
1357  * \brief Set output format based on \p --output-as arguments and mode arguments
1358  *
1359  * When the deprecated output format arguments (\p --as-cgi, \p --as-html,
1360  * \p --simple-status, \p --as-xml) are parsed, callback functions set
1361  * \p output_format (and the umask if appropriate). If none of the deprecated
1362  * arguments were specified, this function does the same based on the current
1363  * \p --output-as arguments and the \p --one-shot and \p --daemonize arguments.
1364  *
1365  * \param[in,out] args  Command line arguments
1366  */
1367 static void
1368 reconcile_output_format(pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
1369 {
1370     if (output_format != mon_output_unset) {
1371         /* One of the deprecated arguments was used, and we're finished. Note
1372          * that this means the deprecated arguments take precedence.
1373          */
1374         return;
1375     }
1376 
1377     if (pcmk__str_eq(args->output_ty, PCMK_VALUE_NONE, pcmk__str_none)) {
1378         output_format = mon_output_none;
1379 
1380     } else if (pcmk__str_eq(args->output_ty, "html", pcmk__str_none)) {
1381         output_format = mon_output_html;
1382         umask(S_IWGRP | S_IWOTH);   // World-readable HTML
1383 
1384     } else if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
1385         output_format = mon_output_xml;
1386 
1387 #if CURSES_ENABLED
1388     } else if (pcmk__str_eq(args->output_ty, "console",
1389                             pcmk__str_null_matches)) {
1390         /* Console is the default format if no conflicting options are given.
1391          *
1392          * Use text output instead if one of the following conditions is met:
1393          * * We've requested daemonized or one-shot mode (console output is
1394          *   incompatible with modes other than mon_exec_update)
1395          * * We requested the version, which is effectively one-shot
1396          * * We specified a non-stdout output destination (console mode is
1397          *   compatible only with stdout)
1398          */
1399         if ((options.exec_mode == mon_exec_daemonized)
1400             || (options.exec_mode == mon_exec_one_shot)
1401             || args->version
1402             || !pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches)) {
1403 
1404             pcmk__str_update(&args->output_ty, "text");
1405             output_format = mon_output_plain;
1406         } else {
1407             pcmk__str_update(&args->output_ty, "console");
1408             output_format = mon_output_console;
1409             crm_enable_stderr(FALSE);
1410         }
1411 #endif // CURSES_ENABLED
1412 
1413     } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) {
1414         /* Text output was explicitly requested, or it's the default because
1415          * curses is not enabled
1416          */
1417         pcmk__str_update(&args->output_ty, "text");
1418         output_format = mon_output_plain;
1419     }
1420 
1421     // Otherwise, invalid format. Let pcmk__output_new() throw an error.
1422 }
1423 
1424 /*!
1425  * \internal
1426  * \brief Set execution mode to the output format's default if appropriate
1427  *
1428  * \param[in,out] args  Command line arguments
1429  */
1430 static void
1431 set_default_exec_mode(const pcmk__common_args_t *args)
     /* [previous][next][first][last][top][bottom][index][help] */
1432 {
1433     if (output_format == mon_output_console) {
1434         /* Update is the only valid mode for console, but set here instead of
1435          * reconcile_output_format() for isolation and consistency
1436          */
1437         options.exec_mode = mon_exec_update;
1438 
1439     } else if (options.exec_mode == mon_exec_unset) {
1440         // Default to one-shot mode for all other formats
1441         options.exec_mode = mon_exec_one_shot;
1442 
1443     } else if ((options.exec_mode == mon_exec_update)
1444                && pcmk__str_eq(args->output_dest, "-",
1445                                pcmk__str_null_matches)) {
1446         // If not using console format, update mode cannot be used with stdout
1447         options.exec_mode = mon_exec_one_shot;
1448     }
1449 }
1450 
1451 static void
1452 clean_up_on_connection_failure(int rc)
     /* [previous][next][first][last][top][bottom][index][help] */
1453 {
1454     if (output_format == mon_output_monitor) {
1455         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "CLUSTER CRIT: Connection to cluster failed: %s",
1456                     pcmk_rc_str(rc));
1457         clean_up(MON_STATUS_CRIT);
1458     } else if (rc == ENOTCONN) {
1459         if (pcmkd_state == pcmk_pacemakerd_state_remote) {
1460             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: remote-node not connected to cluster");
1461         } else {
1462             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error: cluster is not available on this node");
1463         }
1464     } else {
1465         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Connection to cluster failed: %s", pcmk_rc_str(rc));
1466     }
1467 
1468     clean_up(pcmk_rc2exitc(rc));
1469 }
1470 
1471 static void
1472 one_shot(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1473 {
1474     int rc = pcmk__status(out, cib, fence_history, show, show_opts,
1475                           options.only_node, options.only_rsc,
1476                           options.neg_location_prefix,
1477                           output_format == mon_output_monitor, 0);
1478 
1479     if (rc == pcmk_rc_ok) {
1480         clean_up(pcmk_rc2exitc(rc));
1481     } else {
1482         clean_up_on_connection_failure(rc);
1483     }
1484 }
1485 
1486 static void
1487 exit_on_invalid_cib(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1488 {
1489     if (cib != NULL) {
1490         return;
1491     }
1492 
1493     // Shouldn't really be possible
1494     g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Invalid CIB source");
1495     clean_up(CRM_EX_ERROR);
1496 }
1497 
1498 int
1499 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
1500 {
1501     int rc = pcmk_rc_ok;
1502     GOptionGroup *output_group = NULL;
1503 
1504     args = pcmk__new_common_args(SUMMARY);
1505     context = build_arg_context(args, &output_group);
1506     pcmk__register_formats(output_group, formats);
1507 
1508     options.pid_file = strdup("/tmp/ClusterMon.pid");
1509     pcmk__cli_init_logging("crm_mon", 0);
1510 
1511     // Avoid needing to wait for subprocesses forked for -E/--external-agent
1512     avoid_zombies();
1513 
1514     if (pcmk__ends_with_ext(argv[0], ".cgi")) {
1515         output_format = mon_output_cgi;
1516         options.exec_mode = mon_exec_one_shot;
1517     }
1518 
1519     processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
1520 
1521     fence_history_cb("--fence-history", "1", NULL, NULL);
1522 
1523     /* Set an HTML title regardless of what format we will eventually use.  This can't
1524      * be done in add_output_args.  That function is called after command line
1525      * arguments are processed in the next block, which means it'll override whatever
1526      * title the user provides.  Doing this here means the user can give their own
1527      * title on the command line.
1528      */
1529     if (!pcmk__force_args(context, &error, "%s --html-title \"Cluster Status\"",
1530                           g_get_prgname())) {
1531         return clean_up(CRM_EX_USAGE);
1532     }
1533 
1534     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1535         return clean_up(CRM_EX_USAGE);
1536     }
1537 
1538     for (int i = 0; i < args->verbosity; i++) {
1539         crm_bump_log_level(argc, argv);
1540     }
1541 
1542     if (!args->version) {
1543         if (args->quiet) {
1544             include_exclude_cb("--exclude", "times", NULL, NULL);
1545         }
1546 
1547         if (options.watch_fencing) {
1548             fence_history_cb("--fence-history", "0", NULL, NULL);
1549             options.fence_connect = TRUE;
1550         }
1551 
1552         /* create the cib-object early to be able to do further
1553          * decisions based on the cib-source
1554          */
1555         cib = cib_new();
1556 
1557         exit_on_invalid_cib();
1558 
1559         switch (cib->variant) {
1560             case cib_native:
1561                 // Everything (fencer, CIB, pcmkd status) should be available
1562                 break;
1563 
1564             case cib_file:
1565                 // Live fence history is not meaningful
1566                 fence_history_cb("--fence-history", "0", NULL, NULL);
1567 
1568                 /* Notifications are unsupported; nothing to monitor
1569                  * @COMPAT: Let setup_cib_connection() handle this by exiting?
1570                  */
1571                 options.exec_mode = mon_exec_one_shot;
1572                 break;
1573 
1574             case cib_remote:
1575                 // We won't receive any fencing updates
1576                 fence_history_cb("--fence-history", "0", NULL, NULL);
1577                 break;
1578 
1579             default:
1580                 /* something is odd */
1581                 exit_on_invalid_cib();
1582                 break;
1583         }
1584 
1585         if ((options.exec_mode == mon_exec_daemonized)
1586             && !options.external_agent
1587             && pcmk__str_eq(args->output_dest, "-", pcmk__str_null_matches)) {
1588 
1589             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
1590                         "--daemonize requires at least one of --output-to "
1591                         "(with value not set to '-') and --external-agent");
1592             return clean_up(CRM_EX_USAGE);
1593         }
1594     }
1595 
1596     reconcile_output_format(args);
1597     set_default_exec_mode(args);
1598     add_output_args();
1599 
1600     rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1601     if (rc != pcmk_rc_ok) {
1602         g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_ERROR, "Error creating output format %s: %s",
1603                     args->output_ty, pcmk_rc_str(rc));
1604         return clean_up(CRM_EX_ERROR);
1605     }
1606 
1607     if (output_format == mon_output_legacy_xml) {
1608         output_format = mon_output_xml;
1609         pcmk__output_set_legacy_xml(out);
1610     }
1611 
1612     /* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
1613 
1614     /* If we had a valid format for pcmk__output_new(), output_format should be
1615      * set by now.
1616      */
1617     pcmk__assert(output_format != mon_output_unset);
1618 
1619     if (output_format == mon_output_plain) {
1620         pcmk__output_text_set_fancy(out, true);
1621     }
1622 
1623     if (options.exec_mode == mon_exec_daemonized) {
1624         if (!options.external_agent && (output_format == mon_output_none)) {
1625             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
1626                         "--daemonize requires --external-agent if used with "
1627                         "--output-as=none");
1628             return clean_up(CRM_EX_USAGE);
1629         }
1630         crm_enable_stderr(FALSE);
1631         cib_delete(cib);
1632         cib = NULL;
1633         pcmk__daemonize(crm_system_name, options.pid_file);
1634         cib = cib_new();
1635         exit_on_invalid_cib();
1636     }
1637 
1638     show = default_includes(output_format);
1639 
1640     /* Apply --include/--exclude flags we used internally.  There's no error reporting
1641      * here because this would be a programming error.
1642      */
1643     apply_include_exclude(options.includes_excludes, &error);
1644 
1645     /* And now apply any --include/--exclude flags the user gave on the command line.
1646      * These are done in a separate pass from the internal ones because we want to
1647      * make sure whatever the user specifies overrides whatever we do.
1648      */
1649     if (!apply_include_exclude(options.user_includes_excludes, &error)) {
1650         return clean_up(CRM_EX_USAGE);
1651     }
1652 
1653     /* Sync up the initial value of interactive_fence_level with whatever was set with
1654      * --include/--exclude= options.
1655      */
1656     if (pcmk_all_flags_set(show, pcmk_section_fencing_all)) {
1657         interactive_fence_level = 3;
1658     } else if (pcmk_is_set(show, pcmk_section_fence_worked)) {
1659         interactive_fence_level = 2;
1660     } else if (pcmk_any_flags_set(show, pcmk_section_fence_failed | pcmk_section_fence_pending)) {
1661         interactive_fence_level = 1;
1662     } else {
1663         interactive_fence_level = 0;
1664     }
1665 
1666     pcmk__register_lib_messages(out);
1667     crm_mon_register_messages(out);
1668     pe__register_messages(out);
1669     stonith__register_messages(out);
1670 
1671     // Messages internal to this file, nothing curses-specific
1672     pcmk__register_messages(out, fmt_functions);
1673 
1674     if (args->version) {
1675         out->version(out, false);
1676         return clean_up(CRM_EX_OK);
1677     }
1678 
1679     /* Extra sanity checks when in CGI mode */
1680     if (output_format == mon_output_cgi) {
1681         if (cib->variant == cib_file) {
1682             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode used with CIB file");
1683             return clean_up(CRM_EX_USAGE);
1684         } else if (options.external_agent != NULL) {
1685             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with --external-agent");
1686             return clean_up(CRM_EX_USAGE);
1687         } else if (options.exec_mode == mon_exec_daemonized) {
1688             g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "CGI mode cannot be used with -d");
1689             return clean_up(CRM_EX_USAGE);
1690         }
1691     }
1692 
1693     if (output_format == mon_output_xml) {
1694         show_opts |= pcmk_show_inactive_rscs | pcmk_show_timing;
1695     }
1696 
1697     if ((output_format == mon_output_html || output_format == mon_output_cgi)
1698         && (out->dest != stdout)) {
1699 
1700         char *content = pcmk__itoa(options.reconnect_ms / 1000);
1701 
1702         pcmk__html_add_header(PCMK__XE_META,
1703                               PCMK__XA_HTTP_EQUIV, PCMK__VALUE_REFRESH,
1704                               PCMK__XA_CONTENT, content,
1705                               NULL);
1706         free(content);
1707     }
1708 
1709 #ifdef PCMK__COMPAT_2_0
1710     // Keep failed action output the same as 2.0.x
1711     show_opts |= pcmk_show_failed_detail;
1712 #endif
1713 
1714     crm_info("Starting %s", crm_system_name);
1715 
1716     cib__set_output(cib, out);
1717 
1718     if (options.exec_mode == mon_exec_one_shot) {
1719         one_shot();
1720     }
1721 
1722     out->message(out, "crm-mon-disconnected",
1723                  "Waiting for initial connection", pcmkd_state);
1724     do {
1725         out->transient(out, "Connecting to cluster...");
1726         rc = setup_api_connections();
1727 
1728         if (rc != pcmk_rc_ok) {
1729             if ((rc == ENOTCONN) || (rc == ECONNREFUSED)) {
1730                 out->transient(out, "Connection failed. Retrying in %ums...",
1731                                options.reconnect_ms);
1732             }
1733 
1734             // Give some time to view all output even if we won't retry
1735             pcmk__sleep_ms(options.reconnect_ms);
1736 #if CURSES_ENABLED
1737             if (output_format == mon_output_console) {
1738                 clear();
1739                 refresh();
1740             }
1741 #endif
1742         }
1743     } while ((rc == ENOTCONN) || (rc == ECONNREFUSED));
1744 
1745     if (rc != pcmk_rc_ok) {
1746         clean_up_on_connection_failure(rc);
1747     }
1748 
1749     set_fencing_options(interactive_fence_level);
1750     mon_refresh_display(NULL);
1751 
1752     mainloop = g_main_loop_new(NULL, FALSE);
1753 
1754     mainloop_add_signal(SIGTERM, mon_shutdown);
1755     mainloop_add_signal(SIGINT, mon_shutdown);
1756 #if CURSES_ENABLED
1757     if (output_format == mon_output_console) {
1758         ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize);
1759         if (ncurses_winch_handler == SIG_DFL ||
1760             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
1761             ncurses_winch_handler = NULL;
1762 
1763         io_channel = g_io_channel_unix_new(STDIN_FILENO);
1764         g_io_add_watch(io_channel, (G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL),
1765                        detect_user_input, NULL);
1766     }
1767 #endif
1768 
1769     /* When refresh_trigger->trigger is set to TRUE, call mon_refresh_display.  In
1770      * this file, that is anywhere mainloop_set_trigger is called.
1771      */
1772     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
1773 
1774     g_main_loop_run(mainloop);
1775     g_main_loop_unref(mainloop);
1776 
1777     crm_info("Exiting %s", crm_system_name);
1778 
1779     return clean_up(CRM_EX_OK);
1780 }
1781 
1782 static int
1783 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] */
1784                  int status, const char *desc)
1785 {
1786     pid_t pid;
1787 
1788     /*setenv needs chars, these are ints */
1789     char *rc_s = pcmk__itoa(rc);
1790     char *status_s = pcmk__itoa(status);
1791     char *target_rc_s = pcmk__itoa(target_rc);
1792 
1793     crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent);
1794 
1795     if(rsc) {
1796         setenv("CRM_notify_rsc", rsc, 1);
1797     }
1798     if (options.external_recipient) {
1799         setenv("CRM_notify_recipient", options.external_recipient, 1);
1800     }
1801     setenv("CRM_notify_node", node, 1);
1802     setenv("CRM_notify_task", task, 1);
1803     setenv("CRM_notify_desc", desc, 1);
1804     setenv("CRM_notify_rc", rc_s, 1);
1805     setenv("CRM_notify_target_rc", target_rc_s, 1);
1806     setenv("CRM_notify_status", status_s, 1);
1807 
1808     pid = fork();
1809     if (pid == -1) {
1810         out->err(out, "notification fork() failed: %s", strerror(errno));
1811     }
1812     if (pid == 0) {
1813         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
1814         execl(options.external_agent, options.external_agent, NULL);
1815         crm_exit(CRM_EX_ERROR);
1816     }
1817 
1818     crm_trace("Finished running custom notification program '%s'.", options.external_agent);
1819     free(target_rc_s);
1820     free(status_s);
1821     free(rc_s);
1822     return 0;
1823 }
1824 
1825 static int
1826 handle_rsc_op(xmlNode *xml, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1827 {
1828     const char *node_id = (const char *) userdata;
1829     int rc = -1;
1830     int status = -1;
1831     int target_rc = -1;
1832     gboolean notify = TRUE;
1833 
1834     char *rsc = NULL;
1835     char *task = NULL;
1836     const char *desc = NULL;
1837     const char *magic = NULL;
1838     const char *id = NULL;
1839     const char *node = NULL;
1840 
1841     xmlNode *n = xml;
1842     xmlNode * rsc_op = xml;
1843 
1844     if(strcmp((const char*)xml->name, PCMK__XE_LRM_RSC_OP) != 0) {
1845         pcmk__xe_foreach_child(xml, NULL, handle_rsc_op, (void *) node_id);
1846         return pcmk_rc_ok;
1847     }
1848 
1849     id = pcmk__xe_history_key(rsc_op);
1850 
1851     magic = crm_element_value(rsc_op, PCMK__XA_TRANSITION_MAGIC);
1852     if (magic == NULL) {
1853         /* non-change */
1854         return pcmk_rc_ok;
1855     }
1856 
1857     if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc,
1858                                  &target_rc)) {
1859         crm_err("Invalid event %s detected for %s", magic, id);
1860         return pcmk_rc_ok;
1861     }
1862 
1863     if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
1864         crm_err("Invalid event detected for %s", id);
1865         goto bail;
1866     }
1867 
1868     node = crm_element_value(rsc_op, PCMK__META_ON_NODE);
1869 
1870     while ((n != NULL) && !pcmk__xe_is(n, PCMK__XE_NODE_STATE)) {
1871         n = n->parent;
1872     }
1873 
1874     if(node == NULL && n) {
1875         node = crm_element_value(n, PCMK_XA_UNAME);
1876     }
1877 
1878     if (node == NULL && n) {
1879         node = pcmk__xe_id(n);
1880     }
1881 
1882     if (node == NULL) {
1883         node = node_id;
1884     }
1885 
1886     if (node == NULL) {
1887         crm_err("No node detected for event %s (%s)", magic, id);
1888         goto bail;
1889     }
1890 
1891     /* look up where we expected it to be? */
1892     desc = pcmk_rc_str(pcmk_rc_ok);
1893     if ((status == PCMK_EXEC_DONE) && (target_rc == rc)) {
1894         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
1895         if (rc == PCMK_OCF_NOT_RUNNING) {
1896             notify = FALSE;
1897         }
1898 
1899     } else if (status == PCMK_EXEC_DONE) {
1900         desc = services_ocf_exitcode_str(rc);
1901         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
1902 
1903     } else {
1904         desc = pcmk_exec_status_str(status);
1905         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
1906     }
1907 
1908     if (notify && options.external_agent) {
1909         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
1910     }
1911 
1912   bail:
1913     free(rsc);
1914     free(task);
1915     return pcmk_rc_ok;
1916 }
1917 
1918 /* This function is just a wrapper around mainloop_set_trigger so that it can be
1919  * called from a mainloop directly.  It's simply another way of ensuring the screen
1920  * gets redrawn.
1921  */
1922 static gboolean
1923 mon_trigger_refresh(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1924 {
1925     mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
1926     return FALSE;
1927 }
1928 
1929 static int
1930 handle_op_for_node(xmlNode *xml, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1931 {
1932     const char *node = crm_element_value(xml, PCMK_XA_UNAME);
1933 
1934     if (node == NULL) {
1935         node = pcmk__xe_id(xml);
1936     }
1937 
1938     handle_rsc_op(xml, (void *) node);
1939     return pcmk_rc_ok;
1940 }
1941 
1942 static int
1943 crm_diff_update_element_v2(xmlNode *change, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1944 {
1945     const char *name = NULL;
1946     const char *op = crm_element_value(change, PCMK_XA_OPERATION);
1947     const char *xpath = crm_element_value(change, PCMK_XA_PATH);
1948     xmlNode *match = NULL;
1949     const char *node = NULL;
1950 
1951     if (op == NULL) {
1952         return pcmk_rc_ok;
1953 
1954     } else if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
1955         match = change->children;
1956 
1957     } else if (pcmk__str_any_of(op, PCMK_VALUE_MOVE, PCMK_VALUE_DELETE,
1958                                 NULL)) {
1959         return pcmk_rc_ok;
1960 
1961     } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
1962         match = pcmk__xe_first_child(change, PCMK_XE_CHANGE_RESULT, NULL, NULL);
1963         if(match) {
1964             match = match->children;
1965         }
1966     }
1967 
1968     if(match) {
1969         name = (const char *)match->name;
1970     }
1971 
1972     crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
1973     if(xpath == NULL) {
1974         /* Version field, ignore */
1975 
1976     } else if(name == NULL) {
1977         crm_debug("No result for %s operation to %s", op, xpath);
1978         pcmk__assert(pcmk__str_any_of(op, PCMK_VALUE_MOVE, PCMK_VALUE_DELETE,
1979                                       NULL));
1980 
1981     } else if (strcmp(name, PCMK_XE_CIB) == 0) {
1982         pcmk__xe_foreach_child(pcmk__xe_first_child(match, PCMK_XE_STATUS, NULL,
1983                                                     NULL),
1984                                NULL, handle_op_for_node, NULL);
1985 
1986     } else if (strcmp(name, PCMK_XE_STATUS) == 0) {
1987         pcmk__xe_foreach_child(match, NULL, handle_op_for_node, NULL);
1988 
1989     } else if (strcmp(name, PCMK__XE_NODE_STATE) == 0) {
1990         node = crm_element_value(match, PCMK_XA_UNAME);
1991         if (node == NULL) {
1992             node = pcmk__xe_id(match);
1993         }
1994         handle_rsc_op(match, (void *) node);
1995 
1996     } else if (strcmp(name, PCMK__XE_LRM) == 0) {
1997         node = pcmk__xe_id(match);
1998         handle_rsc_op(match, (void *) node);
1999 
2000     } else if (strcmp(name, PCMK__XE_LRM_RESOURCES) == 0) {
2001         char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
2002 
2003         handle_rsc_op(match, local_node);
2004         free(local_node);
2005 
2006     } else if (strcmp(name, PCMK__XE_LRM_RESOURCE) == 0) {
2007         char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
2008 
2009         handle_rsc_op(match, local_node);
2010         free(local_node);
2011 
2012     } else if (strcmp(name, PCMK__XE_LRM_RSC_OP) == 0) {
2013         char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
2014 
2015         handle_rsc_op(match, local_node);
2016         free(local_node);
2017 
2018     } else {
2019         crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
2020     }
2021 
2022     return pcmk_rc_ok;
2023 }
2024 
2025 static void
2026 crm_diff_update_v2(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
2027 {
2028     xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT,
2029                                             NULL, NULL);
2030     xmlNode *diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
2031 
2032     pcmk__xe_foreach_child(diff, NULL, crm_diff_update_element_v2, NULL);
2033 }
2034 
2035 static void
2036 crm_diff_update_v1(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
2037 {
2038     /* Process operation updates */
2039     xmlXPathObject *xpathObj = xpath_search(msg,
2040                                             "//" PCMK__XE_CIB_UPDATE_RESULT
2041                                             "//" PCMK__XE_DIFF_ADDED
2042                                             "//" PCMK__XE_LRM_RSC_OP);
2043     int lpc = 0, max = numXpathResults(xpathObj);
2044 
2045     for (lpc = 0; lpc < max; lpc++) {
2046         xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
2047 
2048         handle_rsc_op(rsc_op, NULL);
2049     }
2050     freeXpathObject(xpathObj);
2051 }
2052 
2053 static void
2054 crm_diff_update(const char *event, xmlNode * msg)
     /* [previous][next][first][last][top][bottom][index][help] */
2055 {
2056     int rc = -1;
2057     static bool stale = FALSE;
2058     gboolean cib_updated = FALSE;
2059     xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT,
2060                                             NULL, NULL);
2061     xmlNode *diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
2062 
2063     out->progress(out, false);
2064 
2065     if (current_cib != NULL) {
2066         rc = xml_apply_patchset(current_cib, diff, TRUE);
2067 
2068         switch (rc) {
2069             case -pcmk_err_diff_resync:
2070             case -pcmk_err_diff_failed:
2071                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
2072                 free_xml(current_cib); current_cib = NULL;
2073                 break;
2074             case pcmk_ok:
2075                 cib_updated = TRUE;
2076                 break;
2077             default:
2078                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
2079                 free_xml(current_cib); current_cib = NULL;
2080         }
2081     }
2082 
2083     if (current_cib == NULL) {
2084         crm_trace("Re-requesting the full cib");
2085         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
2086     }
2087 
2088     if (options.external_agent) {
2089         int format = 0;
2090         crm_element_value_int(diff, PCMK_XA_FORMAT, &format);
2091         switch(format) {
2092             case 1:
2093                 crm_diff_update_v1(event, msg);
2094                 break;
2095             case 2:
2096                 crm_diff_update_v2(event, msg);
2097                 break;
2098             default:
2099                 crm_err("Unknown patch format: %d", format);
2100         }
2101     }
2102 
2103     if (current_cib == NULL) {
2104         if(!stale) {
2105             out->info(out, "--- Stale data ---");
2106         }
2107         stale = TRUE;
2108         return;
2109     }
2110 
2111     stale = FALSE;
2112     refresh_after_event(cib_updated, FALSE);
2113 }
2114 
2115 static int
2116 mon_refresh_display(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2117 {
2118     int rc = pcmk_rc_ok;
2119 
2120     last_refresh = time(NULL);
2121 
2122     if (output_format == mon_output_none) {
2123         return G_SOURCE_REMOVE;
2124     }
2125 
2126     if (fence_history == pcmk__fence_history_full &&
2127         !pcmk_all_flags_set(show, pcmk_section_fencing_all) &&
2128         output_format != mon_output_xml) {
2129         fence_history = pcmk__fence_history_reduced;
2130     }
2131 
2132     // Get an up-to-date pacemakerd status for the cluster summary
2133     if (cib->variant == cib_native) {
2134         pcmk__pacemakerd_status(out, crm_system_name, options.reconnect_ms / 2,
2135                                 false, &pcmkd_state);
2136     }
2137 
2138     if (out->dest != stdout) {
2139         out->reset(out);
2140     }
2141 
2142     rc = pcmk__output_cluster_status(out, st, cib, current_cib, pcmkd_state,
2143                                      fence_history, show, show_opts,
2144                                      options.only_node,options.only_rsc,
2145                                      options.neg_location_prefix,
2146                                      output_format == mon_output_monitor);
2147 
2148     if (output_format == mon_output_monitor && rc != pcmk_rc_ok) {
2149         clean_up(MON_STATUS_WARN);
2150         return G_SOURCE_REMOVE;
2151     } else if (rc == pcmk_rc_schema_validation) {
2152         clean_up(CRM_EX_CONFIG);
2153         return G_SOURCE_REMOVE;
2154     }
2155 
2156     if (out->dest != stdout) {
2157         out->finish(out, CRM_EX_OK, true, NULL);
2158     }
2159 
2160     return G_SOURCE_CONTINUE;
2161 }
2162 
2163 /* This function is called for fencing events (see setup_fencer_connection() for
2164  * which ones) when --watch-fencing is used on the command line
2165  */
2166 static void
2167 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
     /* [previous][next][first][last][top][bottom][index][help] */
2168 {
2169     if (st->state == stonith_disconnected) {
2170         /* disconnect cib as well and have everything reconnect */
2171         mon_cib_connection_destroy(NULL);
2172     } else if (options.external_agent) {
2173         char *desc = stonith__event_description(e);
2174 
2175         send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
2176         free(desc);
2177     }
2178 }
2179 
2180 /* Cause the screen to be redrawn (via mainloop_set_trigger) when various conditions are met:
2181  *
2182  * - If the last update occurred more than reconnect_ms ago (defaults to 5s, but
2183  *   can be changed via the -i command line option), or
2184  * - After every 10 CIB updates, or
2185  * - If it's been 2s since the last update
2186  *
2187  * This function sounds like it would be more broadly useful, but it is only called when a
2188  * fencing event is received or a CIB diff occurrs.
2189  */
2190 static void
2191 refresh_after_event(gboolean data_updated, gboolean enforce)
     /* [previous][next][first][last][top][bottom][index][help] */
2192 {
2193     static int updates = 0;
2194     time_t now = time(NULL);
2195 
2196     if (data_updated) {
2197         updates++;
2198     }
2199 
2200     if(refresh_timer == NULL) {
2201         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
2202     }
2203 
2204     if (reconnect_timer > 0) {
2205         /* we will receive a refresh request after successful reconnect */
2206         mainloop_timer_stop(refresh_timer);
2207         return;
2208     }
2209 
2210     /* as we're not handling initial failure of fencer-connection as
2211      * fatal give it a retry here
2212      * not getting here if cib-reconnection is already on the way
2213      */
2214     setup_fencer_connection();
2215 
2216     if (enforce ||
2217         ((now - last_refresh) > (options.reconnect_ms / 1000)) ||
2218         updates >= 10) {
2219         mainloop_set_trigger((crm_trigger_t *) refresh_trigger);
2220         mainloop_timer_stop(refresh_timer);
2221         updates = 0;
2222 
2223     } else {
2224         mainloop_timer_start(refresh_timer);
2225     }
2226 }
2227 
2228 /* This function is called for fencing events (see setup_fencer_connection() for
2229  * which ones) when --watch-fencing is NOT used on the command line
2230  */
2231 static void
2232 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
     /* [previous][next][first][last][top][bottom][index][help] */
2233 {
2234     if (st->state == stonith_disconnected) {
2235         /* disconnect cib as well and have everything reconnect */
2236         mon_cib_connection_destroy(NULL);
2237     } else {
2238         out->progress(out, false);
2239         refresh_after_event(TRUE, FALSE);
2240     }
2241 }
2242 
2243 /*
2244  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
2245  * deallocate memory and show usage-message if requested.
2246  *
2247  * We don't actually return, but nominally returning crm_exit_t allows a usage
2248  * like "return clean_up(exit_code);" which helps static analysis understand the
2249  * code flow.
2250  */
2251 static crm_exit_t
2252 clean_up(crm_exit_t exit_code)
     /* [previous][next][first][last][top][bottom][index][help] */
2253 {
2254     /* Quitting crm_mon is much more complicated than it ought to be. */
2255 
2256     /* (1) Close connections, free things, etc. */
2257     if (io_channel != NULL) {
2258         g_io_channel_shutdown(io_channel, TRUE, NULL);
2259     }
2260 
2261     cib__clean_up_connection(&cib);
2262     stonith_api_delete(st);
2263     free(options.neg_location_prefix);
2264     free(options.only_node);
2265     free(options.only_rsc);
2266     free(options.pid_file);
2267     g_slist_free_full(options.includes_excludes, free);
2268 
2269     g_strfreev(processed_args);
2270 
2271     /* (2) If this is abnormal termination and we're in curses mode, shut down
2272      * curses first.  Any messages displayed to the screen before curses is shut
2273      * down will be lost because doing the shut down will also restore the
2274      * screen to whatever it looked like before crm_mon was started.
2275      */
2276     if (((error != NULL) || (exit_code == CRM_EX_USAGE))
2277         && (output_format == mon_output_console)
2278         && (out != NULL)) {
2279 
2280         out->finish(out, exit_code, false, NULL);
2281         pcmk__output_free(out);
2282         out = NULL;
2283     }
2284 
2285     /* (3) If this is a command line usage related failure, print the usage
2286      * message.
2287      */
2288     if (exit_code == CRM_EX_USAGE && (output_format == mon_output_console || output_format == mon_output_plain)) {
2289         char *help = g_option_context_get_help(context, TRUE, NULL);
2290 
2291         fprintf(stderr, "%s", help);
2292         g_free(help);
2293     }
2294 
2295     pcmk__free_arg_context(context);
2296 
2297     /* (4) If this is any kind of error, print the error out and exit.  Make
2298      * sure to handle situations both before and after formatted output is
2299      * set up.  We want errors to appear formatted if at all possible.
2300      */
2301     if (error != NULL) {
2302         if (out != NULL) {
2303             out->err(out, "%s: %s", g_get_prgname(), error->message);
2304             out->finish(out, exit_code, true, NULL);
2305             pcmk__output_free(out);
2306         } else {
2307             fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
2308         }
2309 
2310         g_clear_error(&error);
2311         crm_exit(exit_code);
2312     }
2313 
2314     /* (5) Print formatted output to the screen if we made it far enough in
2315      * crm_mon to be able to do so.
2316      */
2317     if (out != NULL) {
2318         if (options.exec_mode != mon_exec_daemonized) {
2319             out->finish(out, exit_code, true, NULL);
2320         }
2321 
2322         pcmk__output_free(out);
2323         pcmk__unregister_formats();
2324     }
2325 
2326     crm_exit(exit_code);
2327 }

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