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

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