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