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

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