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