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
900 CRM_CHECK(cib != NULL, return EINVAL);
901
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, ¤t_cib);
908
909 if (rc == pcmk_rc_ok) {
910 rc = pcmk_legacy2rc(cib->cmds->set_connection_dnotify(cib,
911 mon_cib_connection_destroy));
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
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
928 if (rc != pcmk_rc_ok) {
929 if (rc == EPROTONOSUPPORT) {
930 out->err(out,
931 "CIB client does not support CIB diff "
932 "notifications");
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);
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(¤t_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(¤t_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, ¤t_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. */
|
(1) Event path: |
Condition "io_channel != NULL", taking true branch. |
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 */
|
(2) Event path: |
Condition "has_error", taking true branch. |
|
(3) Event path: |
Condition "output_format == mon_output_console", taking true branch. |
|
(4) Event path: |
Condition "out != NULL", taking true branch. |
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);
|
CID (unavailable; MK=1f52e2c8486c98060b7dad1f3f1c4ea3) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(5) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(6) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
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