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. one_shot_cb
  19. print_brief_cb
  20. print_clone_detail_cb
  21. print_pending_cb
  22. print_timing_cb
  23. reconnect_cb
  24. show_attributes_cb
  25. show_bans_cb
  26. show_failcounts_cb
  27. show_operations_cb
  28. show_tickets_cb
  29. use_cib_file_cb
  30. watch_fencing_cb
  31. mon_timer_popped
  32. do_mon_cib_connection_destroy
  33. mon_cib_connection_destroy_regular
  34. mon_cib_connection_destroy_error
  35. mon_shutdown
  36. mon_winresize
  37. cib_connect
  38. get_option_desc
  39. detect_user_input
  40. avoid_zombies
  41. build_arg_context
  42. add_output_args
  43. reconcile_output_format
  44. main
  45. print_simple_status
  46. reduce_stonith_history
  47. send_custom_trap
  48. handle_rsc_op
  49. mon_trigger_refresh
  50. get_node_from_xpath
  51. crm_diff_update_v2
  52. crm_diff_update_v1
  53. crm_diff_update
  54. mon_refresh_display
  55. mon_st_callback_event
  56. kick_refresh
  57. mon_st_callback_display
  58. clean_up_connections
  59. clean_up

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

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