root/tools/crm_mon.c

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

DEFINITIONS

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

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

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