1 /*
2  * Copyright (C) 2004-2015 Andrew Beekhof <andrew@beekhof.net>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This software is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 #include <crm_internal.h>
20 
21 #include <sys/param.h>
22 
23 #include <crm/crm.h>
24 
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <libgen.h>
34 #include <sys/utsname.h>
35 
36 #include <crm/msg_xml.h>
37 #include <crm/services.h>
38 #include <crm/lrmd.h>
39 #include <crm/common/internal.h>  /* crm_ends_with_ext */
40 #include <crm/common/ipc.h>
41 #include <crm/common/mainloop.h>
42 #include <crm/common/util.h>
43 #include <crm/common/xml.h>
44 
45 #include <crm/cib/internal.h>
46 #include <crm/pengine/status.h>
47 #include <crm/pengine/internal.h>
48 #include <../lib/pengine/unpack.h>
49 #include <../pengine/pengine.h>
50 #include <crm/stonith-ng.h>
51 
52 void clean_up_connections(void);
53 static int clean_up(int rc);
54 void crm_diff_update(const char *event, xmlNode * msg);
55 gboolean mon_refresh_display(gpointer user_data);
56 int cib_connect(gboolean full);
57 void mon_st_callback_event(stonith_t * st, stonith_event_t * e);
58 void mon_st_callback_display(stonith_t * st, stonith_event_t * e);
59 void kick_refresh(gboolean data_updated);
60 static char *get_node_display_name(node_t *node);
61 
62 /*
63  * Definitions indicating which items to print
64  */
65 
66 #define mon_show_times         (0x0001U)
67 #define mon_show_stack         (0x0002U)
68 #define mon_show_dc            (0x0004U)
69 #define mon_show_count         (0x0008U)
70 #define mon_show_nodes         (0x0010U)
71 #define mon_show_resources     (0x0020U)
72 #define mon_show_attributes    (0x0040U)
73 #define mon_show_failcounts    (0x0080U)
74 #define mon_show_operations    (0x0100U)
75 #define mon_show_tickets       (0x0200U)
76 #define mon_show_bans          (0x0400U)
77 #define mon_show_fence_history (0x0800U)
78 
79 #define mon_show_headers       (mon_show_times | mon_show_stack | mon_show_dc \
80                                | mon_show_count)
81 #define mon_show_default       (mon_show_headers | mon_show_nodes \
82                                | mon_show_resources)
83 #define mon_show_all           (mon_show_default | mon_show_attributes \
84                                | mon_show_failcounts | mon_show_operations \
85                                | mon_show_tickets | mon_show_bans \
86                                | mon_show_fence_history)
87 
88 unsigned int show = mon_show_default;
89 
90 /*
91  * Definitions indicating how to output
92  */
93 
94 enum mon_output_format_e {
95     mon_output_none,
96     mon_output_monitor,
97     mon_output_plain,
98     mon_output_console,
99     mon_output_xml,
100     mon_output_html,
101     mon_output_cgi
102 } output_format = mon_output_console;
103 
104 char *output_filename = NULL;   /* if sending output to a file, its name */
105 
106 /* other globals */
107 static GIOChannel *io_channel = NULL;
108 char *pid_file = NULL;
109 char *snmp_target = NULL;
110 char *snmp_community = NULL;
111 
112 gboolean group_by_node = FALSE;
113 gboolean inactive_resources = FALSE;
114 int reconnect_msec = 5000;
115 gboolean daemonize = FALSE;
116 GMainLoop *mainloop = NULL;
117 guint timer_id = 0;
118 mainloop_timer_t *refresh_timer = NULL;
119 static pe_working_set_t *mon_data_set = NULL;
120 GList *attr_list = NULL;
121 
122 const char *crm_mail_host = NULL;
123 const char *crm_mail_prefix = NULL;
124 const char *crm_mail_from = NULL;
125 const char *crm_mail_to = NULL;
126 const char *external_agent = NULL;
127 const char *external_recipient = NULL;
128 
129 cib_t *cib = NULL;
130 stonith_t *st = NULL;
131 xmlNode *current_cib = NULL;
132 
133 gboolean one_shot = FALSE;
134 gboolean has_warnings = FALSE;
135 gboolean print_timing = FALSE;
136 gboolean watch_fencing = FALSE;
137 gboolean fence_history = FALSE;
138 gboolean fence_full_history = FALSE;
139 gboolean fence_connect = FALSE;
140 int fence_history_level = 1;
141 gboolean print_brief = FALSE;
142 gboolean print_pending = TRUE;
143 gboolean print_clone_detail = FALSE;
144 #if CURSES_ENABLED
145 gboolean curses_console_initialized = FALSE;
146 #endif
147 
148 /* FIXME allow, detect, and correctly interpret glob pattern or regex? */
149 const char *print_neg_location_prefix = "";
150 
151 /* Never display node attributes whose name starts with one of these prefixes */
152 #define FILTER_STR { CRM_FAIL_COUNT_PREFIX, CRM_LAST_FAILURE_PREFIX,       \
153                      "shutdown", "terminate", "standby", "probe_complete", \
154                      "#", NULL }
155 
156 long last_refresh = 0;
157 crm_trigger_t *refresh_trigger = NULL;
158 
159 /*
160  * 1.3.6.1.4.1.32723 has been assigned to the project by IANA
161  * http://www.iana.org/assignments/enterprise-numbers
162  */
163 #define PACEMAKER_PREFIX "1.3.6.1.4.1.32723"
164 #define PACEMAKER_TRAP_PREFIX PACEMAKER_PREFIX ".1"
165 
166 #define snmp_crm_trap_oid   PACEMAKER_TRAP_PREFIX
167 #define snmp_crm_oid_node   PACEMAKER_TRAP_PREFIX ".1"
168 #define snmp_crm_oid_rsc    PACEMAKER_TRAP_PREFIX ".2"
169 #define snmp_crm_oid_task   PACEMAKER_TRAP_PREFIX ".3"
170 #define snmp_crm_oid_desc   PACEMAKER_TRAP_PREFIX ".4"
171 #define snmp_crm_oid_status PACEMAKER_TRAP_PREFIX ".5"
172 #define snmp_crm_oid_rc     PACEMAKER_TRAP_PREFIX ".6"
173 #define snmp_crm_oid_trc    PACEMAKER_TRAP_PREFIX ".7"
174 
175 /* Define exit codes for monitoring-compatible output */
176 #define MON_STATUS_OK   (0)
177 #define MON_STATUS_WARN (1)
178 #define MON_STATUS_CRIT (2)
179 #define MON_STATUS_UNKNOWN (3)
180 
181 /* Convenience macro for prettifying output (e.g. "node" vs "nodes") */
182 #define s_if_plural(i) (((i) == 1)? "" : "s")
183 
184 #if CURSES_ENABLED
185 #  define print_dot() if (output_format == mon_output_console) { \
186 	printw(".");				\
187 	clrtoeol();				\
188 	refresh();				\
189     } else {					\
190 	fprintf(stdout, ".");			\
191     }
192 #else
193 #  define print_dot() fprintf(stdout, ".");
194 #endif
195 
196 #if CURSES_ENABLED
197 #  define print_as(fmt, args...) if (output_format == mon_output_console) { \
198 	printw(fmt, ##args);				\
199 	clrtoeol();					\
200 	refresh();					\
201     } else {						\
202 	fprintf(stdout, fmt, ##args);			\
203     }
204 #else
205 #  define print_as(fmt, args...) fprintf(stdout, fmt, ##args);
206 #endif
207 
208 static void
blank_screen(void)209 blank_screen(void)
210 {
211 #if CURSES_ENABLED
212     int lpc = 0;
213 
214     for (lpc = 0; lpc < LINES; lpc++) {
215         move(lpc, 0);
216         clrtoeol();
217     }
218     move(0, 0);
219     refresh();
220 #endif
221 }
222 
223 static gboolean
mon_timer_popped(gpointer data)224 mon_timer_popped(gpointer data)
225 {
226     int rc = pcmk_ok;
227 
228 #if CURSES_ENABLED
229     if (output_format == mon_output_console) {
230         clear();
231         refresh();
232     }
233 #endif
234 
235     if (timer_id > 0) {
236         g_source_remove(timer_id);
237         timer_id = 0;
238     }
239 
240     print_as("Reconnecting...\n");
241     rc = cib_connect(TRUE);
242 
243     if (rc != pcmk_ok) {
244         timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
245     }
246     return FALSE;
247 }
248 
249 static void
mon_cib_connection_destroy(gpointer user_data)250 mon_cib_connection_destroy(gpointer user_data)
251 {
252     print_as("Connection to the cluster-daemons terminated\n");
253     if (refresh_timer != NULL) {
254         /* we'll trigger a refresh after reconnect */
255         mainloop_timer_stop(refresh_timer);
256     }
257     if (timer_id) {
258         /* we'll trigger a new reconnect-timeout at the end */
259         g_source_remove(timer_id);
260         timer_id = 0;
261     }
262     if (st) {
263         /* the client API won't properly reconnect notifications
264          * if they are still in the table - so remove them
265          */
266         st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
267         st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
268         st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
269         if (st->state != stonith_disconnected) {
270             st->cmds->disconnect(st);
271         }
272     }
273     if (cib) {
274         cib->cmds->signoff(cib);
275         timer_id = g_timeout_add(reconnect_msec, mon_timer_popped, NULL);
276     }
277     return;
278 }
279 
280 /*
281  * Mainloop signal handler.
282  */
283 static void
mon_shutdown(int nsig)284 mon_shutdown(int nsig)
285 {
286     clean_up(EX_OK);
287 }
288 
289 #if ON_DARWIN
290 #  define sighandler_t sig_t
291 #endif
292 
293 #if CURSES_ENABLED
294 #  ifndef HAVE_SIGHANDLER_T
295 typedef void (*sighandler_t) (int);
296 #  endif
297 static sighandler_t ncurses_winch_handler;
298 static void
mon_winresize(int nsig)299 mon_winresize(int nsig)
300 {
301     static int not_done;
302     int lines = 0, cols = 0;
303 
304     if (!not_done++) {
305         if (ncurses_winch_handler)
306             /* the original ncurses WINCH signal handler does the
307              * magic of retrieving the new window size;
308              * otherwise, we'd have to use ioctl or tgetent */
309             (*ncurses_winch_handler) (SIGWINCH);
310         getmaxyx(stdscr, lines, cols);
311         resizeterm(lines, cols);
312         mainloop_set_trigger(refresh_trigger);
313     }
314     not_done--;
315 }
316 #endif
317 
318 int
cib_connect(gboolean full)319 cib_connect(gboolean full)
320 {
321     int rc = pcmk_ok;
322     static gboolean need_pass = TRUE;
323 
324     CRM_CHECK(cib != NULL, return -EINVAL);
325 
326     if (getenv("CIB_passwd") != NULL) {
327         need_pass = FALSE;
328     }
329 
330     if ((fence_connect) && (st == NULL)) {
331         st = stonith_api_new();
332     }
333 
334     if ((fence_connect) && (st->state == stonith_disconnected)) {
335         crm_trace("Connecting to stonith");
336         rc = st->cmds->connect(st, crm_system_name, NULL);
337         if (rc == pcmk_ok) {
338             crm_trace("Setting up stonith callbacks");
339             if (watch_fencing) {
340                 st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
341                                                 mon_st_callback_event);
342                 st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event);
343             } else {
344                 st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT,
345                                                 mon_st_callback_display);
346                 st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display);
347             }
348         }
349     }
350 
351     if (cib->state != cib_connected_query && cib->state != cib_connected_command) {
352         crm_trace("Connecting to the CIB");
353         if ((output_format == mon_output_console) && need_pass && (cib->variant == cib_remote)) {
354             need_pass = FALSE;
355             print_as("Password:");
356         }
357 
358         rc = cib->cmds->signon(cib, crm_system_name, cib_query);
359 
360         if (rc != pcmk_ok) {
361             return rc;
362         }
363 
364         rc = cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
365         if (rc == pcmk_ok) {
366             mon_refresh_display(NULL);
367         }
368 
369         if (rc == pcmk_ok && full) {
370             if (rc == pcmk_ok) {
371                 rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy);
372                 if (rc == -EPROTONOSUPPORT) {
373                     print_as
374                         ("Notification setup not supported, won't be able to reconnect after failure");
375                     if (output_format == mon_output_console) {
376                         sleep(2);
377                     }
378                     rc = pcmk_ok;
379                 }
380 
381             }
382 
383             if (rc == pcmk_ok) {
384                 cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
385                 rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update);
386             }
387 
388             if (rc != pcmk_ok) {
389                 print_as("Notification setup failed, could not monitor CIB actions");
390                 if (output_format == mon_output_console) {
391                     sleep(2);
392                 }
393                 clean_up_connections();
394             }
395         }
396     }
397     return rc;
398 }
399 
400 /* *INDENT-OFF* */
401 static struct crm_option long_options[] = {
402     /* Top-level Options */
403     {"help",           0, 0, '?', "\tThis text"},
404     {"version",        0, 0, '$', "\tVersion information"  },
405     {"verbose",        0, 0, 'V', "\tIncrease debug output"},
406     {"quiet",          0, 0, 'Q', "\tDisplay only essential output" },
407 
408     {"-spacer-",	1, 0, '-', "\nModes (mutually exclusive):"},
409     {"as-html",        1, 0, 'h', "\tWrite cluster status to the named html file"},
410     {"as-xml",         0, 0, 'X', "\t\tWrite cluster status as xml to stdout. This will enable one-shot mode."},
411     {"web-cgi",        0, 0, 'w', "\t\tWeb mode with output suitable for CGI (preselected when run as *.cgi)"},
412     {"simple-status",  0, 0, 's', "\tDisplay the cluster status once as a simple one line output (suitable for nagios)"},
413     {"snmp-traps",     1, 0, 'S', "\tSend SNMP traps to this station", !ENABLE_SNMP},
414     {"snmp-community", 1, 0, 'C', "Specify community for SNMP traps(default is NULL)", !ENABLE_SNMP},
415     {"mail-to",        1, 0, 'T', "\tSend Mail alerts to this user.  See also --mail-from, --mail-host, --mail-prefix", !ENABLE_ESMTP},
416 
417     {"-spacer-",	1, 0, '-', "\nDisplay Options:"},
418     {"group-by-node",  0, 0, 'n', "\tGroup resources by node"     },
419     {"inactive",       0, 0, 'r', "\t\tDisplay inactive resources"  },
420     {"failcounts",     0, 0, 'f', "\tDisplay resource fail counts"},
421     {"operations",     0, 0, 'o', "\tDisplay resource operation history" },
422     {"timing-details", 0, 0, 't', "\tDisplay resource operation history with timing details" },
423     {"tickets",        0, 0, 'c', "\t\tDisplay cluster tickets"},
424     {"watch-fencing",  0, 0, 'W', "\tListen for fencing events. For use with --external-agent, --mail-to and/or --snmp-traps where supported"},
425     {"fence-history",  2, 0, 'm', "Show fence history\n"
426                                   "\t\t\t\t\t0=off, 1=failures and pending (default without option),\n"
427                                   "\t\t\t\t\t2=add successes (default without value for option),\n"
428                                   "\t\t\t\t\t3=show full history without reduction to most recent of each flavor"},
429     {"neg-locations",  2, 0, 'L', "Display negative location constraints [optionally filtered by id prefix]"},
430     {"show-node-attributes", 0, 0, 'A', "Display node attributes" },
431     {"hide-headers",   0, 0, 'D', "\tHide all headers" },
432     {"show-detail",    0, 0, 'R', "\tShow more details (node IDs, individual clone instances)" },
433     {"brief",          0, 0, 'b', "\t\tBrief output" },
434     {"pending",        0, 0, 'j', "\t\tDisplay pending state if 'record-pending' is enabled", pcmk_option_hidden},
435 
436     {"-spacer-",	1, 0, '-', "\nAdditional Options:"},
437     {"interval",       1, 0, 'i', "\tUpdate frequency in seconds" },
438     {"one-shot",       0, 0, '1', "\t\tDisplay the cluster status once on the console and exit"},
439     {"disable-ncurses",0, 0, 'N', "\tDisable the use of ncurses", !CURSES_ENABLED},
440     {"daemonize",      0, 0, 'd', "\tRun in the background as a daemon"},
441     {"pid-file",       1, 0, 'p', "\t(Advanced) Daemon pid file location"},
442     {"mail-from",      1, 0, 'F', "\tMail alerts should come from the named user", !ENABLE_ESMTP},
443     {"mail-host",      1, 0, 'H', "\tMail alerts should be sent via the named host", !ENABLE_ESMTP},
444     {"mail-prefix",    1, 0, 'P', "Subjects for mail alerts should start with this string", !ENABLE_ESMTP},
445     {"external-agent",    1, 0, 'E', "A program to run when resource operations take place."},
446     {"external-recipient",1, 0, 'e', "A recipient for your program (assuming you want the program to send something to someone)."},
447 
448 
449     {"xml-file",       1, 0, 'x', NULL, pcmk_option_hidden},
450 
451     {"-spacer-",	1, 0, '-', "\nExamples:", pcmk_option_paragraph},
452     {"-spacer-",	1, 0, '-', "Display the cluster status on the console with updates as they occur:", pcmk_option_paragraph},
453     {"-spacer-",	1, 0, '-', " crm_mon", pcmk_option_example},
454     {"-spacer-",	1, 0, '-', "Display the cluster status on the console just once then exit:", pcmk_option_paragraph},
455     {"-spacer-",	1, 0, '-', " crm_mon -1", pcmk_option_example},
456     {"-spacer-",	1, 0, '-', "Display your cluster status, group resources by node, and include inactive resources in the list:", pcmk_option_paragraph},
457     {"-spacer-",	1, 0, '-', " crm_mon --group-by-node --inactive", pcmk_option_example},
458     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:", pcmk_option_paragraph},
459     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --as-html /path/to/docroot/filename.html", pcmk_option_example},
460     {"-spacer-",	1, 0, '-', "Start crm_mon and export the current cluster status as xml to stdout, then exit.:", pcmk_option_paragraph},
461     {"-spacer-",	1, 0, '-', " crm_mon --as-xml", pcmk_option_example},
462     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it send email alerts:", pcmk_option_paragraph|!ENABLE_ESMTP},
463     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --mail-to user@example.com --mail-host mail.example.com", pcmk_option_example|!ENABLE_ESMTP},
464     {"-spacer-",	1, 0, '-', "Start crm_mon as a background daemon and have it send SNMP alerts:", pcmk_option_paragraph|!ENABLE_SNMP},
465     {"-spacer-",	1, 0, '-', " crm_mon --daemonize --snmp-traps snmptrapd.example.com", pcmk_option_example|!ENABLE_SNMP},
466 
467     {NULL, 0, 0, 0}
468 };
469 /* *INDENT-ON* */
470 
471 #if CURSES_ENABLED
472 static const char *
get_option_desc(char c)473 get_option_desc(char c)
474 {
475     int lpc;
476 
477     for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
478 
479         if (long_options[lpc].name[0] == '-')
480             continue;
481 
482         if (long_options[lpc].val == c) {
483             static char *buf = NULL;
484             const char *rv;
485             char *nl;
486 
487             /* chop off tabs and cut at newline */
488             free(buf); /* free string from last usage */
489             buf = strdup(long_options[lpc].desc);
490             rv = buf; /* make a copy to keep buf pointer unaltered
491                          for freeing when we come by next time.
492                          Like this the result stays valid until
493                          the next call.
494                        */
495             while(isspace(rv[0])) {
496                 rv++;
497             }
498             nl = strchr(rv, '\n');
499             if (nl) {
500                 *nl = '\0';
501             }
502             return rv;
503         }
504     }
505 
506     return NULL;
507 }
508 
509 #define print_option_help(option, condition) \
510     print_as("%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option));
511 
512 static gboolean
detect_user_input(GIOChannel * channel,GIOCondition condition,gpointer unused)513 detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer unused)
514 {
515     int c;
516     gboolean config_mode = FALSE;
517 
518     while (1) {
519 
520         /* Get user input */
521         c = getchar();
522 
523         switch (c) {
524             case 'm':
525                 if (!fence_history_level) {
526                     fence_history = TRUE;
527                     fence_connect = TRUE;
528                     if (st == NULL) {
529                         mon_cib_connection_destroy(NULL);
530                     }
531                 }
532                 show ^= mon_show_fence_history;
533                 break;
534             case 'c':
535                 show ^= mon_show_tickets;
536                 break;
537             case 'f':
538                 show ^= mon_show_failcounts;
539                 break;
540             case 'n':
541                 group_by_node = ! group_by_node;
542                 break;
543             case 'o':
544                 show ^= mon_show_operations;
545                 if ((show & mon_show_operations) == 0) {
546                     print_timing = 0;
547                 }
548                 break;
549             case 'r':
550                 inactive_resources = ! inactive_resources;
551                 break;
552             case 'R':
553                 print_clone_detail = ! print_clone_detail;
554                 break;
555             case 't':
556                 print_timing = ! print_timing;
557                 if (print_timing) {
558                     show |= mon_show_operations;
559                 }
560                 break;
561             case 'A':
562                 show ^= mon_show_attributes;
563                 break;
564             case 'L':
565                 show ^= mon_show_bans;
566                 break;
567             case 'D':
568                 /* If any header is shown, clear them all, otherwise set them all */
569                 if (show & mon_show_headers) {
570                     show &= ~mon_show_headers;
571                 } else {
572                     show |= mon_show_headers;
573                 }
574                 break;
575             case 'b':
576                 print_brief = ! print_brief;
577                 break;
578             case 'j':
579                 print_pending = ! print_pending;
580                 break;
581             case '?':
582                 config_mode = TRUE;
583                 break;
584             default:
585                 goto refresh;
586         }
587 
588         if (!config_mode)
589             goto refresh;
590 
591         blank_screen();
592 
593         print_as("Display option change mode\n");
594         print_as("\n");
595         print_option_help('c', show & mon_show_tickets);
596         print_option_help('f', show & mon_show_failcounts);
597         print_option_help('n', group_by_node);
598         print_option_help('o', show & mon_show_operations);
599         print_option_help('r', inactive_resources);
600         print_option_help('t', print_timing);
601         print_option_help('A', show & mon_show_attributes);
602         print_option_help('L', show & mon_show_bans);
603         print_option_help('D', (show & mon_show_headers) == 0);
604         print_option_help('R', print_clone_detail);
605         print_option_help('b', print_brief);
606         print_option_help('j', print_pending);
607         print_option_help('m', (show & mon_show_fence_history));
608         print_as("\n");
609         print_as("Toggle fields via field letter, type any other key to return");
610     }
611 
612 refresh:
613     mon_refresh_display(NULL);
614     return TRUE;
615 }
616 #endif
617 
618 int
main(int argc,char ** argv)619 main(int argc, char **argv)
620 {
621     int flag;
622     int argerr = 0;
623     int exit_code = 0;
624     int option_index = 0;
625 
626     pid_file = strdup("/tmp/ClusterMon.pid");
627     crm_log_cli_init("crm_mon");
628     crm_set_options(NULL, "mode [options]", long_options,
629                     "Provides a summary of cluster's current state."
630                     "\n\nOutputs varying levels of detail in a number of different formats.\n");
631 
632 #if !defined (ON_DARWIN) && !defined (ON_BSD)
633     /* prevent zombies */
634     signal(SIGCLD, SIG_IGN);
635 #endif
636 
637     if (crm_ends_with_ext(argv[0], ".cgi") == TRUE) {
638         output_format = mon_output_cgi;
639         one_shot = TRUE;
640     }
641 
642     /* to enable stonith-connection when called via some application like pcs
643      * set environment-variable FENCE_HISTORY to desired level
644      * so you don't have to modify this application
645      */
646     /* fence_history_level = crm_atoi(getenv("FENCE_HISTORY"), "0"); */
647 
648     while (1) {
649         flag = crm_get_option(argc, argv, &option_index);
650         if (flag == -1)
651             break;
652 
653         switch (flag) {
654             case 'V':
655                 crm_bump_log_level(argc, argv);
656                 break;
657             case 'Q':
658                 show &= ~mon_show_times;
659                 break;
660             case 'i':
661                 reconnect_msec = crm_get_msec(optarg);
662                 break;
663             case 'n':
664                 group_by_node = TRUE;
665                 break;
666             case 'r':
667                 inactive_resources = TRUE;
668                 break;
669             case 'W':
670                 watch_fencing = TRUE;
671                 fence_connect = TRUE;
672                 break;
673             case 'm':
674                 fence_history_level = crm_atoi(optarg, "2");
675                 break;
676             case 'd':
677                 daemonize = TRUE;
678                 break;
679             case 't':
680                 print_timing = TRUE;
681                 show |= mon_show_operations;
682                 break;
683             case 'o':
684                 show |= mon_show_operations;
685                 break;
686             case 'f':
687                 show |= mon_show_failcounts;
688                 break;
689             case 'A':
690                 show |= mon_show_attributes;
691                 break;
692             case 'L':
693                 show |= mon_show_bans;
694                 print_neg_location_prefix = optarg? optarg : "";
695                 break;
696             case 'D':
697                 show &= ~mon_show_headers;
698                 break;
699             case 'b':
700                 print_brief = TRUE;
701                 break;
702             case 'j':
703                 print_pending = TRUE;
704                 break;
705             case 'R':
706                 print_clone_detail = TRUE;
707                 break;
708             case 'c':
709                 show |= mon_show_tickets;
710                 break;
711             case 'p':
712                 free(pid_file);
713                 if(optarg == NULL) {
714                     return crm_help(flag, EX_USAGE);
715                 }
716                 pid_file = strdup(optarg);
717                 break;
718             case 'x':
719                 if(optarg == NULL) {
720                     return crm_help(flag, EX_USAGE);
721                 }
722                 setenv("CIB_file", optarg, 1);
723                 one_shot = TRUE;
724                 break;
725             case 'h':
726                 if(optarg == NULL) {
727                     return crm_help(flag, EX_USAGE);
728                 }
729                 argerr += (output_format != mon_output_console);
730                 output_format = mon_output_html;
731                 output_filename = strdup(optarg);
732                 umask(S_IWGRP | S_IWOTH);
733                 break;
734             case 'X':
735                 argerr += (output_format != mon_output_console);
736                 output_format = mon_output_xml;
737                 one_shot = TRUE;
738                 break;
739             case 'w':
740                 /* do not allow argv[0] and argv[1...] redundancy */
741                 argerr += (output_format != mon_output_console);
742                 output_format = mon_output_cgi;
743                 one_shot = TRUE;
744                 break;
745             case 's':
746                 argerr += (output_format != mon_output_console);
747                 output_format = mon_output_monitor;
748                 one_shot = TRUE;
749                 break;
750             case 'S':
751                 snmp_target = optarg;
752                 break;
753             case 'T':
754                 crm_mail_to = optarg;
755                 break;
756             case 'F':
757                 crm_mail_from = optarg;
758                 break;
759             case 'H':
760                 crm_mail_host = optarg;
761                 break;
762             case 'P':
763                 crm_mail_prefix = optarg;
764                 break;
765             case 'E':
766                 external_agent = optarg;
767                 break;
768             case 'e':
769                 external_recipient = optarg;
770                 break;
771             case '1':
772                 one_shot = TRUE;
773                 break;
774             case 'N':
775                 if (output_format == mon_output_console) {
776                     output_format = mon_output_plain;
777                 }
778                 break;
779             case 'C':
780                 snmp_community = optarg;
781                 break;
782             case '$':
783             case '?':
784                 return crm_help(flag, EX_OK);
785                 break;
786             default:
787                 printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag);
788                 ++argerr;
789                 break;
790         }
791     }
792 
793     if (watch_fencing) {
794         /* don't moan as fence_history_level == 1 is default */
795         fence_history_level = 0;
796     }
797 
798     /* create the cib-object early to be able to do further
799      * decisions based on the cib-source
800      */
801     cib = cib_new();
802 
803     if (cib == NULL) {
804         exit_code = -EINVAL;
805     } else {
806         switch (cib->variant) {
807 
808             case cib_native:
809                 /* cib & fencing - everything available */
810                 break;
811 
812             case cib_file:
813                 /* Don't try to connect to fencing as we
814                  * either don't have a running cluster or
815                  * the fencing-information would possibly
816                  * not match the cib data from a file.
817                  * As we don't expect cib-updates coming
818                  * in enforce one-shot. */
819                 fence_history_level = 0;
820                 one_shot = TRUE;
821                 break;
822 
823             case cib_remote:
824                 /* updates coming in but no fencing */
825                 fence_history_level = 0;
826                 break;
827 
828             case cib_undefined:
829             case cib_database:
830             default:
831                 /* something is odd */
832                 exit_code = -EINVAL;
833                 crm_err("Invalid cib-source");
834                 break;
835         }
836     }
837 
838     switch (fence_history_level) {
839         case 3:
840             fence_full_history = TRUE;
841             /* fall through to next lower level */
842         case 2:
843             show |= mon_show_fence_history;
844             /* fall through to next lower level */
845         case 1:
846             fence_history = TRUE;
847             fence_connect = TRUE;
848             break;
849         default:
850             break;
851     }
852 
853     /* Extra sanity checks when in CGI mode */
854     if (output_format == mon_output_cgi) {
855         argerr += (optind < argc);
856         argerr += (output_filename != NULL);
857         argerr += ((cib) && (cib->variant == cib_file));
858         argerr += (snmp_target != NULL);
859         argerr += (crm_mail_to != NULL);
860         argerr += (external_agent != NULL);
861         argerr += (daemonize == TRUE);  /* paranoia */
862 
863     } else if (optind < argc) {
864         printf("non-option ARGV-elements: ");
865         while (optind < argc)
866             printf("%s ", argv[optind++]);
867         printf("\n");
868     }
869 
870     if (argerr) {
871         return clean_up(EX_USAGE);
872     }
873 
874     /* XML output always prints everything */
875     if (output_format == mon_output_xml) {
876         show = mon_show_all;
877         print_timing = TRUE;
878     }
879 
880     if (one_shot) {
881         if (output_format == mon_output_console) {
882             output_format = mon_output_plain;
883         }
884 
885     } else if (daemonize) {
886         if ((output_format == mon_output_console) || (output_format == mon_output_plain)) {
887             output_format = mon_output_none;
888         }
889         crm_enable_stderr(FALSE);
890 
891         if ((output_format != mon_output_html) && (output_format != mon_output_xml)
892             && !snmp_target && !crm_mail_to && !external_agent) {
893             printf ("Looks like you forgot to specify one or more of: "
894                     "--as-html, --external-agent, --mail-to, --snmp-target\n");
895             return clean_up(EX_USAGE);
896         }
897 
898         if (cib) {
899             /* to be on the safe side don't have cib-object around
900              * when we are forking
901              */
902             cib_delete(cib);
903             cib = NULL;
904             crm_make_daemon(crm_system_name, TRUE, pid_file);
905             cib = cib_new();
906             if (cib == NULL) {
907                 exit_code = -EINVAL;
908             }
909             /* otherwise assume we've got the same cib-object we've just destroyed
910              * in our parent
911              */
912         }
913 
914     } else if (output_format == mon_output_console) {
915 #if CURSES_ENABLED
916         initscr();
917         cbreak();
918         noecho();
919         crm_enable_stderr(FALSE);
920         curses_console_initialized = TRUE;
921 #else
922         one_shot = TRUE;
923         output_format = mon_output_plain;
924         printf("Defaulting to one-shot mode\n");
925         printf("You need to have curses available at compile time to enable console mode\n");
926 #endif
927     }
928 
929     crm_info("Starting %s", crm_system_name);
930 
931     if (cib) {
932 
933         do {
934             if (!one_shot) {
935                 print_as("Waiting until cluster is available on this node ...\n");
936             }
937             exit_code = cib_connect(!one_shot);
938 
939             if (one_shot) {
940                 break;
941             } else if (exit_code != pcmk_ok) {
942                 sleep(reconnect_msec / 1000);
943 #if CURSES_ENABLED
944                 if (output_format == mon_output_console) {
945                     clear();
946                     refresh();
947                 }
948 #endif
949             } else {
950                 if (output_format == mon_output_html) {
951                     print_as("Writing html to %s ...\n", output_filename);
952                 }
953             }
954         } while (exit_code == -ENOTCONN);
955     }
956 
957     if (exit_code != pcmk_ok) {
958         if (output_format == mon_output_monitor) {
959             printf("CLUSTER CRIT: Connection to cluster failed: %s\n",
960                    pcmk_strerror(exit_code));
961             return clean_up(MON_STATUS_CRIT);
962         } else {
963             if (exit_code == -ENOTCONN) {
964                 print_as("\nError: cluster is not available on this node\n");
965             } else {
966                 print_as("\nConnection to cluster failed: %s\n",
967                          pcmk_strerror(exit_code));
968             }
969         }
970         if (output_format == mon_output_console) {
971             sleep(2);
972         }
973         return clean_up(-exit_code);
974     }
975 
976     if (one_shot) {
977         return clean_up(0);
978     }
979 
980     mainloop = g_main_new(FALSE);
981 
982     mainloop_add_signal(SIGTERM, mon_shutdown);
983     mainloop_add_signal(SIGINT, mon_shutdown);
984 #if CURSES_ENABLED
985     if (output_format == mon_output_console) {
986         ncurses_winch_handler = signal(SIGWINCH, mon_winresize);
987         if (ncurses_winch_handler == SIG_DFL ||
988             ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR)
989             ncurses_winch_handler = NULL;
990 
991         io_channel = g_io_channel_unix_new(STDIN_FILENO);
992         g_io_add_watch(io_channel, G_IO_IN, detect_user_input, NULL);
993     }
994 #endif
995     refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL);
996 
997     g_main_run(mainloop);
998     g_main_destroy(mainloop);
999 
1000     if (io_channel != NULL) {
1001         g_io_channel_shutdown(io_channel, TRUE, NULL);
1002     }
1003 
1004     crm_info("Exiting %s", crm_system_name);
1005 
1006     return clean_up(0);
1007 }
1008 
1009 #define mon_warn(fmt...) do {			\
1010 	if (!has_warnings) {			\
1011 	    print_as("CLUSTER WARN:");		\
1012 	} else {				\
1013 	    print_as(",");			\
1014 	}					\
1015 	print_as(fmt);				\
1016 	has_warnings = TRUE;			\
1017     } while(0)
1018 
1019 
1020 /*!
1021  * \internal
1022  * \brief Print one-line status suitable for use with monitoring software
1023  *
1024  * \param[in] data_set  Working set of CIB state
1025  * \param[in] history   List of stonith actions
1026  *
1027  * \note This function's output (and the return code when the program exits)
1028  *       should conform to https://www.monitoring-plugins.org/doc/guidelines.html
1029  */
1030 static void
print_simple_status(pe_working_set_t * data_set,stonith_history_t * history)1031 print_simple_status(pe_working_set_t * data_set,
1032                     stonith_history_t *history)
1033 {
1034     GListPtr gIter = NULL;
1035     int nodes_online = 0;
1036     int nodes_standby = 0;
1037     int nodes_maintenance = 0;
1038 
1039     if (data_set->dc_node == NULL) {
1040         mon_warn(" No DC");
1041     }
1042 
1043     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
1044         node_t *node = (node_t *) gIter->data;
1045 
1046         if (node->details->standby && node->details->online) {
1047             nodes_standby++;
1048         } else if (node->details->maintenance && node->details->online) {
1049             nodes_maintenance++;
1050         } else if (node->details->online) {
1051             nodes_online++;
1052         } else {
1053             mon_warn(" offline node: %s", node->details->uname);
1054         }
1055     }
1056 
1057     if (!has_warnings) {
1058 
1059         print_as("CLUSTER OK: %d node%s online", nodes_online, s_if_plural(nodes_online));
1060         if (nodes_standby > 0) {
1061             print_as(", %d standby node%s", nodes_standby, s_if_plural(nodes_standby));
1062         }
1063         if (nodes_maintenance > 0) {
1064             print_as(", %d maintenance node%s", nodes_maintenance, s_if_plural(nodes_maintenance));
1065         }
1066         print_as(", %d resource instance%s configured",
1067                  data_set->ninstances, s_if_plural(data_set->ninstances));
1068     }
1069 
1070     print_as("\n");
1071 }
1072 
1073 /*!
1074  * \internal
1075  * \brief Print a [name]=[value][units] pair, optionally using time string
1076  *
1077  * \param[in] stream      File stream to display output to
1078  * \param[in] name        Name to display
1079  * \param[in] value       Value to display (or NULL to convert time instead)
1080  * \param[in] units       Units to display (or NULL for no units)
1081  * \param[in] epoch_time  Epoch time to convert if value is NULL
1082  */
1083 static void
print_nvpair(FILE * stream,const char * name,const char * value,const char * units,time_t epoch_time)1084 print_nvpair(FILE *stream, const char *name, const char *value,
1085              const char *units, time_t epoch_time)
1086 {
1087     /* print name= */
1088     switch (output_format) {
1089         case mon_output_plain:
1090         case mon_output_console:
1091             print_as(" %s=", name);
1092             break;
1093 
1094         case mon_output_html:
1095         case mon_output_cgi:
1096         case mon_output_xml:
1097             fprintf(stream, " %s=", name);
1098             break;
1099 
1100         default:
1101             break;
1102     }
1103 
1104     /* If we have a value (and optionally units), print it */
1105     if (value) {
1106         switch (output_format) {
1107             case mon_output_plain:
1108             case mon_output_console:
1109                 print_as("%s%s", value, (units? units : ""));
1110                 break;
1111 
1112             case mon_output_html:
1113             case mon_output_cgi:
1114                 fprintf(stream, "%s%s", value, (units? units : ""));
1115                 break;
1116 
1117             case mon_output_xml:
1118                 fprintf(stream, "\"%s%s\"", value, (units? units : ""));
1119                 break;
1120 
1121             default:
1122                 break;
1123         }
1124 
1125     /* Otherwise print user-friendly time string */
1126     } else {
1127         static char empty_str[] = "";
1128         char *c, *date_str = asctime(localtime(&epoch_time));
1129 
1130         for (c = (date_str != NULL) ? date_str : empty_str; *c != '\0'; ++c) {
1131             if (*c == '\n') {
1132                 *c = '\0';
1133                 break;
1134             }
1135         }
1136         switch (output_format) {
1137             case mon_output_plain:
1138             case mon_output_console:
1139                 print_as("'%s'", date_str);
1140                 break;
1141 
1142             case mon_output_html:
1143             case mon_output_cgi:
1144             case mon_output_xml:
1145                 fprintf(stream, "\"%s\"", date_str);
1146                 break;
1147 
1148             default:
1149                 break;
1150         }
1151     }
1152 }
1153 
1154 /*!
1155  * \internal
1156  * \brief Print whatever is needed to start a node section
1157  *
1158  * \param[in] stream     File stream to display output to
1159  * \param[in] node       Node to print
1160  */
1161 static void
print_node_start(FILE * stream,node_t * node)1162 print_node_start(FILE *stream, node_t *node)
1163 {
1164     char *node_name;
1165 
1166     switch (output_format) {
1167         case mon_output_plain:
1168         case mon_output_console:
1169             node_name = get_node_display_name(node);
1170             print_as("* Node %s:\n", node_name);
1171             free(node_name);
1172             break;
1173 
1174         case mon_output_html:
1175         case mon_output_cgi:
1176             node_name = get_node_display_name(node);
1177             fprintf(stream, "  <h3>Node: %s</h3>\n  <ul>\n", node_name);
1178             free(node_name);
1179             break;
1180 
1181         case mon_output_xml:
1182             fprintf(stream, "        <node name=\"%s\">\n", node->details->uname);
1183             break;
1184 
1185         default:
1186             break;
1187     }
1188 }
1189 
1190 /*!
1191  * \internal
1192  * \brief Print whatever is needed to end a node section
1193  *
1194  * \param[in] stream     File stream to display output to
1195  */
1196 static void
print_node_end(FILE * stream)1197 print_node_end(FILE *stream)
1198 {
1199     switch (output_format) {
1200         case mon_output_html:
1201         case mon_output_cgi:
1202             fprintf(stream, "  </ul>\n");
1203             break;
1204 
1205         case mon_output_xml:
1206             fprintf(stream, "        </node>\n");
1207             break;
1208 
1209         default:
1210             break;
1211     }
1212 }
1213 
1214 /*!
1215  * \internal
1216  * \brief Print resources section heading appropriate to options
1217  *
1218  * \param[in] stream      File stream to display output to
1219  */
1220 static void
print_resources_heading(FILE * stream)1221 print_resources_heading(FILE *stream)
1222 {
1223     const char *heading;
1224 
1225     if (group_by_node) {
1226 
1227         /* Active resources have already been printed by node */
1228         heading = (inactive_resources? "Inactive resources" : NULL);
1229 
1230     } else if (inactive_resources) {
1231         heading = "Full list of resources";
1232 
1233     } else {
1234         heading = "Active resources";
1235     }
1236 
1237     /* Print section heading */
1238     switch (output_format) {
1239         case mon_output_plain:
1240         case mon_output_console:
1241             print_as("\n%s:\n\n", heading);
1242             break;
1243 
1244         case mon_output_html:
1245         case mon_output_cgi:
1246             fprintf(stream, " <hr />\n <h2>%s</h2>\n", heading);
1247             break;
1248 
1249         case mon_output_xml:
1250             fprintf(stream, "    <resources>\n");
1251             break;
1252 
1253         default:
1254             break;
1255     }
1256 
1257 }
1258 
1259 /*!
1260  * \internal
1261  * \brief Print whatever resource section closing is appropriate
1262  *
1263  * \param[in] stream     File stream to display output to
1264  */
1265 static void
print_resources_closing(FILE * stream,gboolean printed_heading)1266 print_resources_closing(FILE *stream, gboolean printed_heading)
1267 {
1268     const char *heading;
1269 
1270     /* What type of resources we did or did not display */
1271     if (group_by_node) {
1272         heading = "inactive ";
1273     } else if (inactive_resources) {
1274         heading = "";
1275     } else {
1276         heading = "active ";
1277     }
1278 
1279     switch (output_format) {
1280         case mon_output_plain:
1281         case mon_output_console:
1282             if (!printed_heading) {
1283                 print_as("\nNo %sresources\n\n", heading);
1284             }
1285             break;
1286 
1287         case mon_output_html:
1288         case mon_output_cgi:
1289             if (!printed_heading) {
1290                 fprintf(stream, " <hr />\n <h2>No %sresources</h2>\n", heading);
1291             }
1292             break;
1293 
1294         case mon_output_xml:
1295             fprintf(stream, "    %s\n",
1296                     (printed_heading? "</resources>" : "<resources/>"));
1297             break;
1298 
1299         default:
1300             break;
1301     }
1302 }
1303 
1304 /*!
1305  * \internal
1306  * \brief Print whatever resource section(s) are appropriate
1307  *
1308  * \param[in] stream     File stream to display output to
1309  * \param[in] data_set   Cluster state to display
1310  * \param[in] print_opts  Bitmask of pe_print_options
1311  */
1312 static void
print_resources(FILE * stream,pe_working_set_t * data_set,int print_opts)1313 print_resources(FILE *stream, pe_working_set_t *data_set, int print_opts)
1314 {
1315     GListPtr rsc_iter;
1316     const char *prefix = NULL;
1317     gboolean printed_heading = FALSE;
1318     gboolean brief_output = print_brief;
1319 
1320     /* If we already showed active resources by node, and
1321      * we're not showing inactive resources, we have nothing to do
1322      */
1323     if (group_by_node && !inactive_resources) {
1324         return;
1325     }
1326 
1327     /* XML uses an indent, and ignores brief option for resources */
1328     if (output_format == mon_output_xml) {
1329         prefix = "        ";
1330         brief_output = FALSE;
1331     }
1332 
1333     /* If we haven't already printed resources grouped by node,
1334      * and brief output was requested, print resource summary */
1335     if (brief_output && !group_by_node) {
1336         print_resources_heading(stream);
1337         printed_heading = TRUE;
1338         print_rscs_brief(data_set->resources, NULL, print_opts, stream,
1339                          inactive_resources);
1340     }
1341 
1342     /* For each resource, display it if appropriate */
1343     for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
1344         resource_t *rsc = (resource_t *) rsc_iter->data;
1345 
1346         /* Complex resources may have some sub-resources active and some inactive */
1347         gboolean is_active = rsc->fns->active(rsc, TRUE);
1348         gboolean partially_active = rsc->fns->active(rsc, FALSE);
1349 
1350         /* Skip inactive orphans (deleted but still in CIB) */
1351         if (is_set(rsc->flags, pe_rsc_orphan) && !is_active) {
1352             continue;
1353 
1354         /* Skip active resources if we already displayed them by node */
1355         } else if (group_by_node) {
1356             if (is_active) {
1357                 continue;
1358             }
1359 
1360         /* Skip primitives already counted in a brief summary */
1361         } else if (brief_output && (rsc->variant == pe_native)) {
1362             continue;
1363 
1364         /* Skip resources that aren't at least partially active,
1365          * unless we're displaying inactive resources
1366          */
1367         } else if (!partially_active && !inactive_resources) {
1368             continue;
1369         }
1370 
1371         /* Print this resource */
1372         if (printed_heading == FALSE) {
1373             print_resources_heading(stream);
1374             printed_heading = TRUE;
1375         }
1376         rsc->fns->print(rsc, prefix, print_opts, stream);
1377     }
1378 
1379     print_resources_closing(stream, printed_heading);
1380 }
1381 
1382 /*!
1383  * \internal
1384  * \brief Print heading for resource history
1385  *
1386  * \param[in] stream      File stream to display output to
1387  * \param[in] data_set    Current state of CIB
1388  * \param[in] node        Node that ran this resource
1389  * \param[in] rsc         Resource to print
1390  * \param[in] rsc_id      ID of resource to print
1391  * \param[in] all         Whether to print every resource or just failed ones
1392  */
1393 static void
print_rsc_history_start(FILE * stream,pe_working_set_t * data_set,node_t * node,resource_t * rsc,const char * rsc_id,gboolean all)1394 print_rsc_history_start(FILE *stream, pe_working_set_t *data_set, node_t *node,
1395                         resource_t *rsc, const char *rsc_id, gboolean all)
1396 {
1397     time_t last_failure = 0;
1398     int failcount = rsc?
1399                     pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
1400                                      NULL, data_set)
1401                     : 0;
1402 
1403     if (!all && !failcount && (last_failure <= 0)) {
1404         return;
1405     }
1406 
1407     /* Print resource ID */
1408     switch (output_format) {
1409         case mon_output_plain:
1410         case mon_output_console:
1411             print_as("   %s:", rsc_id);
1412             break;
1413 
1414         case mon_output_html:
1415         case mon_output_cgi:
1416             fprintf(stream, "   <li>%s:", rsc_id);
1417             break;
1418 
1419         case mon_output_xml:
1420             fprintf(stream, "            <resource_history id=\"%s\"", rsc_id);
1421             break;
1422 
1423         default:
1424             break;
1425     }
1426 
1427     /* If resource is an orphan, that's all we can say about it */
1428     if (rsc == NULL) {
1429         switch (output_format) {
1430             case mon_output_plain:
1431             case mon_output_console:
1432                 print_as(" orphan");
1433                 break;
1434 
1435             case mon_output_html:
1436             case mon_output_cgi:
1437                 fprintf(stream, " orphan");
1438                 break;
1439 
1440             case mon_output_xml:
1441                 fprintf(stream, " orphan=\"true\"");
1442                 break;
1443 
1444             default:
1445                 break;
1446         }
1447 
1448     /* If resource is not an orphan, print some details */
1449     } else if (all || failcount || (last_failure > 0)) {
1450 
1451         /* Print migration threshold */
1452         switch (output_format) {
1453             case mon_output_plain:
1454             case mon_output_console:
1455                 print_as(" migration-threshold=%d", rsc->migration_threshold);
1456                 break;
1457 
1458             case mon_output_html:
1459             case mon_output_cgi:
1460                 fprintf(stream, " migration-threshold=%d", rsc->migration_threshold);
1461                 break;
1462 
1463             case mon_output_xml:
1464                 fprintf(stream, " orphan=\"false\" migration-threshold=\"%d\"",
1465                         rsc->migration_threshold);
1466                 break;
1467 
1468             default:
1469                 break;
1470         }
1471 
1472         /* Print fail count if any */
1473         if (failcount > 0) {
1474             switch (output_format) {
1475                 case mon_output_plain:
1476                 case mon_output_console:
1477                     print_as(" " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
1478                     break;
1479 
1480                 case mon_output_html:
1481                 case mon_output_cgi:
1482                     fprintf(stream, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount);
1483                     break;
1484 
1485                 case mon_output_xml:
1486                     fprintf(stream, " " CRM_FAIL_COUNT_PREFIX "=\"%d\"",
1487                             failcount);
1488                     break;
1489 
1490                 default:
1491                     break;
1492             }
1493         }
1494 
1495         /* Print last failure time if any */
1496         if (last_failure > 0) {
1497             print_nvpair(stream, CRM_LAST_FAILURE_PREFIX, NULL, NULL,
1498                          last_failure);
1499         }
1500     }
1501 
1502     /* End the heading */
1503     switch (output_format) {
1504         case mon_output_plain:
1505         case mon_output_console:
1506             print_as("\n");
1507             break;
1508 
1509         case mon_output_html:
1510         case mon_output_cgi:
1511             fprintf(stream, "\n    <ul>\n");
1512             break;
1513 
1514         case mon_output_xml:
1515             fprintf(stream, ">\n");
1516             break;
1517 
1518         default:
1519             break;
1520     }
1521 }
1522 
1523 /*!
1524  * \internal
1525  * \brief Print closing for resource history
1526  *
1527  * \param[in] stream      File stream to display output to
1528  */
1529 static void
print_rsc_history_end(FILE * stream)1530 print_rsc_history_end(FILE *stream)
1531 {
1532     switch (output_format) {
1533         case mon_output_html:
1534         case mon_output_cgi:
1535             fprintf(stream, "    </ul>\n   </li>\n");
1536             break;
1537 
1538         case mon_output_xml:
1539             fprintf(stream, "            </resource_history>\n");
1540             break;
1541 
1542         default:
1543             break;
1544     }
1545 }
1546 
1547 /*!
1548  * \internal
1549  * \brief Print operation history
1550  *
1551  * \param[in] stream      File stream to display output to
1552  * \param[in] data_set    Current state of CIB
1553  * \param[in] node        Node this operation is for
1554  * \param[in] xml_op      Root of XML tree describing this operation
1555  * \param[in] task        Task parsed from this operation's XML
1556  * \param[in] interval    Interval parsed from this operation's XML
1557  * \param[in] rc          Return code parsed from this operation's XML
1558  */
1559 static void
print_op_history(FILE * stream,pe_working_set_t * data_set,node_t * node,xmlNode * xml_op,const char * task,const char * interval,int rc)1560 print_op_history(FILE *stream, pe_working_set_t *data_set, node_t *node,
1561                  xmlNode *xml_op, const char *task, const char *interval, int rc)
1562 {
1563     const char *value = NULL;
1564     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
1565 
1566     /* Begin the operation description */
1567     switch (output_format) {
1568         case mon_output_plain:
1569         case mon_output_console:
1570             print_as("    + (%s) %s:", call, task);
1571             break;
1572 
1573         case mon_output_html:
1574         case mon_output_cgi:
1575             fprintf(stream, "     <li>(%s) %s:", call, task);
1576             break;
1577 
1578         case mon_output_xml:
1579             fprintf(stream, "                <operation_history call=\"%s\" task=\"%s\"",
1580                     call, task);
1581             break;
1582 
1583         default:
1584             break;
1585     }
1586 
1587     /* Add name=value pairs as appropriate */
1588     if (safe_str_neq(interval, "0")) {
1589         print_nvpair(stream, "interval", interval, "ms", 0);
1590     }
1591     if (print_timing) {
1592         int int_value;
1593         const char *attr;
1594 
1595         attr = XML_RSC_OP_LAST_CHANGE;
1596         value = crm_element_value(xml_op, attr);
1597         if (value) {
1598             int_value = crm_parse_int(value, NULL);
1599             if (int_value > 0) {
1600                 print_nvpair(stream, attr, NULL, NULL, int_value);
1601             }
1602         }
1603 
1604         attr = XML_RSC_OP_LAST_RUN;
1605         value = crm_element_value(xml_op, attr);
1606         if (value) {
1607             int_value = crm_parse_int(value, NULL);
1608             if (int_value > 0) {
1609                 print_nvpair(stream, attr, NULL, NULL, int_value);
1610             }
1611         }
1612 
1613         attr = XML_RSC_OP_T_EXEC;
1614         value = crm_element_value(xml_op, attr);
1615         if (value) {
1616             print_nvpair(stream, attr, value, "ms", 0);
1617         }
1618 
1619         attr = XML_RSC_OP_T_QUEUE;
1620         value = crm_element_value(xml_op, attr);
1621         if (value) {
1622             print_nvpair(stream, attr, value, "ms", 0);
1623         }
1624     }
1625 
1626     /* End the operation description */
1627     switch (output_format) {
1628         case mon_output_plain:
1629         case mon_output_console:
1630             print_as(" rc=%d (%s)\n", rc, services_ocf_exitcode_str(rc));
1631             break;
1632 
1633         case mon_output_html:
1634         case mon_output_cgi:
1635             fprintf(stream, " rc=%d (%s)</li>\n", rc, services_ocf_exitcode_str(rc));
1636             break;
1637 
1638         case mon_output_xml:
1639             fprintf(stream, " rc=\"%d\" rc_text=\"%s\" />\n", rc, services_ocf_exitcode_str(rc));
1640             break;
1641 
1642         default:
1643             break;
1644     }
1645 }
1646 
1647 /*!
1648  * \internal
1649  * \brief Print resource operation/failure history
1650  *
1651  * \param[in] stream      File stream to display output to
1652  * \param[in] data_set    Current state of CIB
1653  * \param[in] node        Node that ran this resource
1654  * \param[in] rsc_entry   Root of XML tree describing resource status
1655  * \param[in] operations  Whether to print operations or just failcounts
1656  */
1657 static void
print_rsc_history(FILE * stream,pe_working_set_t * data_set,node_t * node,xmlNode * rsc_entry,gboolean operations)1658 print_rsc_history(FILE *stream, pe_working_set_t *data_set, node_t *node,
1659                   xmlNode *rsc_entry, gboolean operations)
1660 {
1661     GListPtr gIter = NULL;
1662     GListPtr op_list = NULL;
1663     gboolean printed = FALSE;
1664     const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
1665     resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
1666     xmlNode *rsc_op = NULL;
1667 
1668     /* If we're not showing operations, just print the resource failure summary */
1669     if (operations == FALSE) {
1670         print_rsc_history_start(stream, data_set, node, rsc, rsc_id, FALSE);
1671         print_rsc_history_end(stream);
1672         return;
1673     }
1674 
1675     /* Create a list of this resource's operations */
1676     for (rsc_op = __xml_first_child_element(rsc_entry); rsc_op != NULL;
1677          rsc_op = __xml_next_element(rsc_op)) {
1678         if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) {
1679             op_list = g_list_append(op_list, rsc_op);
1680         }
1681     }
1682     op_list = g_list_sort(op_list, sort_op_by_callid);
1683 
1684     /* Print each operation */
1685     for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
1686         xmlNode *xml_op = (xmlNode *) gIter->data;
1687         const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
1688         const char *interval = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL);
1689         const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
1690         int rc = crm_parse_int(op_rc, "0");
1691 
1692         /* Display 0-interval monitors as "probe" */
1693         if (safe_str_eq(task, CRMD_ACTION_STATUS) && safe_str_eq(interval, "0")) {
1694             task = "probe";
1695         }
1696 
1697         /* Ignore notifies and some probes */
1698         if (safe_str_eq(task, CRMD_ACTION_NOTIFY) || (safe_str_eq(task, "probe") && (rc == 7))) {
1699             continue;
1700         }
1701 
1702         /* If this is the first printed operation, print heading for resource */
1703         if (printed == FALSE) {
1704             printed = TRUE;
1705             print_rsc_history_start(stream, data_set, node, rsc, rsc_id, TRUE);
1706         }
1707 
1708         /* Print the operation */
1709         print_op_history(stream, data_set, node, xml_op, task, interval, rc);
1710     }
1711 
1712     /* Free the list we created (no need to free the individual items) */
1713     g_list_free(op_list);
1714 
1715     /* If we printed anything, close the resource */
1716     if (printed) {
1717         print_rsc_history_end(stream);
1718     }
1719 }
1720 
1721 /*!
1722  * \internal
1723  * \brief Print node operation/failure history
1724  *
1725  * \param[in] stream      File stream to display output to
1726  * \param[in] data_set    Current state of CIB
1727  * \param[in] node_state  Root of XML tree describing node status
1728  * \param[in] operations  Whether to print operations or just failcounts
1729  */
1730 static void
print_node_history(FILE * stream,pe_working_set_t * data_set,xmlNode * node_state,gboolean operations)1731 print_node_history(FILE *stream, pe_working_set_t *data_set,
1732                    xmlNode *node_state, gboolean operations)
1733 {
1734     node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
1735     xmlNode *lrm_rsc = NULL;
1736     xmlNode *rsc_entry = NULL;
1737 
1738     if (node && node->details && node->details->online) {
1739         print_node_start(stream, node);
1740 
1741         lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
1742         lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
1743 
1744         /* Print history of each of the node's resources */
1745         for (rsc_entry = __xml_first_child_element(lrm_rsc); rsc_entry != NULL;
1746              rsc_entry = __xml_next_element(rsc_entry)) {
1747 
1748             if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) {
1749                 print_rsc_history(stream, data_set, node, rsc_entry, operations);
1750             }
1751         }
1752 
1753         print_node_end(stream);
1754     }
1755 }
1756 
1757 /*!
1758  * \internal
1759  * \brief Print extended information about an attribute if appropriate
1760  *
1761  * \param[in] data_set  Working set of CIB state
1762  *
1763  * \return TRUE if extended information was printed, FALSE otherwise
1764  * \note Currently, extended information is only supported for ping/pingd
1765  *       resources, for which a message will be printed if connectivity is lost
1766  *       or degraded.
1767  */
1768 static gboolean
print_attr_msg(FILE * stream,node_t * node,GListPtr rsc_list,const char * attrname,const char * attrvalue)1769 print_attr_msg(FILE *stream, node_t * node, GListPtr rsc_list, const char *attrname, const char *attrvalue)
1770 {
1771     GListPtr gIter = NULL;
1772 
1773     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
1774         resource_t *rsc = (resource_t *) gIter->data;
1775         const char *type = g_hash_table_lookup(rsc->meta, "type");
1776 
1777         if (rsc->children != NULL) {
1778             if (print_attr_msg(stream, node, rsc->children, attrname, attrvalue)) {
1779                 return TRUE;
1780             }
1781         }
1782 
1783         if (safe_str_eq(type, "ping") || safe_str_eq(type, "pingd")) {
1784             const char *name = g_hash_table_lookup(rsc->parameters, "name");
1785 
1786             if (name == NULL) {
1787                 name = "pingd";
1788             }
1789 
1790             /* To identify the resource with the attribute name. */
1791             if (safe_str_eq(name, attrname)) {
1792                 int host_list_num = 0;
1793                 int expected_score = 0;
1794                 int value = crm_parse_int(attrvalue, "0");
1795                 const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list");
1796                 const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier");
1797 
1798                 if(hosts) {
1799                     char **host_list = g_strsplit(hosts, " ", 0);
1800                     host_list_num = g_strv_length(host_list);
1801                     g_strfreev(host_list);
1802                 }
1803 
1804                 /* pingd multiplier is the same as the default value. */
1805                 expected_score = host_list_num * crm_parse_int(multiplier, "1");
1806 
1807                 switch (output_format) {
1808                     case mon_output_plain:
1809                     case mon_output_console:
1810                         if (value <= 0) {
1811                             print_as("\t: Connectivity is lost");
1812                         } else if (value < expected_score) {
1813                             print_as("\t: Connectivity is degraded (Expected=%d)", expected_score);
1814                         }
1815                         break;
1816 
1817                     case mon_output_html:
1818                     case mon_output_cgi:
1819                         if (value <= 0) {
1820                             fprintf(stream, " <b>(connectivity is lost)</b>");
1821                         } else if (value < expected_score) {
1822                             fprintf(stream, " <b>(connectivity is degraded -- expected %d)</b>",
1823                                     expected_score);
1824                         }
1825                         break;
1826 
1827                     case mon_output_xml:
1828                         fprintf(stream, " expected=\"%d\"", expected_score);
1829                         break;
1830 
1831                     default:
1832                         break;
1833                 }
1834                 return TRUE;
1835             }
1836         }
1837     }
1838     return FALSE;
1839 }
1840 
1841 static int
compare_attribute(gconstpointer a,gconstpointer b)1842 compare_attribute(gconstpointer a, gconstpointer b)
1843 {
1844     int rc;
1845 
1846     rc = strcmp((const char *)a, (const char *)b);
1847 
1848     return rc;
1849 }
1850 
1851 static void
create_attr_list(gpointer name,gpointer value,gpointer data)1852 create_attr_list(gpointer name, gpointer value, gpointer data)
1853 {
1854     int i;
1855     const char *filt_str[] = FILTER_STR;
1856 
1857     CRM_CHECK(name != NULL, return);
1858 
1859     /* filtering automatic attributes */
1860     for (i = 0; filt_str[i] != NULL; i++) {
1861         if (g_str_has_prefix(name, filt_str[i])) {
1862             return;
1863         }
1864     }
1865 
1866     attr_list = g_list_insert_sorted(attr_list, name, compare_attribute);
1867 }
1868 
1869 /* structure for passing multiple user data to g_list_foreach() */
1870 struct mon_attr_data {
1871     FILE *stream;
1872     node_t *node;
1873 };
1874 
1875 static void
print_node_attribute(gpointer name,gpointer user_data)1876 print_node_attribute(gpointer name, gpointer user_data)
1877 {
1878     const char *value = NULL;
1879     struct mon_attr_data *data = (struct mon_attr_data *) user_data;
1880 
1881     value = pe_node_attribute_raw(data->node, name);
1882 
1883     /* Print attribute name and value */
1884     switch (output_format) {
1885         case mon_output_plain:
1886         case mon_output_console:
1887             print_as("    + %-32s\t: %-10s", (char *)name, value);
1888             break;
1889 
1890         case mon_output_html:
1891         case mon_output_cgi:
1892             fprintf(data->stream, "   <li>%s: %s",
1893                     (char *)name, value);
1894             break;
1895 
1896         case mon_output_xml:
1897             fprintf(data->stream,
1898                     "            <attribute name=\"%s\" value=\"%s\"",
1899                     (char *)name, value);
1900             break;
1901 
1902         default:
1903             break;
1904     }
1905 
1906     /* Print extended information if appropriate */
1907     print_attr_msg(data->stream, data->node, data->node->details->running_rsc,
1908                    name, value);
1909 
1910     /* Close out the attribute */
1911     switch (output_format) {
1912         case mon_output_plain:
1913         case mon_output_console:
1914             print_as("\n");
1915             break;
1916 
1917         case mon_output_html:
1918         case mon_output_cgi:
1919             fprintf(data->stream, "</li>\n");
1920             break;
1921 
1922         case mon_output_xml:
1923             fprintf(data->stream, " />\n");
1924             break;
1925 
1926         default:
1927             break;
1928     }
1929 }
1930 
1931 static void
print_node_summary(FILE * stream,pe_working_set_t * data_set,gboolean operations)1932 print_node_summary(FILE *stream, pe_working_set_t * data_set, gboolean operations)
1933 {
1934     xmlNode *node_state = NULL;
1935     xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input);
1936 
1937     /* Print heading */
1938     switch (output_format) {
1939         case mon_output_plain:
1940         case mon_output_console:
1941             if (operations) {
1942                 print_as("\nOperations:\n");
1943             } else {
1944                 print_as("\nMigration Summary:\n");
1945             }
1946             break;
1947 
1948         case mon_output_html:
1949         case mon_output_cgi:
1950             if (operations) {
1951                 fprintf(stream, " <hr />\n <h2>Operations</h2>\n");
1952             } else {
1953                 fprintf(stream, " <hr />\n <h2>Migration Summary</h2>\n");
1954             }
1955             break;
1956 
1957         case mon_output_xml:
1958             fprintf(stream, "    <node_history>\n");
1959             break;
1960 
1961         default:
1962             break;
1963     }
1964 
1965     /* Print each node in the CIB status */
1966     for (node_state = __xml_first_child_element(cib_status); node_state != NULL;
1967          node_state = __xml_next_element(node_state)) {
1968         if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) {
1969             print_node_history(stream, data_set, node_state, operations);
1970         }
1971     }
1972 
1973     /* Close section */
1974     switch (output_format) {
1975         case mon_output_xml:
1976             fprintf(stream, "    </node_history>\n");
1977             break;
1978 
1979         default:
1980             break;
1981     }
1982 }
1983 
1984 static void
print_ticket(gpointer name,gpointer value,gpointer data)1985 print_ticket(gpointer name, gpointer value, gpointer data)
1986 {
1987     ticket_t *ticket = (ticket_t *) value;
1988     FILE *stream = (FILE *) data;
1989 
1990     switch (output_format) {
1991         case mon_output_plain:
1992         case mon_output_console:
1993             print_as("* %s:\t%s%s", ticket->id,
1994                      (ticket->granted? "granted" : "revoked"),
1995                      (ticket->standby? " [standby]" : ""));
1996             break;
1997 
1998         case mon_output_html:
1999         case mon_output_cgi:
2000             fprintf(stream, "  <li>%s: %s%s", ticket->id,
2001                     (ticket->granted? "granted" : "revoked"),
2002                     (ticket->standby? " [standby]" : ""));
2003             break;
2004 
2005         case mon_output_xml:
2006             fprintf(stream, "        <ticket id=\"%s\" status=\"%s\" standby=\"%s\"",
2007                     ticket->id, (ticket->granted? "granted" : "revoked"),
2008                     (ticket->standby? "true" : "false"));
2009             break;
2010 
2011         default:
2012             break;
2013     }
2014     if (ticket->last_granted > -1) {
2015         print_nvpair(stdout, "last-granted", NULL, NULL, ticket->last_granted);
2016     }
2017     switch (output_format) {
2018         case mon_output_plain:
2019         case mon_output_console:
2020             print_as("\n");
2021             break;
2022 
2023         case mon_output_html:
2024         case mon_output_cgi:
2025             fprintf(stream, "</li>\n");
2026             break;
2027 
2028         case mon_output_xml:
2029             fprintf(stream, " />\n");
2030             break;
2031 
2032         default:
2033             break;
2034     }
2035 }
2036 
2037 static void
print_cluster_tickets(FILE * stream,pe_working_set_t * data_set)2038 print_cluster_tickets(FILE *stream, pe_working_set_t * data_set)
2039 {
2040     /* Print section heading */
2041     switch (output_format) {
2042         case mon_output_plain:
2043         case mon_output_console:
2044             print_as("\nTickets:\n");
2045             break;
2046 
2047         case mon_output_html:
2048         case mon_output_cgi:
2049             fprintf(stream, " <hr />\n <h2>Tickets</h2>\n <ul>\n");
2050             break;
2051 
2052         case mon_output_xml:
2053             fprintf(stream, "    <tickets>\n");
2054             break;
2055 
2056         default:
2057             break;
2058     }
2059 
2060     /* Print each ticket */
2061     g_hash_table_foreach(data_set->tickets, print_ticket, stream);
2062 
2063     /* Close section */
2064     switch (output_format) {
2065         case mon_output_html:
2066         case mon_output_cgi:
2067             fprintf(stream, " </ul>\n");
2068             break;
2069 
2070         case mon_output_xml:
2071             fprintf(stream, "    </tickets>\n");
2072             break;
2073 
2074         default:
2075             break;
2076     }
2077 }
2078 
2079 /*!
2080  * \internal
2081  * \brief Return human-friendly string representing node name
2082  *
2083  * The returned string will be in the format
2084  *    uname[@hostUname] [(nodeID)]
2085  * "@hostUname" will be printed if the node is a guest node.
2086  * "(nodeID)" will be printed if the node ID is different from the node uname,
2087  *  and detailed output has been requested.
2088  *
2089  * \param[in] node  Node to represent
2090  * \return Newly allocated string with representation of node name
2091  * \note It is the caller's responsibility to free the result with free().
2092  */
2093 static char *
get_node_display_name(node_t * node)2094 get_node_display_name(node_t *node)
2095 {
2096     char *node_name;
2097     const char *node_host = NULL;
2098     const char *node_id = NULL;
2099     int name_len;
2100 
2101     CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
2102 
2103     /* Host is displayed only if this is a guest node */
2104     if (is_container_remote_node(node)) {
2105         node_t *host_node = pe__current_node(node->details->remote_rsc);
2106 
2107         if (host_node && host_node->details) {
2108             node_host = host_node->details->uname;
2109         }
2110         if (node_host == NULL) {
2111             node_host = ""; /* so we at least get "uname@" to indicate guest */
2112         }
2113     }
2114 
2115     /* Node ID is displayed if different from uname and detail is requested */
2116     if (print_clone_detail && safe_str_neq(node->details->uname, node->details->id)) {
2117         node_id = node->details->id;
2118     }
2119 
2120     /* Determine name length */
2121     name_len = strlen(node->details->uname) + 1;
2122     if (node_host) {
2123         name_len += strlen(node_host) + 1; /* "@node_host" */
2124     }
2125     if (node_id) {
2126         name_len += strlen(node_id) + 3; /* + " (node_id)" */
2127     }
2128 
2129     /* Allocate and populate display name */
2130     node_name = malloc(name_len);
2131     CRM_ASSERT(node_name != NULL);
2132     strcpy(node_name, node->details->uname);
2133     if (node_host) {
2134         strcat(node_name, "@");
2135         strcat(node_name, node_host);
2136     }
2137     if (node_id) {
2138         strcat(node_name, " (");
2139         strcat(node_name, node_id);
2140         strcat(node_name, ")");
2141     }
2142     return node_name;
2143 }
2144 
2145 /*!
2146  * \internal
2147  * \brief Print a negative location constraint
2148  *
2149  * \param[in] stream     File stream to display output to
2150  * \param[in] node       Node affected by constraint
2151  * \param[in] location   Constraint to print
2152  */
2153 static void
print_ban(FILE * stream,pe_node_t * node,pe__location_t * location)2154 print_ban(FILE *stream, pe_node_t *node, pe__location_t *location)
2155 {
2156     char *node_name = NULL;
2157 
2158     switch (output_format) {
2159         case mon_output_plain:
2160         case mon_output_console:
2161             node_name = get_node_display_name(node);
2162             print_as(" %s\tprevents %s from running %son %s\n",
2163                      location->id, location->rsc_lh->id,
2164                      ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
2165                      node_name);
2166             break;
2167 
2168         case mon_output_html:
2169         case mon_output_cgi:
2170             node_name = get_node_display_name(node);
2171             fprintf(stream, "  <li>%s prevents %s from running %son %s</li>\n",
2172                      location->id, location->rsc_lh->id,
2173                      ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""),
2174                      node_name);
2175             break;
2176 
2177         case mon_output_xml:
2178             fprintf(stream,
2179                     "        <ban id=\"%s\" resource=\"%s\" node=\"%s\" weight=\"%d\" master_only=\"%s\" />\n",
2180                     location->id, location->rsc_lh->id, node->details->uname, node->weight,
2181                     ((location->role_filter == RSC_ROLE_MASTER)? "true" : "false"));
2182             break;
2183 
2184         default:
2185             break;
2186     }
2187     free(node_name);
2188 }
2189 
2190 /*!
2191  * \internal
2192  * \brief Print section for negative location constraints
2193  *
2194  * \param[in] stream     File stream to display output to
2195  * \param[in] data_set   Working set corresponding to CIB status to display
2196  */
print_neg_locations(FILE * stream,pe_working_set_t * data_set)2197 static void print_neg_locations(FILE *stream, pe_working_set_t *data_set)
2198 {
2199     GListPtr gIter, gIter2;
2200 
2201     /* Print section heading */
2202     switch (output_format) {
2203         case mon_output_plain:
2204         case mon_output_console:
2205             print_as("\nNegative Location Constraints:\n");
2206             break;
2207 
2208         case mon_output_html:
2209         case mon_output_cgi:
2210             fprintf(stream, " <hr />\n <h2>Negative Location Constraints</h2>\n <ul>\n");
2211             break;
2212 
2213         case mon_output_xml:
2214             fprintf(stream, "    <bans>\n");
2215             break;
2216 
2217         default:
2218             break;
2219     }
2220 
2221     /* Print each ban */
2222     for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
2223         pe__location_t *location = gIter->data;
2224         if (!g_str_has_prefix(location->id, print_neg_location_prefix))
2225             continue;
2226         for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
2227             node_t *node = (node_t *) gIter2->data;
2228 
2229             if (node->weight < 0) {
2230                 print_ban(stream, node, location);
2231             }
2232         }
2233     }
2234 
2235     /* Close section */
2236     switch (output_format) {
2237         case mon_output_cgi:
2238         case mon_output_html:
2239             fprintf(stream, " </ul>\n");
2240             break;
2241 
2242         case mon_output_xml:
2243             fprintf(stream, "    </bans>\n");
2244             break;
2245 
2246         default:
2247             break;
2248     }
2249 }
2250 
2251 static void
crm_mon_get_parameters(resource_t * rsc,pe_working_set_t * data_set)2252 crm_mon_get_parameters(resource_t *rsc, pe_working_set_t * data_set)
2253 {
2254     get_rsc_attributes(rsc->parameters, rsc, NULL, data_set);
2255     crm_trace("Beekhof: unpacked params for %s (%d)", rsc->id, g_hash_table_size(rsc->parameters));
2256     if(rsc->children) {
2257         GListPtr gIter = NULL;
2258 
2259         for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
2260             crm_mon_get_parameters(gIter->data, data_set);
2261         }
2262     }
2263 }
2264 
2265 /*!
2266  * \internal
2267  * \brief Print node attributes section
2268  *
2269  * \param[in] stream     File stream to display output to
2270  * \param[in] data_set   Working set of CIB state
2271  */
2272 static void
print_node_attributes(FILE * stream,pe_working_set_t * data_set)2273 print_node_attributes(FILE *stream, pe_working_set_t *data_set)
2274 {
2275     GListPtr gIter = NULL;
2276 
2277     /* Print section heading */
2278     switch (output_format) {
2279         case mon_output_plain:
2280         case mon_output_console:
2281             print_as("\nNode Attributes:\n");
2282             break;
2283 
2284         case mon_output_html:
2285         case mon_output_cgi:
2286             fprintf(stream, " <hr />\n <h2>Node Attributes</h2>\n");
2287             break;
2288 
2289         case mon_output_xml:
2290             fprintf(stream, "    <node_attributes>\n");
2291             break;
2292 
2293         default:
2294             break;
2295     }
2296 
2297     /* Unpack all resource parameters (it would be more efficient to do this
2298      * only when needed for the first time in print_attr_msg())
2299      */
2300     for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
2301         crm_mon_get_parameters(gIter->data, data_set);
2302     }
2303 
2304     /* Display each node's attributes */
2305     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
2306         struct mon_attr_data data;
2307 
2308         data.stream = stream;
2309         data.node = (node_t *) gIter->data;
2310 
2311         if (data.node && data.node->details && data.node->details->online) {
2312             print_node_start(stream, data.node);
2313             g_hash_table_foreach(data.node->details->attrs, create_attr_list, NULL);
2314             g_list_foreach(attr_list, print_node_attribute, &data);
2315             g_list_free(attr_list);
2316             attr_list = NULL;
2317             print_node_end(stream);
2318         }
2319     }
2320 
2321     /* Print section footer */
2322     switch (output_format) {
2323         case mon_output_xml:
2324             fprintf(stream, "    </node_attributes>\n");
2325             break;
2326 
2327         default:
2328             break;
2329     }
2330 }
2331 
2332 /*!
2333  * \internal
2334  * \brief Return resource display options corresponding to command-line choices
2335  *
2336  * \return Bitmask of pe_print_options suitable for resource print functions
2337  */
2338 static int
get_resource_display_options(void)2339 get_resource_display_options(void)
2340 {
2341     int print_opts;
2342 
2343     /* Determine basic output format */
2344     switch (output_format) {
2345         case mon_output_console:
2346             print_opts = pe_print_ncurses;
2347             break;
2348         case mon_output_html:
2349         case mon_output_cgi:
2350             print_opts = pe_print_html;
2351             break;
2352         case mon_output_xml:
2353             print_opts = pe_print_xml;
2354             break;
2355         default:
2356             print_opts = pe_print_printf;
2357             break;
2358     }
2359 
2360     /* Add optional display elements */
2361     if (print_pending) {
2362         print_opts |= pe_print_pending;
2363     }
2364     if (print_clone_detail) {
2365         print_opts |= pe_print_clone_details|pe_print_implicit;
2366     }
2367     if (!inactive_resources) {
2368         print_opts |= pe_print_clone_active;
2369     }
2370     if (print_brief) {
2371         print_opts |= pe_print_brief;
2372     }
2373     return print_opts;
2374 }
2375 
2376 /*!
2377  * \internal
2378  * \brief Return human-friendly string representing current time
2379  *
2380  * \return Current time as string (as by ctime() but without newline) on success
2381  *         or "Could not determine current time" on error
2382  * \note The return value points to a statically allocated string which might be
2383  *       overwritten by subsequent calls to any of the C library date and time functions.
2384  */
2385 static const char *
crm_now_string(void)2386 crm_now_string(void)
2387 {
2388     time_t a_time = time(NULL);
2389     char *since_epoch = ctime(&a_time);
2390 
2391     if ((a_time == (time_t) -1) || (since_epoch == NULL)) {
2392         return "Could not determine current time";
2393     }
2394     since_epoch[strlen(since_epoch) - 1] = EOS; /* trim newline */
2395     return (since_epoch);
2396 }
2397 
2398 /*!
2399  * \internal
2400  * \brief Print header for cluster summary if needed
2401  *
2402  * \param[in] stream     File stream to display output to
2403  */
2404 static void
print_cluster_summary_header(FILE * stream)2405 print_cluster_summary_header(FILE *stream)
2406 {
2407     switch (output_format) {
2408         case mon_output_html:
2409         case mon_output_cgi:
2410             fprintf(stream, " <h2>Cluster Summary</h2>\n <p>\n");
2411             break;
2412 
2413         case mon_output_xml:
2414             fprintf(stream, "    <summary>\n");
2415             break;
2416 
2417         default:
2418             break;
2419     }
2420 }
2421 
2422 /*!
2423  * \internal
2424  * \brief Print footer for cluster summary if needed
2425  *
2426  * \param[in] stream     File stream to display output to
2427  */
2428 static void
print_cluster_summary_footer(FILE * stream)2429 print_cluster_summary_footer(FILE *stream)
2430 {
2431     switch (output_format) {
2432         case mon_output_cgi:
2433         case mon_output_html:
2434             fprintf(stream, " </p>\n");
2435             break;
2436 
2437         case mon_output_xml:
2438             fprintf(stream, "    </summary>\n");
2439             break;
2440 
2441         default:
2442             break;
2443     }
2444 }
2445 
2446 /*!
2447  * \internal
2448  * \brief Print times the display was last updated and CIB last changed
2449  *
2450  * \param[in] stream     File stream to display output to
2451  * \param[in] data_set   Working set of CIB state
2452  */
2453 static void
print_cluster_times(FILE * stream,pe_working_set_t * data_set)2454 print_cluster_times(FILE *stream, pe_working_set_t *data_set)
2455 {
2456     const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
2457     const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
2458     const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
2459     const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
2460 
2461     switch (output_format) {
2462         case mon_output_plain:
2463         case mon_output_console:
2464             print_as("Last updated: %s", crm_now_string());
2465             print_as((user || client || origin)? "\n" : "\t\t");
2466             print_as("Last change: %s", last_written ? last_written : "");
2467             if (user) {
2468                 print_as(" by %s", user);
2469             }
2470             if (client) {
2471                 print_as(" via %s", client);
2472             }
2473             if (origin) {
2474                 print_as(" on %s", origin);
2475             }
2476             print_as("\n");
2477             break;
2478 
2479         case mon_output_html:
2480         case mon_output_cgi:
2481             fprintf(stream, " <b>Last updated:</b> %s<br/>\n", crm_now_string());
2482             fprintf(stream, " <b>Last change:</b> %s", last_written ? last_written : "");
2483             if (user) {
2484                 fprintf(stream, " by %s", user);
2485             }
2486             if (client) {
2487                 fprintf(stream, " via %s", client);
2488             }
2489             if (origin) {
2490                 fprintf(stream, " on %s", origin);
2491             }
2492             fprintf(stream, "<br/>\n");
2493             break;
2494 
2495         case mon_output_xml:
2496             fprintf(stream, "        <last_update time=\"%s\" />\n", crm_now_string());
2497             fprintf(stream, "        <last_change time=\"%s\" user=\"%s\" client=\"%s\" origin=\"%s\" />\n",
2498                     last_written ? last_written : "", user ? user : "",
2499                     client ? client : "", origin ? origin : "");
2500             break;
2501 
2502         default:
2503             break;
2504     }
2505 }
2506 
2507 /*!
2508  * \internal
2509  * \brief Print cluster stack
2510  *
2511  * \param[in] stream     File stream to display output to
2512  * \param[in] stack_s    Stack name
2513  */
2514 static void
print_cluster_stack(FILE * stream,const char * stack_s)2515 print_cluster_stack(FILE *stream, const char *stack_s)
2516 {
2517     switch (output_format) {
2518         case mon_output_plain:
2519         case mon_output_console:
2520             print_as("Stack: %s\n", stack_s);
2521             break;
2522 
2523         case mon_output_html:
2524         case mon_output_cgi:
2525             fprintf(stream, " <b>Stack:</b> %s<br/>\n", stack_s);
2526             break;
2527 
2528         case mon_output_xml:
2529             fprintf(stream, "        <stack type=\"%s\" />\n", stack_s);
2530             break;
2531 
2532         default:
2533             break;
2534     }
2535 }
2536 
2537 /*!
2538  * \internal
2539  * \brief Print current DC and its version
2540  *
2541  * \param[in] stream     File stream to display output to
2542  * \param[in] data_set   Working set of CIB state
2543  */
2544 static void
print_cluster_dc(FILE * stream,pe_working_set_t * data_set)2545 print_cluster_dc(FILE *stream, pe_working_set_t *data_set)
2546 {
2547     node_t *dc = data_set->dc_node;
2548     xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
2549                                            data_set->input, LOG_DEBUG);
2550     const char *dc_version_s = dc_version?
2551                                crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
2552                                : NULL;
2553     const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
2554     char *dc_name = dc? get_node_display_name(dc) : NULL;
2555 
2556     switch (output_format) {
2557         case mon_output_plain:
2558         case mon_output_console:
2559             print_as("Current DC: ");
2560             if (dc) {
2561                 print_as("%s (version %s) - partition %s quorum\n",
2562                          dc_name, (dc_version_s? dc_version_s : "unknown"),
2563                          (crm_is_true(quorum) ? "with" : "WITHOUT"));
2564             } else {
2565                 print_as("NONE\n");
2566             }
2567             break;
2568 
2569         case mon_output_html:
2570         case mon_output_cgi:
2571             fprintf(stream, " <b>Current DC:</b> ");
2572             if (dc) {
2573                 fprintf(stream, "%s (version %s) - partition %s quorum",
2574                         dc_name, (dc_version_s? dc_version_s : "unknown"),
2575                         (crm_is_true(quorum)? "with" : "<font color=\"red\"><b>WITHOUT</b></font>"));
2576             } else {
2577                 fprintf(stream, "<font color=\"red\"><b>NONE</b></font>");
2578             }
2579             fprintf(stream, "<br/>\n");
2580             break;
2581 
2582         case mon_output_xml:
2583             fprintf(stream,  "        <current_dc ");
2584             if (dc) {
2585                 fprintf(stream,
2586                         "present=\"true\" version=\"%s\" name=\"%s\" id=\"%s\" with_quorum=\"%s\"",
2587                         (dc_version_s? dc_version_s : ""), dc->details->uname, dc->details->id,
2588                         (crm_is_true(quorum) ? "true" : "false"));
2589             } else {
2590                 fprintf(stream, "present=\"false\"");
2591             }
2592             fprintf(stream, " />\n");
2593             break;
2594 
2595         default:
2596             break;
2597     }
2598     free(dc_name);
2599 }
2600 
2601 /*!
2602  * \internal
2603  * \brief Print counts of configured nodes and resources
2604  *
2605  * \param[in] stream     File stream to display output to
2606  * \param[in] data_set   Working set of CIB state
2607  * \param[in] stack_s    Stack name
2608  */
2609 static void
print_cluster_counts(FILE * stream,pe_working_set_t * data_set,const char * stack_s)2610 print_cluster_counts(FILE *stream, pe_working_set_t *data_set, const char *stack_s)
2611 {
2612     int nnodes = g_list_length(data_set->nodes);
2613     xmlNode *quorum_node = get_xpath_object("//nvpair[@name='" XML_ATTR_EXPECTED_VOTES "']",
2614                                             data_set->input, LOG_DEBUG);
2615     const char *quorum_votes = quorum_node?
2616                                crm_element_value(quorum_node, XML_NVPAIR_ATTR_VALUE)
2617                                : "unknown";
2618 
2619     switch (output_format) {
2620         case mon_output_plain:
2621         case mon_output_console:
2622 
2623             print_as("\n%d node%s configured", nnodes, s_if_plural(nnodes));
2624             if (stack_s && strstr(stack_s, "classic openais") != NULL) {
2625                 print_as(" (%s expected votes)", quorum_votes);
2626             }
2627             print_as("\n");
2628 
2629             print_as("%d resource instance%s configured",
2630                      data_set->ninstances, s_if_plural(data_set->ninstances));
2631             if(data_set->disabled_resources || data_set->blocked_resources) {
2632                 print_as(" (");
2633                 if (data_set->disabled_resources) {
2634                     print_as("%d DISABLED", data_set->disabled_resources);
2635                 }
2636                 if (data_set->disabled_resources && data_set->blocked_resources) {
2637                     print_as(", ");
2638                 }
2639                 if (data_set->blocked_resources) {
2640                     print_as("%d BLOCKED from further action due to failure",
2641                              data_set->blocked_resources);
2642                 }
2643                 print_as(")");
2644             }
2645             print_as("\n");
2646 
2647             break;
2648 
2649         case mon_output_html:
2650         case mon_output_cgi:
2651 
2652             fprintf(stream, " %d node%s configured", nnodes, s_if_plural(nnodes));
2653             if (stack_s && strstr(stack_s, "classic openais") != NULL) {
2654                 fprintf(stream, " (%s expected votes)", quorum_votes);
2655             }
2656             fprintf(stream, "<br/>\n");
2657 
2658             fprintf(stream, " %d resource instance%s configured",
2659                     data_set->ninstances, s_if_plural(data_set->ninstances));
2660             if (data_set->disabled_resources || data_set->blocked_resources) {
2661                 fprintf(stream, " (");
2662                 if (data_set->disabled_resources) {
2663                     fprintf(stream, "%d <strong>DISABLED</strong>",
2664                             data_set->disabled_resources);
2665                 }
2666                 if (data_set->disabled_resources && data_set->blocked_resources) {
2667                     fprintf(stream, ", ");
2668                 }
2669                 if (data_set->blocked_resources) {
2670                     fprintf(stream,
2671                             "%d <strong>BLOCKED</strong> from further action due to failure",
2672                             data_set->blocked_resources);
2673                 }
2674                 fprintf(stream, ")");
2675             }
2676             fprintf(stream, "<br/>\n");
2677             break;
2678 
2679         case mon_output_xml:
2680             fprintf(stream,
2681                     "        <nodes_configured number=\"%d\" expected_votes=\"%s\" />\n",
2682                     g_list_length(data_set->nodes), quorum_votes);
2683             fprintf(stream,
2684                     "        <resources_configured number=\"%d\" disabled=\"%d\" blocked=\"%d\" />\n",
2685                     data_set->ninstances,
2686                     data_set->disabled_resources, data_set->blocked_resources);
2687             break;
2688 
2689         default:
2690             break;
2691     }
2692 }
2693 
2694 /*!
2695  * \internal
2696  * \brief Print cluster-wide options
2697  *
2698  * \param[in] stream     File stream to display output to
2699  * \param[in] data_set   Working set of CIB state
2700  *
2701  * \note Currently this is only implemented for HTML and XML output, and
2702  *       prints only a few options. If there is demand, more could be added.
2703  */
2704 static void
print_cluster_options(FILE * stream,pe_working_set_t * data_set)2705 print_cluster_options(FILE *stream, pe_working_set_t *data_set)
2706 {
2707     switch (output_format) {
2708         case mon_output_plain:
2709         case mon_output_console:
2710             if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
2711                 print_as("\n              *** Resource management is DISABLED ***");
2712                 print_as("\n  The cluster will not attempt to start, stop or recover services");
2713                 print_as("\n");
2714             }
2715             break;
2716 
2717         case mon_output_html:
2718             fprintf(stream, " </p>\n <h3>Config Options</h3>\n");
2719             fprintf(stream, " <table>\n");
2720             fprintf(stream, "  <tr><th>STONITH of failed nodes</th><td>%s</td></tr>\n",
2721                     is_set(data_set->flags, pe_flag_stonith_enabled)? "enabled" : "disabled");
2722 
2723             fprintf(stream, "  <tr><th>Cluster is</th><td>%ssymmetric</td></tr>\n",
2724                     is_set(data_set->flags, pe_flag_symmetric_cluster)? "" : "a");
2725 
2726             fprintf(stream, "  <tr><th>No Quorum Policy</th><td>");
2727             switch (data_set->no_quorum_policy) {
2728                 case no_quorum_freeze:
2729                     fprintf(stream, "Freeze resources");
2730                     break;
2731                 case no_quorum_stop:
2732                     fprintf(stream, "Stop ALL resources");
2733                     break;
2734                 case no_quorum_ignore:
2735                     fprintf(stream, "Ignore");
2736                     break;
2737                 case no_quorum_suicide:
2738                     fprintf(stream, "Suicide");
2739                     break;
2740             }
2741             fprintf(stream, "</td></tr>\n");
2742 
2743             fprintf(stream, "  <tr><th>Resource management</th><td>");
2744             if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
2745                 fprintf(stream, "<strong>DISABLED</strong> (the cluster will "
2746                                 "not attempt to start, stop or recover services)");
2747             } else {
2748                 fprintf(stream, "enabled");
2749             }
2750             fprintf(stream, "</td></tr>\n");
2751 
2752             fprintf(stream, "</table>\n <p>\n");
2753             break;
2754 
2755         case mon_output_xml:
2756             fprintf(stream, "        <cluster_options");
2757             fprintf(stream, " stonith-enabled=\"%s\"",
2758                     is_set(data_set->flags, pe_flag_stonith_enabled)?
2759                     "true" : "false");
2760             fprintf(stream, " symmetric-cluster=\"%s\"",
2761                     is_set(data_set->flags, pe_flag_symmetric_cluster)?
2762                     "true" : "false");
2763             fprintf(stream, " no-quorum-policy=\"");
2764             switch (data_set->no_quorum_policy) {
2765                 case no_quorum_freeze:
2766                     fprintf(stream, "freeze");
2767                     break;
2768                 case no_quorum_stop:
2769                     fprintf(stream, "stop");
2770                     break;
2771                 case no_quorum_ignore:
2772                     fprintf(stream, "ignore");
2773                     break;
2774                 case no_quorum_suicide:
2775                     fprintf(stream, "suicide");
2776                     break;
2777             }
2778             fprintf(stream, "\"");
2779             fprintf(stream, " maintenance-mode=\"%s\"",
2780                     is_set(data_set->flags, pe_flag_maintenance_mode)?
2781                     "true" : "false");
2782             fprintf(stream, " />\n");
2783             break;
2784 
2785         default:
2786             break;
2787     }
2788 }
2789 
2790 /*!
2791  * \internal
2792  * \brief Get the name of the stack in use (or "unknown" if not available)
2793  *
2794  * \param[in] data_set   Working set of CIB state
2795  *
2796  * \return String representing stack name
2797  */
2798 static const char *
get_cluster_stack(pe_working_set_t * data_set)2799 get_cluster_stack(pe_working_set_t *data_set)
2800 {
2801     xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
2802                                       data_set->input, LOG_DEBUG);
2803     return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
2804 }
2805 
2806 /*!
2807  * \internal
2808  * \brief Print a summary of cluster-wide information
2809  *
2810  * \param[in] stream     File stream to display output to
2811  * \param[in] data_set   Working set of CIB state
2812  */
2813 static void
print_cluster_summary(FILE * stream,pe_working_set_t * data_set)2814 print_cluster_summary(FILE *stream, pe_working_set_t *data_set)
2815 {
2816     const char *stack_s = get_cluster_stack(data_set);
2817     gboolean header_printed = FALSE;
2818 
2819     if (show & mon_show_stack) {
2820         if (header_printed == FALSE) {
2821             print_cluster_summary_header(stream);
2822             header_printed = TRUE;
2823         }
2824         print_cluster_stack(stream, stack_s);
2825     }
2826 
2827     /* Always print DC if none, even if not requested */
2828     if ((data_set->dc_node == NULL) || (show & mon_show_dc)) {
2829         if (header_printed == FALSE) {
2830             print_cluster_summary_header(stream);
2831             header_printed = TRUE;
2832         }
2833         print_cluster_dc(stream, data_set);
2834     }
2835 
2836     if (show & mon_show_times) {
2837         if (header_printed == FALSE) {
2838             print_cluster_summary_header(stream);
2839             header_printed = TRUE;
2840         }
2841         print_cluster_times(stream, data_set);
2842     }
2843 
2844     if (is_set(data_set->flags, pe_flag_maintenance_mode)
2845         || data_set->disabled_resources
2846         || data_set->blocked_resources
2847         || is_set(show, mon_show_count)) {
2848         if (header_printed == FALSE) {
2849             print_cluster_summary_header(stream);
2850             header_printed = TRUE;
2851         }
2852         print_cluster_counts(stream, data_set, stack_s);
2853     }
2854 
2855     /* There is not a separate option for showing cluster options, so show with
2856      * stack for now; a separate option could be added if there is demand
2857      */
2858     if (show & mon_show_stack) {
2859         print_cluster_options(stream, data_set);
2860     }
2861 
2862     if (header_printed) {
2863         print_cluster_summary_footer(stream);
2864     }
2865 }
2866 
2867 /*!
2868  * \internal
2869  * \brief Print a failed action
2870  *
2871  * \param[in] stream     File stream to display output to
2872  * \param[in] xml_op     Root of XML tree describing failed action
2873  */
2874 static void
print_failed_action(FILE * stream,xmlNode * xml_op)2875 print_failed_action(FILE *stream, xmlNode *xml_op)
2876 {
2877     const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY);
2878     const char *op_key_attr = "op_key";
2879     const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE);
2880     const char *node = crm_element_value(xml_op, XML_ATTR_UNAME);
2881     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
2882     const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
2883     int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0");
2884     int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0");
2885     char *exit_reason_cleaned;
2886 
2887     /* If no op_key was given, use id instead */
2888     if (op_key == NULL) {
2889         op_key = ID(xml_op);
2890         op_key_attr = "id";
2891     }
2892 
2893     /* If no exit reason was given, use "none" */
2894     if (exit_reason == NULL) {
2895         exit_reason = "none";
2896     }
2897 
2898     /* Print common action information */
2899     switch (output_format) {
2900         case mon_output_plain:
2901         case mon_output_console:
2902             print_as("* %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
2903                      op_key, node, services_ocf_exitcode_str(rc), rc,
2904                      call, services_lrm_status_str(status), exit_reason);
2905             break;
2906 
2907         case mon_output_html:
2908         case mon_output_cgi:
2909             fprintf(stream, "  <li>%s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'",
2910                      op_key, node, services_ocf_exitcode_str(rc), rc,
2911                      call, services_lrm_status_str(status), exit_reason);
2912             break;
2913 
2914         case mon_output_xml:
2915             exit_reason_cleaned = crm_xml_escape(exit_reason);
2916             fprintf(stream, "        <failure %s=\"%s\" node=\"%s\"",
2917                     op_key_attr, op_key, node);
2918             fprintf(stream, " exitstatus=\"%s\" exitreason=\"%s\" exitcode=\"%d\"",
2919                     services_ocf_exitcode_str(rc), exit_reason_cleaned, rc);
2920             fprintf(stream, " call=\"%s\" status=\"%s\"",
2921                     call, services_lrm_status_str(status));
2922             free(exit_reason_cleaned);
2923             break;
2924 
2925         default:
2926             break;
2927     }
2928 
2929     /* If last change was given, print timing information as well */
2930     if (last) {
2931         time_t run_at = crm_parse_int(last, "0");
2932         char *run_at_s = ctime(&run_at);
2933 
2934         if (run_at_s) {
2935             run_at_s[24] = 0; /* Overwrite the newline */
2936         }
2937 
2938         switch (output_format) {
2939             case mon_output_plain:
2940             case mon_output_console:
2941                 print_as(",\n    last-rc-change='%s', queued=%sms, exec=%sms",
2942                          run_at_s? run_at_s : "",
2943                          crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2944                          crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
2945                 break;
2946 
2947             case mon_output_html:
2948             case mon_output_cgi:
2949                 fprintf(stream, " last-rc-change='%s', queued=%sms, exec=%sms",
2950                         run_at_s? run_at_s : "",
2951                         crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2952                         crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
2953                 break;
2954 
2955             case mon_output_xml:
2956                 fprintf(stream,
2957                         " last-rc-change=\"%s\" queued=\"%s\" exec=\"%s\" interval=\"%d\" task=\"%s\"",
2958                         run_at_s? run_at_s : "",
2959                         crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
2960                         crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
2961                         crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL), "0"),
2962                         crm_element_value(xml_op, XML_LRM_ATTR_TASK));
2963                 break;
2964 
2965             default:
2966                 break;
2967         }
2968     }
2969 
2970     /* End the action listing */
2971     switch (output_format) {
2972         case mon_output_plain:
2973         case mon_output_console:
2974             print_as("\n");
2975             break;
2976 
2977         case mon_output_html:
2978         case mon_output_cgi:
2979             fprintf(stream, "</li>\n");
2980             break;
2981 
2982         case mon_output_xml:
2983             fprintf(stream, " />\n");
2984             break;
2985 
2986         default:
2987             break;
2988     }
2989 }
2990 
2991 /*!
2992  * \internal
2993  * \brief Print a section for failed actions
2994  *
2995  * \param[in] stream     File stream to display output to
2996  * \param[in] data_set   Working set of CIB state
2997  */
2998 static void
print_failed_actions(FILE * stream,pe_working_set_t * data_set)2999 print_failed_actions(FILE *stream, pe_working_set_t *data_set)
3000 {
3001     xmlNode *xml_op = NULL;
3002 
3003     /* Print section heading */
3004     switch (output_format) {
3005         case mon_output_plain:
3006         case mon_output_console:
3007             print_as("\nFailed Resource Actions:\n");
3008             break;
3009 
3010         case mon_output_html:
3011         case mon_output_cgi:
3012             fprintf(stream,
3013                     " <hr />\n <h2>Failed Resource Actions</h2>\n <ul>\n");
3014             break;
3015 
3016         case mon_output_xml:
3017             fprintf(stream, "    <failures>\n");
3018             break;
3019 
3020         default:
3021             break;
3022     }
3023 
3024     /* Print each failed action */
3025     for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL;
3026          xml_op = __xml_next(xml_op)) {
3027         print_failed_action(stream, xml_op);
3028     }
3029 
3030     /* End section */
3031     switch (output_format) {
3032         case mon_output_html:
3033         case mon_output_cgi:
3034             fprintf(stream, " </ul>\n");
3035             break;
3036 
3037         case mon_output_xml:
3038             fprintf(stream, "    </failures>\n");
3039             break;
3040 
3041         default:
3042             break;
3043     }
3044 }
3045 
3046 /*!
3047  * \internal
3048  * \brief Reduce the stonith-history
3049  *        for successful actions we keep the last of every action-type & target
3050  *        for failed actions we record as well who had failed
3051  *        for actions in progress we keep full track
3052  *
3053  * \param[in] history    List of stonith actions
3054  *
3055  */
3056 static stonith_history_t *
reduce_stonith_history(stonith_history_t * history)3057 reduce_stonith_history(stonith_history_t *history)
3058 {
3059     stonith_history_t *new = history, *hp, *np;
3060 
3061     if (new) {
3062         hp = new->next;
3063         new->next = NULL;
3064 
3065         while (hp) {
3066             stonith_history_t *hp_next = hp->next;
3067 
3068             hp->next = NULL;
3069 
3070             for (np = new; ; np = np->next) {
3071                 if ((hp->state == st_done) || (hp->state == st_failed)) {
3072                     /* action not in progress */
3073                     if (safe_str_eq(hp->target, np->target) &&
3074                         safe_str_eq(hp->action, np->action) &&
3075                         (hp->state == np->state) &&
3076                         ((hp->state == st_done) ||
3077                          safe_str_eq(hp->delegate, np->delegate))) {
3078                             /* purge older hp */
3079                             stonith_history_free(hp);
3080                             break;
3081                     }
3082                 }
3083 
3084                 if (!np->next) {
3085                     np->next = hp;
3086                     break;
3087                 }
3088             }
3089             hp = hp_next;
3090         }
3091     }
3092 
3093     return new;
3094 }
3095 
3096 /*!
3097  * \internal
3098  * \brief Sort the stonith-history
3099  *        sort by competed most current on the top
3100  *        pending actions lacking a completed-stamp are gathered at the top
3101  *
3102  * \param[in] history    List of stonith actions
3103  *
3104  */
3105 static stonith_history_t *
sort_stonith_history(stonith_history_t * history)3106 sort_stonith_history(stonith_history_t *history)
3107 {
3108     stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
3109 
3110     for (hp = history; hp; ) {
3111         tmp = hp->next;
3112         if ((hp->state == st_done) || (hp->state == st_failed)) {
3113             /* sort into new */
3114             if ((!new) || (hp->completed > new->completed)) {
3115                 hp->next = new;
3116                 new = hp;
3117             } else {
3118                 np = new;
3119                 do {
3120                     if ((!np->next) || (hp->completed > np->next->completed)) {
3121                         hp->next = np->next;
3122                         np->next = hp;
3123                         break;
3124                     }
3125                     np = np->next;
3126                 } while (1);
3127             }
3128         } else {
3129             /* put into pending */
3130             hp->next = pending;
3131             pending = hp;
3132         }
3133         hp = tmp;
3134     }
3135 
3136     /* pending actions don't have a completed-stamp so make them go front */
3137     if (pending) {
3138         stonith_history_t *last_pending = pending;
3139 
3140         while (last_pending->next) {
3141             last_pending = last_pending->next;
3142         }
3143 
3144         last_pending->next = new;
3145         new = pending;
3146     }
3147     return new;
3148 }
3149 
3150 /*!
3151  * \internal
3152  * \brief Turn stonith action into a better readable string
3153  *
3154  * \param[in] action     Stonith action
3155  */
3156 static char *
fence_action_str(const char * action)3157 fence_action_str(const char *action)
3158 {
3159     char *str = NULL;
3160 
3161     if (action == NULL) {
3162         str = strdup("fencing");
3163     } else if (!strcmp(action, "on")) {
3164         str = strdup("unfencing");
3165     } else if (!strcmp(action, "off")) {
3166         str = strdup("turning off");
3167     } else {
3168         str = strdup(action);
3169     }
3170     return str;
3171 }
3172 
3173 /*!
3174  * \internal
3175  * \brief Print a stonith action
3176  *
3177  * \param[in] stream     File stream to display output to
3178  * \param[in] event      stonith event
3179  */
3180 static gboolean
is_later_succeeded(stonith_history_t * event,stonith_history_t * top_history)3181 is_later_succeeded(stonith_history_t *event, stonith_history_t *top_history)
3182 {
3183 
3184      gboolean ret = FALSE;
3185 
3186      for (stonith_history_t *prev_hp = top_history; prev_hp; prev_hp = prev_hp->next) {
3187         if (prev_hp == event) {
3188             break;
3189         }
3190 
3191          if ((prev_hp->state == st_done) &&
3192             safe_str_eq(event->target, prev_hp->target) &&
3193             safe_str_eq(event->action, prev_hp->action) &&
3194             safe_str_eq(event->delegate, prev_hp->delegate) &&
3195             (event->completed < prev_hp->completed)) {
3196             ret = TRUE;
3197             break;
3198         }
3199     }
3200     return ret;
3201 }
3202 
3203 static void
print_stonith_action(FILE * stream,stonith_history_t * event,stonith_history_t * top_history)3204 print_stonith_action(FILE *stream, stonith_history_t *event, stonith_history_t *top_history)
3205 {
3206     char *action_s = fence_action_str(event->action);
3207     time_t completed = event->completed;
3208     char *run_at_s = ctime((const time_t *) &completed);
3209 
3210     if ((run_at_s) && (run_at_s[0] != 0)) {
3211         run_at_s[strlen(run_at_s)-1] = 0; /* Overwrite the newline */
3212     }
3213 
3214     switch(output_format) {
3215         case mon_output_xml:
3216             fprintf(stream, "        <fence_event target=\"%s\" action=\"%s\"",
3217                     event->target, event->action);
3218             switch(event->state) {
3219                 case st_done:
3220                     fprintf(stream, " state=\"success\"");
3221                     break;
3222                 case st_failed:
3223                     fprintf(stream, " state=\"failed\"");
3224                     break;
3225                 default:
3226                     fprintf(stream, " state=\"pending\"");
3227             }
3228             fprintf(stream, " origin=\"%s\" client=\"%s\"",
3229                     event->origin, event->client);
3230             if (event->delegate) {
3231                 fprintf(stream, " delegate=\"%s\"", event->delegate);
3232             }
3233             switch(event->state) {
3234                 case st_done:
3235                 case st_failed:
3236                     fprintf(stream, " completed=\"%s\"", run_at_s?run_at_s:"");
3237                     break;
3238                 default:
3239                     break;
3240             }
3241             fprintf(stream, " />\n");
3242             break;
3243 
3244         case mon_output_plain:
3245         case mon_output_console:
3246             switch(event->state) {
3247                 case st_done:
3248                     print_as("* %s of %s successful: delegate=%s, client=%s, origin=%s,\n"
3249                              "    %s='%s'\n",
3250                              action_s, event->target,
3251                              event->delegate ? event->delegate : "",
3252                              event->client, event->origin,
3253                              fence_full_history?"completed":"last-successful",
3254                              run_at_s?run_at_s:"");
3255                     break;
3256                 case st_failed:
3257                     print_as("* %s of %s failed: delegate=%s, client=%s, origin=%s,\n"
3258                              "    %s='%s' %s\n",
3259                              action_s, event->target,
3260                              event->delegate ? event->delegate : "",
3261                              event->client, event->origin,
3262                              fence_full_history?"completed":"last-failed",
3263                              run_at_s?run_at_s:"",
3264                              is_later_succeeded(event, top_history) ? "(a later attempt succeeded)" : "");
3265                     break;
3266                 default:
3267                     print_as("* %s of %s pending: client=%s, origin=%s\n",
3268                              action_s, event->target,
3269                              event->client, event->origin);
3270             }
3271             break;
3272 
3273         case mon_output_html:
3274         case mon_output_cgi:
3275             switch(event->state) {
3276                 case st_done:
3277                     fprintf(stream, "  <li>%s of %s successful: delegate=%s, "
3278                                     "client=%s, origin=%s, %s='%s'</li>\n",
3279                                     action_s, event->target,
3280                                     event->delegate ? event->delegate : "",
3281                                     event->client, event->origin,
3282                                     fence_full_history?"completed":"last-successful",
3283                                     run_at_s?run_at_s:"");
3284                     break;
3285                 case st_failed:
3286                     fprintf(stream, "  <li>%s of %s failed: delegate=%s, "
3287                                     "client=%s, origin=%s, %s='%s' %s</li>\n",
3288                                     action_s, event->target,
3289                                     event->delegate ? event->delegate : "",
3290                                     event->client, event->origin,
3291                                     fence_full_history?"completed":"last-failed",
3292                                     run_at_s?run_at_s:"",
3293                                     is_later_succeeded(event, top_history) ? "(a later attempt succeeded)" : "");
3294                     break;
3295                 default:
3296                     fprintf(stream, "  <li>%s of %s pending: client=%s, "
3297                                     "origin=%s</li>\n",
3298                                     action_s, event->target,
3299                                     event->client, event->origin);
3300             }
3301             break;
3302 
3303         default:
3304             /* no support for fence history for other formats so far */
3305             break;
3306     }
3307 
3308     free(action_s);
3309 }
3310 
3311 /*!
3312  * \internal
3313  * \brief Print a section for failed stonith actions
3314  *
3315  * \param[in] stream     File stream to display output to
3316  * \param[in] history    List of stonith actions
3317  *
3318  */
3319 static void
print_failed_stonith_actions(FILE * stream,stonith_history_t * history)3320 print_failed_stonith_actions(FILE *stream, stonith_history_t *history)
3321 {
3322     stonith_history_t *hp;
3323 
3324     for (hp = history; hp; hp = hp->next) {
3325         if (hp->state == st_failed) {
3326             break;
3327         }
3328     }
3329     if (!hp) {
3330         return;
3331     }
3332 
3333     /* Print section heading */
3334     switch (output_format) {
3335         /* no need to take care of xml in here as xml gets full
3336          * history anyway
3337          */
3338         case mon_output_plain:
3339         case mon_output_console:
3340             print_as("\nFailed Fencing Actions:\n");
3341             break;
3342 
3343         case mon_output_html:
3344         case mon_output_cgi:
3345             fprintf(stream, " <hr />\n <h2>Failed Fencing Actions</h2>\n <ul>\n");
3346             break;
3347 
3348         default:
3349             break;
3350     }
3351 
3352     /* Print each failed stonith action */
3353     for (hp = history; hp; hp = hp->next) {
3354         if (hp->state == st_failed) {
3355             print_stonith_action(stream, hp, history);
3356         }
3357     }
3358 
3359     /* End section */
3360     switch (output_format) {
3361         case mon_output_html:
3362         case mon_output_cgi:
3363             fprintf(stream, " </ul>\n");
3364             break;
3365 
3366         default:
3367             break;
3368     }
3369 }
3370 
3371 /*!
3372  * \internal
3373  * \brief Print pending stonith actions
3374  *
3375  * \param[in] stream     File stream to display output to
3376  * \param[in] history    List of stonith actions
3377  *
3378  */
3379 static void
print_stonith_pending(FILE * stream,stonith_history_t * history)3380 print_stonith_pending(FILE *stream, stonith_history_t *history)
3381 {
3382     /* xml-output always shows the full history
3383      * so we'll never have to show pending-actions
3384      * separately
3385      */
3386     if (history && (history->state != st_failed) &&
3387         (history->state != st_done)) {
3388         stonith_history_t *hp;
3389 
3390         /* Print section heading */
3391         switch (output_format) {
3392             case mon_output_plain:
3393             case mon_output_console:
3394                 print_as("\nPending Fencing Actions:\n");
3395                 break;
3396 
3397             case mon_output_html:
3398             case mon_output_cgi:
3399                 fprintf(stream, " <hr />\n <h2>Pending Fencing Actions</h2>\n <ul>\n");
3400                 break;
3401 
3402             default:
3403                 break;
3404         }
3405 
3406         for (hp = history; hp; hp = hp->next) {
3407             if ((hp->state == st_failed) || (hp->state == st_done)) {
3408                 break;
3409             }
3410             print_stonith_action(stream, hp, NULL);
3411         }
3412 
3413         /* End section */
3414         switch (output_format) {
3415             case mon_output_html:
3416             case mon_output_cgi:
3417                 fprintf(stream, " </ul>\n");
3418                 break;
3419 
3420         default:
3421             break;
3422         }
3423     }
3424 }
3425 
3426 /*!
3427  * \internal
3428  * \brief Print a section for stonith-history
3429  *
3430  * \param[in] stream     File stream to display output to
3431  * \param[in] history    List of stonith actions
3432  *
3433  */
3434 static void
print_stonith_history(FILE * stream,stonith_history_t * history)3435 print_stonith_history(FILE *stream, stonith_history_t *history)
3436 {
3437     stonith_history_t *hp;
3438 
3439     /* Print section heading */
3440     switch (output_format) {
3441         case mon_output_plain:
3442         case mon_output_console:
3443             print_as("\nFencing History:\n");
3444             break;
3445 
3446         case mon_output_html:
3447         case mon_output_cgi:
3448             fprintf(stream, " <hr />\n <h2>Fencing History</h2>\n <ul>\n");
3449             break;
3450 
3451         case mon_output_xml:
3452             fprintf(stream, "    <fence_history>\n");
3453             break;
3454 
3455         default:
3456             break;
3457     }
3458 
3459     for (hp = history; hp; hp = hp->next) {
3460         if ((hp->state != st_failed) || (output_format == mon_output_xml)) {
3461             print_stonith_action(stream, hp, NULL);
3462         }
3463     }
3464 
3465     /* End section */
3466     switch (output_format) {
3467         case mon_output_html:
3468         case mon_output_cgi:
3469             fprintf(stream, " </ul>\n");
3470             break;
3471 
3472         case mon_output_xml:
3473             fprintf(stream, "    </fence_history>\n");
3474             break;
3475 
3476         default:
3477             break;
3478     }
3479 }
3480 
3481 /*!
3482  * \internal
3483  * \brief Print cluster status to screen
3484  *
3485  * This uses the global display preferences set by command-line options
3486  * to display cluster status in a human-friendly way.
3487  *
3488  * \param[in] data_set          Working set of CIB state
3489  * \param[in] stonith_history   List of stonith actions
3490  */
3491 static void
print_status(pe_working_set_t * data_set,stonith_history_t * stonith_history)3492 print_status(pe_working_set_t * data_set,
3493              stonith_history_t *stonith_history)
3494 {
3495     GListPtr gIter = NULL;
3496     int print_opts = get_resource_display_options();
3497 
3498     /* space-separated lists of node names */
3499     char *online_nodes = NULL;
3500     char *online_remote_nodes = NULL;
3501     char *online_guest_nodes = NULL;
3502     char *offline_nodes = NULL;
3503     char *offline_remote_nodes = NULL;
3504 
3505     if (output_format == mon_output_console) {
3506         blank_screen();
3507     }
3508     print_cluster_summary(stdout, data_set);
3509     print_as("\n");
3510 
3511     /* Gather node information (and print if in bad state or grouping by node) */
3512     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3513         node_t *node = (node_t *) gIter->data;
3514         const char *node_mode = NULL;
3515         char *node_name = get_node_display_name(node);
3516 
3517         /* Get node mode */
3518         if (node->details->unclean) {
3519             if (node->details->online) {
3520                 node_mode = "UNCLEAN (online)";
3521 
3522             } else if (node->details->pending) {
3523                 node_mode = "UNCLEAN (pending)";
3524 
3525             } else {
3526                 node_mode = "UNCLEAN (offline)";
3527             }
3528 
3529         } else if (node->details->pending) {
3530             node_mode = "pending";
3531 
3532         } else if (node->details->standby_onfail && node->details->online) {
3533             node_mode = "standby (on-fail)";
3534 
3535         } else if (node->details->standby) {
3536             if (node->details->online) {
3537                 if (node->details->running_rsc) {
3538                     node_mode = "standby (with active resources)";
3539                 } else {
3540                     node_mode = "standby";
3541                 }
3542             } else {
3543                 node_mode = "OFFLINE (standby)";
3544             }
3545 
3546         } else if (node->details->maintenance) {
3547             if (node->details->online) {
3548                 node_mode = "maintenance";
3549             } else {
3550                 node_mode = "OFFLINE (maintenance)";
3551             }
3552 
3553         } else if (node->details->online) {
3554             node_mode = "online";
3555             if (group_by_node == FALSE) {
3556                 if (is_container_remote_node(node)) {
3557                     online_guest_nodes = add_list_element(online_guest_nodes, node_name);
3558                 } else if (is_baremetal_remote_node(node)) {
3559                     online_remote_nodes = add_list_element(online_remote_nodes, node_name);
3560                 } else {
3561                     online_nodes = add_list_element(online_nodes, node_name);
3562                 }
3563                 free(node_name);
3564                 continue;
3565             }
3566         } else {
3567             node_mode = "OFFLINE";
3568             if (group_by_node == FALSE) {
3569                 if (is_baremetal_remote_node(node)) {
3570                     offline_remote_nodes = add_list_element(offline_remote_nodes, node_name);
3571                 } else if (is_container_remote_node(node)) {
3572                     /* ignore offline guest nodes */
3573                 } else {
3574                     offline_nodes = add_list_element(offline_nodes, node_name);
3575                 }
3576                 free(node_name);
3577                 continue;
3578             }
3579         }
3580 
3581         /* If we get here, node is in bad state, or we're grouping by node */
3582 
3583         /* Print the node name and status */
3584         if (is_container_remote_node(node)) {
3585             print_as("Guest");
3586         } else if (is_baremetal_remote_node(node)) {
3587             print_as("Remote");
3588         }
3589         print_as("Node %s: %s\n", node_name, node_mode);
3590 
3591         /* If we're grouping by node, print its resources */
3592         if (group_by_node) {
3593             if (print_brief) {
3594                 print_rscs_brief(node->details->running_rsc, "\t", print_opts | pe_print_rsconly,
3595                                  stdout, FALSE);
3596             } else {
3597                 GListPtr gIter2 = NULL;
3598 
3599                 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
3600                     resource_t *rsc = (resource_t *) gIter2->data;
3601 
3602                     rsc->fns->print(rsc, "\t", print_opts | pe_print_rsconly, stdout);
3603                 }
3604             }
3605         }
3606         free(node_name);
3607     }
3608 
3609     /* If we're not grouping by node, summarize nodes by status */
3610     if (online_nodes) {
3611         print_as("Online: [%s ]\n", online_nodes);
3612         free(online_nodes);
3613     }
3614     if (offline_nodes) {
3615         print_as("OFFLINE: [%s ]\n", offline_nodes);
3616         free(offline_nodes);
3617     }
3618     if (online_remote_nodes) {
3619         print_as("RemoteOnline: [%s ]\n", online_remote_nodes);
3620         free(online_remote_nodes);
3621     }
3622     if (offline_remote_nodes) {
3623         print_as("RemoteOFFLINE: [%s ]\n", offline_remote_nodes);
3624         free(offline_remote_nodes);
3625     }
3626     if (online_guest_nodes) {
3627         print_as("GuestOnline: [%s ]\n", online_guest_nodes);
3628         free(online_guest_nodes);
3629     }
3630 
3631     /* Print resources section, if needed */
3632     print_resources(stdout, data_set, print_opts);
3633 
3634     /* print Node Attributes section if requested */
3635     if (show & mon_show_attributes) {
3636         print_node_attributes(stdout, data_set);
3637     }
3638 
3639     /* If requested, print resource operations (which includes failcounts)
3640      * or just failcounts
3641      */
3642     if (show & (mon_show_operations | mon_show_failcounts)) {
3643         print_node_summary(stdout, data_set,
3644                            ((show & mon_show_operations)? TRUE : FALSE));
3645     }
3646 
3647     /* If there were any failed actions, print them */
3648     if (xml_has_children(data_set->failed)) {
3649         print_failed_actions(stdout, data_set);
3650     }
3651 
3652     /* Print failed stonith actions */
3653     if (fence_history) {
3654         print_failed_stonith_actions(stdout, stonith_history);
3655     }
3656 
3657     /* Print tickets if requested */
3658     if (show & mon_show_tickets) {
3659         print_cluster_tickets(stdout, data_set);
3660     }
3661 
3662     /* Print negative location constraints if requested */
3663     if (show & mon_show_bans) {
3664         print_neg_locations(stdout, data_set);
3665     }
3666 
3667     /* Print stonith history */
3668     if (fence_history) {
3669         if (show & mon_show_fence_history) {
3670             print_stonith_history(stdout, stonith_history);
3671         } else {
3672             print_stonith_pending(stdout, stonith_history);
3673         }
3674     }
3675 
3676 #if CURSES_ENABLED
3677     if (output_format == mon_output_console) {
3678         refresh();
3679     }
3680 #endif
3681 }
3682 
3683 /*!
3684  * \internal
3685  * \brief Print cluster status in XML format
3686  *
3687  * \param[in] data_set   Working set of CIB state
3688  */
3689 static void
print_xml_status(pe_working_set_t * data_set,stonith_history_t * stonith_history)3690 print_xml_status(pe_working_set_t * data_set,
3691                  stonith_history_t *stonith_history)
3692 {
3693     FILE *stream = stdout;
3694     GListPtr gIter = NULL;
3695     int print_opts = get_resource_display_options();
3696 
3697     fprintf(stream, "<?xml version=\"1.0\"?>\n");
3698     fprintf(stream, "<crm_mon version=\"%s\">\n", VERSION);
3699 
3700     print_cluster_summary(stream, data_set);
3701 
3702     /*** NODES ***/
3703     fprintf(stream, "    <nodes>\n");
3704     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3705         node_t *node = (node_t *) gIter->data;
3706         const char *node_type = "unknown";
3707 
3708         switch (node->details->type) {
3709             case node_member:
3710                 node_type = "member";
3711                 break;
3712             case node_remote:
3713                 node_type = "remote";
3714                 break;
3715             case node_ping:
3716                 node_type = "ping";
3717                 break;
3718         }
3719 
3720         fprintf(stream, "        <node name=\"%s\" ", node->details->uname);
3721         fprintf(stream, "id=\"%s\" ", node->details->id);
3722         fprintf(stream, "online=\"%s\" ", node->details->online ? "true" : "false");
3723         fprintf(stream, "standby=\"%s\" ", node->details->standby ? "true" : "false");
3724         fprintf(stream, "standby_onfail=\"%s\" ", node->details->standby_onfail ? "true" : "false");
3725         fprintf(stream, "maintenance=\"%s\" ", node->details->maintenance ? "true" : "false");
3726         fprintf(stream, "pending=\"%s\" ", node->details->pending ? "true" : "false");
3727         fprintf(stream, "unclean=\"%s\" ", node->details->unclean ? "true" : "false");
3728         fprintf(stream, "shutdown=\"%s\" ", node->details->shutdown ? "true" : "false");
3729         fprintf(stream, "expected_up=\"%s\" ", node->details->expected_up ? "true" : "false");
3730         fprintf(stream, "is_dc=\"%s\" ", node->details->is_dc ? "true" : "false");
3731         fprintf(stream, "resources_running=\"%d\" ", g_list_length(node->details->running_rsc));
3732         fprintf(stream, "type=\"%s\" ", node_type);
3733         if (is_container_remote_node(node)) {
3734             fprintf(stream, "id_as_resource=\"%s\" ", node->details->remote_rsc->container->id);
3735         }
3736 
3737         if (group_by_node) {
3738             GListPtr lpc2 = NULL;
3739 
3740             fprintf(stream, ">\n");
3741             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
3742                 resource_t *rsc = (resource_t *) lpc2->data;
3743 
3744                 rsc->fns->print(rsc, "            ", print_opts | pe_print_rsconly, stream);
3745             }
3746             fprintf(stream, "        </node>\n");
3747         } else {
3748             fprintf(stream, "/>\n");
3749         }
3750     }
3751     fprintf(stream, "    </nodes>\n");
3752 
3753     /* Print resources section, if needed */
3754     print_resources(stream, data_set, print_opts);
3755 
3756     /* print Node Attributes section if requested */
3757     if (show & mon_show_attributes) {
3758         print_node_attributes(stream, data_set);
3759     }
3760 
3761     /* If requested, print resource operations (which includes failcounts)
3762      * or just failcounts
3763      */
3764     if (show & (mon_show_operations | mon_show_failcounts)) {
3765         print_node_summary(stream, data_set,
3766                            ((show & mon_show_operations)? TRUE : FALSE));
3767     }
3768 
3769     /* If there were any failed actions, print them */
3770     if (xml_has_children(data_set->failed)) {
3771         print_failed_actions(stream, data_set);
3772     }
3773 
3774     /* Print stonith history */
3775     if (fence_history) {
3776         print_stonith_history(stdout, stonith_history);
3777     }
3778 
3779     /* Print tickets if requested */
3780     if (show & mon_show_tickets) {
3781         print_cluster_tickets(stream, data_set);
3782     }
3783 
3784     /* Print negative location constraints if requested */
3785     if (show & mon_show_bans) {
3786         print_neg_locations(stream, data_set);
3787     }
3788 
3789     fprintf(stream, "</crm_mon>\n");
3790     fflush(stream);
3791     fclose(stream);
3792 }
3793 
3794 /*!
3795  * \internal
3796  * \brief Print cluster status in HTML format (with HTTP headers if CGI)
3797  *
3798  * \param[in] data_set   Working set of CIB state
3799  * \param[in] filename   Name of file to write HTML to (ignored if CGI)
3800  *
3801  * \return 0 on success, -1 on error
3802  */
3803 static int
print_html_status(pe_working_set_t * data_set,const char * filename,stonith_history_t * stonith_history)3804 print_html_status(pe_working_set_t * data_set,
3805                   const char *filename,
3806                   stonith_history_t *stonith_history)
3807 {
3808     FILE *stream;
3809     GListPtr gIter = NULL;
3810     char *filename_tmp = NULL;
3811     int print_opts = get_resource_display_options();
3812 
3813     if (output_format == mon_output_cgi) {
3814         stream = stdout;
3815         fprintf(stream, "Content-Type: text/html\n\n");
3816 
3817     } else {
3818         filename_tmp = crm_concat(filename, "tmp", '.');
3819         stream = fopen(filename_tmp, "w");
3820         if (stream == NULL) {
3821             crm_perror(LOG_ERR, "Cannot open %s for writing", filename_tmp);
3822             free(filename_tmp);
3823             return -1;
3824         }
3825     }
3826 
3827     fprintf(stream, "<html>\n");
3828     fprintf(stream, " <head>\n");
3829     fprintf(stream, "  <title>Cluster status</title>\n");
3830     fprintf(stream, "  <meta http-equiv=\"refresh\" content=\"%d\">\n", reconnect_msec / 1000);
3831     fprintf(stream, " </head>\n");
3832     fprintf(stream, "<body>\n");
3833 
3834     print_cluster_summary(stream, data_set);
3835 
3836     /*** NODE LIST ***/
3837 
3838     fprintf(stream, " <hr />\n <h2>Node List</h2>\n");
3839     fprintf(stream, "<ul>\n");
3840     for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
3841         node_t *node = (node_t *) gIter->data;
3842         char *node_name = get_node_display_name(node);
3843 
3844         fprintf(stream, "<li>Node: %s: ", node_name);
3845         if (node->details->standby_onfail && node->details->online) {
3846             fprintf(stream, "<font color=\"orange\">standby (on-fail)</font>\n");
3847         } else if (node->details->standby && node->details->online) {
3848 
3849             fprintf(stream, "<font color=\"orange\">standby%s</font>\n",
3850                 node->details->running_rsc?" (with active resources)":"");
3851         } else if (node->details->standby) {
3852             fprintf(stream, "<font color=\"red\">OFFLINE (standby)</font>\n");
3853         } else if (node->details->maintenance && node->details->online) {
3854             fprintf(stream, "<font color=\"blue\">maintenance</font>\n");
3855         } else if (node->details->maintenance) {
3856             fprintf(stream, "<font color=\"red\">OFFLINE (maintenance)</font>\n");
3857         } else if (node->details->online) {
3858             fprintf(stream, "<font color=\"green\">online</font>\n");
3859         } else {
3860             fprintf(stream, "<font color=\"red\">OFFLINE</font>\n");
3861         }
3862         if (print_brief && group_by_node) {
3863             fprintf(stream, "<ul>\n");
3864             print_rscs_brief(node->details->running_rsc, NULL, print_opts | pe_print_rsconly,
3865                              stream, FALSE);
3866             fprintf(stream, "</ul>\n");
3867 
3868         } else if (group_by_node) {
3869             GListPtr lpc2 = NULL;
3870 
3871             fprintf(stream, "<ul>\n");
3872             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
3873                 resource_t *rsc = (resource_t *) lpc2->data;
3874 
3875                 fprintf(stream, "<li>");
3876                 rsc->fns->print(rsc, NULL, print_opts | pe_print_rsconly, stream);
3877                 fprintf(stream, "</li>\n");
3878             }
3879             fprintf(stream, "</ul>\n");
3880         }
3881         fprintf(stream, "</li>\n");
3882         free(node_name);
3883     }
3884     fprintf(stream, "</ul>\n");
3885 
3886     /* Print resources section, if needed */
3887     print_resources(stream, data_set, print_opts);
3888 
3889     /* print Node Attributes section if requested */
3890     if (show & mon_show_attributes) {
3891         print_node_attributes(stream, data_set);
3892     }
3893 
3894     /* If requested, print resource operations (which includes failcounts)
3895      * or just failcounts
3896      */
3897     if (show & (mon_show_operations | mon_show_failcounts)) {
3898         print_node_summary(stream, data_set,
3899                            ((show & mon_show_operations)? TRUE : FALSE));
3900     }
3901 
3902     /* If there were any failed actions, print them */
3903     if (xml_has_children(data_set->failed)) {
3904         print_failed_actions(stream, data_set);
3905     }
3906 
3907     /* Print failed stonith actions */
3908     if (fence_history) {
3909         print_failed_stonith_actions(stream, stonith_history);
3910     }
3911 
3912     /* Print stonith history */
3913     if (fence_history) {
3914         if (show & mon_show_fence_history) {
3915             print_stonith_history(stream, stonith_history);
3916         } else {
3917             print_stonith_pending(stdout, stonith_history);
3918         }
3919     }
3920 
3921     /* Print tickets if requested */
3922     if (show & mon_show_tickets) {
3923         print_cluster_tickets(stream, data_set);
3924     }
3925 
3926     /* Print negative location constraints if requested */
3927     if (show & mon_show_bans) {
3928         print_neg_locations(stream, data_set);
3929     }
3930 
3931     fprintf(stream, "</body>\n");
3932     fprintf(stream, "</html>\n");
3933     fflush(stream);
3934     fclose(stream);
3935 
3936     if (output_format != mon_output_cgi) {
3937         if (rename(filename_tmp, filename) != 0) {
3938             crm_perror(LOG_ERR, "Unable to rename %s->%s", filename_tmp, filename);
3939         }
3940         free(filename_tmp);
3941     }
3942     return 0;
3943 }
3944 
3945 #if ENABLE_SNMP
3946 #  include <net-snmp/net-snmp-config.h>
3947 #  include <net-snmp/snmpv3_api.h>
3948 #  include <net-snmp/agent/agent_trap.h>
3949 #  include <net-snmp/library/snmp_client.h>
3950 #  include <net-snmp/library/mib.h>
3951 #  include <net-snmp/library/snmp_debug.h>
3952 
3953 #  define add_snmp_field(list, oid_string, value) do {			\
3954 	oid name[MAX_OID_LEN];						\
3955         size_t name_length = MAX_OID_LEN;				\
3956 	if (snmp_parse_oid(oid_string, name, &name_length)) {		\
3957 	    int s_rc = snmp_add_var(list, name, name_length, 's', (value)); \
3958 	    if(s_rc != 0) {						\
3959 		crm_err("Could not add %s=%s rc=%d", oid_string, value, s_rc); \
3960 	    } else {							\
3961 		crm_trace("Added %s=%s", oid_string, value);		\
3962 	    }								\
3963 	} else {							\
3964 	    crm_err("Could not parse OID: %s", oid_string);		\
3965 	}								\
3966     } while(0)								\
3967 
3968 #  define add_snmp_field_int(list, oid_string, value) do {		\
3969 	oid name[MAX_OID_LEN];						\
3970         size_t name_length = MAX_OID_LEN;				\
3971 	if (snmp_parse_oid(oid_string, name, &name_length)) {		\
3972 	    if(NULL == snmp_pdu_add_variable(				\
3973 		   list, name, name_length, ASN_INTEGER,		\
3974 		   (u_char *) & value, sizeof(value))) {		\
3975 		crm_err("Could not add %s=%d", oid_string, value);	\
3976 	    } else {							\
3977 		crm_trace("Added %s=%d", oid_string, value);		\
3978 	    }								\
3979 	} else {							\
3980 	    crm_err("Could not parse OID: %s", oid_string);		\
3981 	}								\
3982     } while(0)								\
3983 
3984 static int
snmp_input(int operation,netsnmp_session * session,int reqid,netsnmp_pdu * pdu,void * magic)3985 snmp_input(int operation, netsnmp_session * session, int reqid, netsnmp_pdu * pdu, void *magic)
3986 {
3987     return 1;
3988 }
3989 
3990 static netsnmp_session *
crm_snmp_init(const char * target,char * community)3991 crm_snmp_init(const char *target, char *community)
3992 {
3993     static netsnmp_session *session = NULL;
3994 
3995 #  ifdef NETSNMPV53
3996     char target53[128];
3997 
3998     snprintf(target53, sizeof(target53), "%s:162", target);
3999 #  endif
4000 
4001     if (session) {
4002         return session;
4003     }
4004 
4005     if (target == NULL) {
4006         return NULL;
4007     }
4008 
4009     if (get_crm_log_level() > LOG_INFO) {
4010         char *debug_tokens = strdup("run:shell,snmptrap,tdomain");
4011 
4012         debug_register_tokens(debug_tokens);
4013         snmp_set_do_debugging(1);
4014     }
4015 
4016     session = calloc(1, sizeof(netsnmp_session));
4017     snmp_sess_init(session);
4018     session->version = SNMP_VERSION_2c;
4019     session->callback = snmp_input;
4020     session->callback_magic = NULL;
4021 
4022     if (community) {
4023         session->community_len = strlen(community);
4024         session->community = (unsigned char *)community;
4025     }
4026 
4027     session = snmp_add(session,
4028 #  ifdef NETSNMPV53
4029                        netsnmp_tdomain_transport(target53, 0, "udp"),
4030 #  else
4031                        netsnmp_transport_open_client("snmptrap", target),
4032 #  endif
4033                        NULL, NULL);
4034 
4035     if (session == NULL) {
4036         snmp_sess_perror("Could not create snmp transport", session);
4037     }
4038     return session;
4039 }
4040 
4041 #endif
4042 
4043 static int
send_snmp_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4044 send_snmp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4045                int status, const char *desc)
4046 {
4047     int ret = 1;
4048 
4049 #if ENABLE_SNMP
4050     static oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
4051     static oid sysuptime_oid[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
4052 
4053     netsnmp_pdu *trap_pdu;
4054     netsnmp_session *session = crm_snmp_init(snmp_target, snmp_community);
4055 
4056     trap_pdu = snmp_pdu_create(SNMP_MSG_TRAP2);
4057     if (!trap_pdu) {
4058         crm_err("Failed to create SNMP notification");
4059         return SNMPERR_GENERR;
4060     }
4061 
4062     if (1) {
4063         /* send uptime */
4064         char csysuptime[20];
4065         time_t now = time(NULL);
4066 
4067         sprintf(csysuptime, "%lld", (long long) now);
4068         snmp_add_var(trap_pdu, sysuptime_oid, sizeof(sysuptime_oid) / sizeof(oid), 't', csysuptime);
4069     }
4070 
4071     /* Indicate what the trap is by setting snmpTrapOid.0 */
4072     ret =
4073         snmp_add_var(trap_pdu, snmptrap_oid, sizeof(snmptrap_oid) / sizeof(oid), 'o',
4074                      snmp_crm_trap_oid);
4075     if (ret != 0) {
4076         crm_err("Failed set snmpTrapOid.0=%s", snmp_crm_trap_oid);
4077         return ret;
4078     }
4079 
4080     /* Add extries to the trap */
4081     if (rsc) {
4082         add_snmp_field(trap_pdu, snmp_crm_oid_rsc, rsc);
4083     }
4084     add_snmp_field(trap_pdu, snmp_crm_oid_node, node);
4085     add_snmp_field(trap_pdu, snmp_crm_oid_task, task);
4086     add_snmp_field(trap_pdu, snmp_crm_oid_desc, desc);
4087 
4088     add_snmp_field_int(trap_pdu, snmp_crm_oid_rc, rc);
4089     add_snmp_field_int(trap_pdu, snmp_crm_oid_trc, target_rc);
4090     add_snmp_field_int(trap_pdu, snmp_crm_oid_status, status);
4091 
4092     /* Send and cleanup */
4093     ret = snmp_send(session, trap_pdu);
4094     if (ret == 0) {
4095         /* error */
4096         snmp_sess_perror("Could not send SNMP trap", session);
4097         snmp_free_pdu(trap_pdu);
4098         ret = SNMPERR_GENERR;
4099     } else {
4100         ret = SNMPERR_SUCCESS;
4101     }
4102 #else
4103     crm_err("Sending SNMP traps is not supported by this installation");
4104 #endif
4105     return ret;
4106 }
4107 
4108 #if ENABLE_ESMTP
4109 #  include <auth-client.h>
4110 #  include <libesmtp.h>
4111 
4112 static void
print_recipient_status(smtp_recipient_t recipient,const char * mailbox,void * arg)4113 print_recipient_status(smtp_recipient_t recipient, const char *mailbox, void *arg)
4114 {
4115     const smtp_status_t *status;
4116 
4117     status = smtp_recipient_status(recipient);
4118     printf("%s: %d %s", mailbox, status->code, status->text);
4119 }
4120 
4121 static void
event_cb(smtp_session_t session,int event_no,void * arg,...)4122 event_cb(smtp_session_t session, int event_no, void *arg, ...)
4123 {
4124     int *ok;
4125     va_list alist;
4126 
4127     va_start(alist, arg);
4128     switch (event_no) {
4129         case SMTP_EV_CONNECT:
4130         case SMTP_EV_MAILSTATUS:
4131         case SMTP_EV_RCPTSTATUS:
4132         case SMTP_EV_MESSAGEDATA:
4133         case SMTP_EV_MESSAGESENT:
4134         case SMTP_EV_DISCONNECT:
4135             break;
4136 
4137         case SMTP_EV_WEAK_CIPHER:{
4138                 int bits = va_arg(alist, long);
4139                 ok = va_arg(alist, int *);
4140 
4141                 crm_debug("SMTP_EV_WEAK_CIPHER, bits=%d - accepted.", bits);
4142                 *ok = 1;
4143                 break;
4144             }
4145         case SMTP_EV_STARTTLS_OK:
4146             crm_debug("SMTP_EV_STARTTLS_OK - TLS started here.");
4147             break;
4148 
4149         case SMTP_EV_INVALID_PEER_CERTIFICATE:{
4150                 long vfy_result = va_arg(alist, long);
4151                 ok = va_arg(alist, int *);
4152 
4153                 /* There is a table in handle_invalid_peer_certificate() of mail-file.c */
4154                 crm_err("SMTP_EV_INVALID_PEER_CERTIFICATE: %ld", vfy_result);
4155                 *ok = 1;
4156                 break;
4157             }
4158         case SMTP_EV_NO_PEER_CERTIFICATE:
4159             ok = va_arg(alist, int *);
4160 
4161             crm_debug("SMTP_EV_NO_PEER_CERTIFICATE - accepted.");
4162             *ok = 1;
4163             break;
4164         case SMTP_EV_WRONG_PEER_CERTIFICATE:
4165             ok = va_arg(alist, int *);
4166 
4167             crm_debug("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted.");
4168             *ok = 1;
4169             break;
4170         case SMTP_EV_NO_CLIENT_CERTIFICATE:
4171             ok = va_arg(alist, int *);
4172 
4173             crm_debug("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted.");
4174             *ok = 1;
4175             break;
4176         default:
4177             crm_debug("Got event: %d - ignored.", event_no);
4178     }
4179     va_end(alist);
4180 }
4181 #endif
4182 
4183 #define BODY_MAX 2048
4184 
4185 #if ENABLE_ESMTP
4186 static void
crm_smtp_debug(const char * buf,int buflen,int writing,void * arg)4187 crm_smtp_debug(const char *buf, int buflen, int writing, void *arg)
4188 {
4189     char type = 0;
4190     int lpc = 0, last = 0, level = *(int *)arg;
4191 
4192     if (writing == SMTP_CB_HEADERS) {
4193         type = 'H';
4194     } else if (writing) {
4195         type = 'C';
4196     } else {
4197         type = 'S';
4198     }
4199 
4200     for (; lpc < buflen; lpc++) {
4201         switch (buf[lpc]) {
4202             case 0:
4203             case '\n':
4204                 if (last > 0) {
4205                     do_crm_log(level, "   %.*s", lpc - last, buf + last);
4206                 } else {
4207                     do_crm_log(level, "%c: %.*s", type, lpc - last, buf + last);
4208                 }
4209                 last = lpc + 1;
4210                 break;
4211         }
4212     }
4213 }
4214 #endif
4215 
4216 static int
send_custom_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4217 send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4218                  int status, const char *desc)
4219 {
4220     pid_t pid;
4221 
4222     /*setenv needs chars, these are ints */
4223     char *rc_s = crm_itoa(rc);
4224     char *status_s = crm_itoa(status);
4225     char *target_rc_s = crm_itoa(target_rc);
4226 
4227     crm_debug("Sending external notification to '%s' via '%s'", external_recipient, external_agent);
4228 
4229     if(rsc) {
4230         setenv("CRM_notify_rsc", rsc, 1);
4231     }
4232     if (external_recipient) {
4233         setenv("CRM_notify_recipient", external_recipient, 1);
4234     }
4235     setenv("CRM_notify_node", node, 1);
4236     setenv("CRM_notify_task", task, 1);
4237     setenv("CRM_notify_desc", desc, 1);
4238     setenv("CRM_notify_rc", rc_s, 1);
4239     setenv("CRM_notify_target_rc", target_rc_s, 1);
4240     setenv("CRM_notify_status", status_s, 1);
4241 
4242     pid = fork();
4243     if (pid == -1) {
4244         crm_perror(LOG_ERR, "notification fork() failed.");
4245     }
4246     if (pid == 0) {
4247         /* crm_debug("notification: I am the child. Executing the nofitication program."); */
4248         execl(external_agent, external_agent, NULL);
4249         exit(EXIT_FAILURE);
4250     }
4251 
4252     crm_trace("Finished running custom notification program '%s'.", external_agent);
4253     free(target_rc_s);
4254     free(status_s);
4255     free(rc_s);
4256     return 0;
4257 }
4258 
4259 static int
send_smtp_trap(const char * node,const char * rsc,const char * task,int target_rc,int rc,int status,const char * desc)4260 send_smtp_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc,
4261                int status, const char *desc)
4262 {
4263 #if ENABLE_ESMTP
4264     smtp_session_t session;
4265     smtp_message_t message;
4266     auth_context_t authctx;
4267     struct sigaction sa;
4268 
4269     int len = 25; /* Note: Check extra padding on the Subject line below */
4270     int noauth = 1;
4271     int smtp_debug = LOG_DEBUG;
4272     char crm_mail_body[BODY_MAX];
4273     char *crm_mail_subject = NULL;
4274 
4275     memset(&sa, 0, sizeof(struct sigaction));
4276 
4277     if (node == NULL) {
4278         node = "-";
4279     }
4280     if (rsc == NULL) {
4281         rsc = "-";
4282     }
4283     if (desc == NULL) {
4284         desc = "-";
4285     }
4286 
4287     if (crm_mail_to == NULL) {
4288         return 1;
4289     }
4290 
4291     if (crm_mail_host == NULL) {
4292         crm_mail_host = "localhost:25";
4293     }
4294 
4295     if (crm_mail_prefix == NULL) {
4296         crm_mail_prefix = "Cluster notification";
4297     }
4298 
4299     crm_debug("Sending '%s' mail to %s via %s", crm_mail_prefix, crm_mail_to, crm_mail_host);
4300 
4301     len += strlen(crm_mail_prefix);
4302     len += strlen(task);
4303     len += strlen(rsc);
4304     len += strlen(node);
4305     len += strlen(desc);
4306     len++;
4307 
4308     crm_mail_subject = calloc(1, len);
4309     /* If you edit this line, ensure you allocate enough memory for it by altering 'len' above */
4310     snprintf(crm_mail_subject, len, "%s - %s event for %s on %s: %s\r\n", crm_mail_prefix, task,
4311              rsc, node, desc);
4312 
4313     len = 0;
4314     len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\n%s\r\n", crm_mail_prefix);
4315     len += snprintf(crm_mail_body + len, BODY_MAX - len, "====\r\n\r\n");
4316     if (rc == target_rc) {
4317         len += snprintf(crm_mail_body + len, BODY_MAX - len,
4318                         "Completed operation %s for resource %s on %s\r\n", task, rsc, node);
4319     } else {
4320         len += snprintf(crm_mail_body + len, BODY_MAX - len,
4321                         "Operation %s for resource %s on %s failed: %s\r\n", task, rsc, node, desc);
4322     }
4323 
4324     len += snprintf(crm_mail_body + len, BODY_MAX - len, "\r\nDetails:\r\n");
4325     len += snprintf(crm_mail_body + len, BODY_MAX - len,
4326                     "\toperation status: (%d) %s\r\n", status, services_lrm_status_str(status));
4327     if (status == PCMK_LRM_OP_DONE) {
4328         len += snprintf(crm_mail_body + len, BODY_MAX - len,
4329                         "\tscript returned: (%d) %s\r\n", rc, services_ocf_exitcode_str(rc));
4330         len += snprintf(crm_mail_body + len, BODY_MAX - len,
4331                         "\texpected return value: (%d) %s\r\n", target_rc,
4332                         services_ocf_exitcode_str(target_rc));
4333     }
4334 
4335     auth_client_init();
4336     session = smtp_create_session();
4337     message = smtp_add_message(session);
4338 
4339     smtp_starttls_enable(session, Starttls_ENABLED);
4340 
4341     sa.sa_handler = SIG_IGN;
4342     sigemptyset(&sa.sa_mask);
4343     sa.sa_flags = 0;
4344     sigaction(SIGPIPE, &sa, NULL);
4345 
4346     smtp_set_server(session, crm_mail_host);
4347 
4348     authctx = auth_create_context();
4349     auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
4350 
4351     smtp_set_eventcb(session, event_cb, NULL);
4352 
4353     /* Now tell libESMTP it can use the SMTP AUTH extension.
4354      */
4355     if (!noauth) {
4356         crm_debug("Adding authentication context");
4357         smtp_auth_set_context(session, authctx);
4358     }
4359 
4360     if (crm_mail_from == NULL) {
4361         struct utsname us;
4362         char auto_from[BODY_MAX];
4363 
4364         CRM_ASSERT(uname(&us) == 0);
4365         snprintf(auto_from, BODY_MAX, "crm_mon@%s", us.nodename);
4366         smtp_set_reverse_path(message, auto_from);
4367 
4368     } else {
4369         /* NULL is ok */
4370         smtp_set_reverse_path(message, crm_mail_from);
4371     }
4372 
4373     smtp_set_header(message, "To", NULL /*phrase */ , NULL /*addr */ ); /* "Phrase" <addr> */
4374     smtp_add_recipient(message, crm_mail_to);
4375 
4376     /* Set the Subject: header and override any subject line in the message headers. */
4377     smtp_set_header(message, "Subject", crm_mail_subject);
4378     smtp_set_header_option(message, "Subject", Hdr_OVERRIDE, 1);
4379 
4380     smtp_set_message_str(message, crm_mail_body);
4381     smtp_set_monitorcb(session, crm_smtp_debug, &smtp_debug, 1);
4382 
4383     if (smtp_start_session(session)) {
4384         char buf[128];
4385         int rc = smtp_errno();
4386 
4387         crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
4388 
4389     } else {
4390         char buf[128];
4391         int rc = smtp_errno();
4392         const smtp_status_t *smtp_status = smtp_message_transfer_status(message);
4393 
4394         if (rc != 0) {
4395             crm_err("SMTP server problem: %s (%d)", smtp_strerror(rc, buf, sizeof buf), rc);
4396         }
4397         crm_info("Send status: %d %s", smtp_status->code, crm_str(smtp_status->text));
4398         smtp_enumerate_recipients(message, print_recipient_status, NULL);
4399     }
4400 
4401     smtp_destroy_session(session);
4402     auth_destroy_context(authctx);
4403     auth_client_exit();
4404 #endif
4405     return 0;
4406 }
4407 
4408 static void
handle_rsc_op(xmlNode * xml,const char * node_id)4409 handle_rsc_op(xmlNode * xml, const char *node_id)
4410 {
4411     int rc = -1;
4412     int status = -1;
4413     int action = -1;
4414     int target_rc = -1;
4415     int transition_num = -1;
4416     gboolean notify = TRUE;
4417 
4418     char *rsc = NULL;
4419     char *task = NULL;
4420     const char *desc = NULL;
4421     const char *magic = NULL;
4422     const char *id = NULL;
4423     char *update_te_uuid = NULL;
4424     const char *node = NULL;
4425 
4426     xmlNode *n = xml;
4427     xmlNode * rsc_op = xml;
4428 
4429     if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) {
4430         xmlNode *cIter;
4431 
4432         for(cIter = xml->children; cIter; cIter = cIter->next) {
4433             handle_rsc_op(cIter, node_id);
4434         }
4435 
4436         return;
4437     }
4438 
4439     id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY);
4440     if (id == NULL) {
4441         /* Compatibility with <= 1.1.5 */
4442         id = ID(rsc_op);
4443     }
4444 
4445     magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC);
4446     if (magic == NULL) {
4447         /* non-change */
4448         return;
4449     }
4450 
4451     if (FALSE == decode_transition_magic(magic, &update_te_uuid, &transition_num, &action,
4452                                          &status, &rc, &target_rc)) {
4453         crm_err("Invalid event %s detected for %s", magic, id);
4454         return;
4455     }
4456 
4457     if (parse_op_key(id, &rsc, &task, NULL) == FALSE) {
4458         crm_err("Invalid event detected for %s", id);
4459         goto bail;
4460     }
4461 
4462     node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET);
4463 
4464     while (n != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) {
4465         n = n->parent;
4466     }
4467 
4468     if(node == NULL && n) {
4469         node = crm_element_value(n, XML_ATTR_UNAME);
4470     }
4471 
4472     if (node == NULL && n) {
4473         node = ID(n);
4474     }
4475 
4476     if (node == NULL) {
4477         node = node_id;
4478     }
4479 
4480     if (node == NULL) {
4481         crm_err("No node detected for event %s (%s)", magic, id);
4482         goto bail;
4483     }
4484 
4485     /* look up where we expected it to be? */
4486     desc = pcmk_strerror(pcmk_ok);
4487     if (status == PCMK_LRM_OP_DONE && target_rc == rc) {
4488         crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc);
4489         if (rc == PCMK_OCF_NOT_RUNNING) {
4490             notify = FALSE;
4491         }
4492 
4493     } else if (status == PCMK_LRM_OP_DONE) {
4494         desc = services_ocf_exitcode_str(rc);
4495         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
4496 
4497     } else {
4498         desc = services_lrm_status_str(status);
4499         crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc);
4500     }
4501 
4502     if (notify && snmp_target) {
4503         send_snmp_trap(node, rsc, task, target_rc, rc, status, desc);
4504     }
4505     if (notify && crm_mail_to) {
4506         send_smtp_trap(node, rsc, task, target_rc, rc, status, desc);
4507     }
4508     if (notify && external_agent) {
4509         send_custom_trap(node, rsc, task, target_rc, rc, status, desc);
4510     }
4511   bail:
4512     free(update_te_uuid);
4513     free(rsc);
4514     free(task);
4515 }
4516 
4517 static gboolean
mon_trigger_refresh(gpointer user_data)4518 mon_trigger_refresh(gpointer user_data)
4519 {
4520     mainloop_set_trigger(refresh_trigger);
4521     return FALSE;
4522 }
4523 
4524 #define NODE_PATT "/lrm[@id="
get_node_from_xpath(const char * xpath)4525 static char *get_node_from_xpath(const char *xpath)
4526 {
4527     char *nodeid = NULL;
4528     char *tmp = strstr(xpath, NODE_PATT);
4529 
4530     if(tmp) {
4531         tmp += strlen(NODE_PATT);
4532         tmp += 1;
4533 
4534         nodeid = strdup(tmp);
4535         tmp = strstr(nodeid, "\'");
4536         CRM_ASSERT(tmp);
4537         tmp[0] = 0;
4538     }
4539     return nodeid;
4540 }
4541 
crm_diff_update_v2(const char * event,xmlNode * msg)4542 static void crm_diff_update_v2(const char *event, xmlNode * msg)
4543 {
4544     xmlNode *change = NULL;
4545     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
4546 
4547     for (change = __xml_first_child(diff); change != NULL; change = __xml_next(change)) {
4548         const char *name = NULL;
4549         const char *op = crm_element_value(change, XML_DIFF_OP);
4550         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
4551         xmlNode *match = NULL;
4552         const char *node = NULL;
4553 
4554         if(op == NULL) {
4555             continue;
4556 
4557         } else if(strcmp(op, "create") == 0) {
4558             match = change->children;
4559 
4560         } else if(strcmp(op, "move") == 0) {
4561             continue;
4562 
4563         } else if(strcmp(op, "delete") == 0) {
4564             continue;
4565 
4566         } else if(strcmp(op, "modify") == 0) {
4567             match = first_named_child(change, XML_DIFF_RESULT);
4568             if(match) {
4569                 match = match->children;
4570             }
4571         }
4572 
4573         if(match) {
4574             name = (const char *)match->name;
4575         }
4576 
4577         crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name);
4578         if(xpath == NULL) {
4579             /* Version field, ignore */
4580 
4581         } else if(name == NULL) {
4582             crm_debug("No result for %s operation to %s", op, xpath);
4583             CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0);
4584 
4585         } else if(strcmp(name, XML_TAG_CIB) == 0) {
4586             xmlNode *state = NULL;
4587             xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS);
4588 
4589             for (state = __xml_first_child_element(status); state != NULL;
4590                  state = __xml_next_element(state)) {
4591 
4592                 node = crm_element_value(state, XML_ATTR_UNAME);
4593                 if (node == NULL) {
4594                     node = ID(state);
4595                 }
4596                 handle_rsc_op(state, node);
4597             }
4598 
4599         } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) {
4600             xmlNode *state = NULL;
4601 
4602             for (state = __xml_first_child_element(match); state != NULL;
4603                  state = __xml_next_element(state)) {
4604 
4605                 node = crm_element_value(state, XML_ATTR_UNAME);
4606                 if (node == NULL) {
4607                     node = ID(state);
4608                 }
4609                 handle_rsc_op(state, node);
4610             }
4611 
4612         } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) {
4613             node = crm_element_value(match, XML_ATTR_UNAME);
4614             if (node == NULL) {
4615                 node = ID(match);
4616             }
4617             handle_rsc_op(match, node);
4618 
4619         } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) {
4620             node = ID(match);
4621             handle_rsc_op(match, node);
4622 
4623         } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) {
4624             char *local_node = get_node_from_xpath(xpath);
4625 
4626             handle_rsc_op(match, local_node);
4627             free(local_node);
4628 
4629         } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) {
4630             char *local_node = get_node_from_xpath(xpath);
4631 
4632             handle_rsc_op(match, local_node);
4633             free(local_node);
4634 
4635         } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) {
4636             char *local_node = get_node_from_xpath(xpath);
4637 
4638             handle_rsc_op(match, local_node);
4639             free(local_node);
4640 
4641         } else {
4642             crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name);
4643         }
4644     }
4645 }
4646 
crm_diff_update_v1(const char * event,xmlNode * msg)4647 static void crm_diff_update_v1(const char *event, xmlNode * msg)
4648 {
4649     /* Process operation updates */
4650     xmlXPathObject *xpathObj = xpath_search(msg,
4651                                             "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
4652                                             "//" XML_LRM_TAG_RSC_OP);
4653     int lpc = 0, max = numXpathResults(xpathObj);
4654 
4655     for (lpc = 0; lpc < max; lpc++) {
4656         xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
4657 
4658         handle_rsc_op(rsc_op, NULL);
4659     }
4660     freeXpathObject(xpathObj);
4661 }
4662 
4663 void
crm_diff_update(const char * event,xmlNode * msg)4664 crm_diff_update(const char *event, xmlNode * msg)
4665 {
4666     int rc = -1;
4667     static bool stale = FALSE;
4668     gboolean cib_updated = FALSE;
4669     xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);
4670 
4671     print_dot();
4672 
4673     if (current_cib != NULL) {
4674         rc = xml_apply_patchset(current_cib, diff, TRUE);
4675 
4676         switch (rc) {
4677             case -pcmk_err_diff_resync:
4678             case -pcmk_err_diff_failed:
4679                 crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
4680                 free_xml(current_cib); current_cib = NULL;
4681                 break;
4682             case pcmk_ok:
4683                 cib_updated = TRUE;
4684                 break;
4685             default:
4686                 crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
4687                 free_xml(current_cib); current_cib = NULL;
4688         }
4689     }
4690 
4691     if (current_cib == NULL) {
4692         crm_trace("Re-requesting the full cib");
4693         cib->cmds->query(cib, NULL, &current_cib, cib_scope_local | cib_sync_call);
4694     }
4695 
4696     if (crm_mail_to || snmp_target || external_agent) {
4697         int format = 0;
4698         crm_element_value_int(diff, "format", &format);
4699         switch(format) {
4700             case 1:
4701                 crm_diff_update_v1(event, msg);
4702                 break;
4703             case 2:
4704                 crm_diff_update_v2(event, msg);
4705                 break;
4706             default:
4707                 crm_err("Unknown patch format: %d", format);
4708         }
4709     }
4710 
4711     if (current_cib == NULL) {
4712         if(!stale) {
4713             print_as("--- Stale data ---");
4714         }
4715         stale = TRUE;
4716         return;
4717     }
4718 
4719     stale = FALSE;
4720     kick_refresh(cib_updated);
4721 }
4722 
4723 gboolean
mon_refresh_display(gpointer user_data)4724 mon_refresh_display(gpointer user_data)
4725 {
4726     xmlNode *cib_copy = copy_xml(current_cib);
4727     stonith_history_t *stonith_history = NULL;
4728 
4729     last_refresh = time(NULL);
4730 
4731     if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
4732         if (cib) {
4733             cib->cmds->signoff(cib);
4734         }
4735         print_as("Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation));
4736         if (output_format == mon_output_console) {
4737             sleep(2);
4738         }
4739         clean_up(EX_USAGE);
4740         return FALSE;
4741     }
4742 
4743     /* get the stonith-history if there is evidence we need it
4744      */
4745     while (fence_history) {
4746         if (st != NULL) {
4747             if (st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120)) {
4748                 fprintf(stderr, "Critical: Unable to get stonith-history\n");
4749                 mon_cib_connection_destroy(NULL);
4750             } else {
4751                 stonith_history = sort_stonith_history(stonith_history);
4752                 if ((!fence_full_history) && (output_format != mon_output_xml)) {
4753                     stonith_history = reduce_stonith_history(stonith_history);
4754                 }
4755                 break; /* all other cases are errors */
4756             }
4757         } else {
4758             fprintf(stderr, "Critical: No stonith-API\n");
4759         }
4760         free_xml(cib_copy);
4761         print_as("Reading stonith-history failed");
4762         if (output_format == mon_output_console) {
4763             sleep(2);
4764         }
4765         return FALSE;
4766     }
4767 
4768     if (mon_data_set == NULL) {
4769         mon_data_set = pe_new_working_set();
4770         CRM_ASSERT(mon_data_set != NULL);
4771     }
4772     set_bit(mon_data_set->flags, pe_flag_no_compat);
4773 
4774     mon_data_set->input = cib_copy;
4775     cluster_status(mon_data_set);
4776 
4777     /* Unpack constraints if any section will need them
4778      * (tickets may be referenced in constraints but not granted yet,
4779      * and bans need negative location constraints) */
4780     if (show & (mon_show_bans | mon_show_tickets)) {
4781         xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS,
4782                                                    mon_data_set->input);
4783         unpack_constraints(cib_constraints, mon_data_set);
4784     }
4785 
4786     switch (output_format) {
4787         case mon_output_html:
4788         case mon_output_cgi:
4789             if (print_html_status(mon_data_set, output_filename, stonith_history) != 0) {
4790                 fprintf(stderr, "Critical: Unable to output html file\n");
4791                 clean_up(EX_USAGE);
4792                 return FALSE;
4793             }
4794             break;
4795 
4796         case mon_output_xml:
4797             print_xml_status(mon_data_set, stonith_history);
4798             break;
4799 
4800         case mon_output_monitor:
4801             print_simple_status(mon_data_set, stonith_history);
4802             if (has_warnings) {
4803                 clean_up(MON_STATUS_WARN);
4804                 return FALSE;
4805             }
4806             break;
4807 
4808         case mon_output_plain:
4809         case mon_output_console:
4810             print_status(mon_data_set, stonith_history);
4811             break;
4812 
4813         case mon_output_none:
4814             break;
4815     }
4816 
4817     stonith_history_free(stonith_history);
4818     stonith_history = NULL;
4819     pe_reset_working_set(mon_data_set);
4820     return TRUE;
4821 }
4822 
4823 void
mon_st_callback_event(stonith_t * st,stonith_event_t * e)4824 mon_st_callback_event(stonith_t * st, stonith_event_t * e)
4825 {
4826     if (st->state == stonith_disconnected) {
4827         /* disconnect cib as well and have everything reconnect */
4828         mon_cib_connection_destroy(NULL);
4829     } else {
4830         char *desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
4831                                  e->operation, e->origin, e->target, pcmk_strerror(e->result),
4832                                  e->id);
4833 
4834         if (snmp_target) {
4835             send_snmp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4836         }
4837         if (crm_mail_to) {
4838             send_smtp_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4839         }
4840         if (external_agent) {
4841             send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc);
4842         }
4843         free(desc);
4844     }
4845 }
4846 
kick_refresh(gboolean data_updated)4847 void kick_refresh(gboolean data_updated)
4848 {
4849     static int updates = 0;
4850     long now = time(NULL);
4851 
4852     if (data_updated) {
4853         updates++;
4854     }
4855 
4856     if(refresh_timer == NULL) {
4857         refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL);
4858     }
4859 
4860     /* Refresh
4861      * - immediately if the last update was more than 5s ago
4862      * - every 10 cib-updates
4863      * - at most 2s after the last update
4864      */
4865     if ((now - last_refresh) > (reconnect_msec / 1000)) {
4866         mainloop_set_trigger(refresh_trigger);
4867         mainloop_timer_stop(refresh_timer);
4868         updates = 0;
4869 
4870     } else if(updates >= 10) {
4871         mainloop_set_trigger(refresh_trigger);
4872         mainloop_timer_stop(refresh_timer);
4873         updates = 0;
4874 
4875     } else {
4876         mainloop_timer_start(refresh_timer);
4877     }
4878 }
4879 
4880 void
mon_st_callback_display(stonith_t * st,stonith_event_t * e)4881 mon_st_callback_display(stonith_t * st, stonith_event_t * e)
4882 {
4883     if (st->state == stonith_disconnected) {
4884         /* disconnect cib as well and have everything reconnect */
4885         mon_cib_connection_destroy(NULL);
4886     } else {
4887         print_dot();
4888         kick_refresh(TRUE);
4889     }
4890 }
4891 
4892 void
clean_up_connections(void)4893 clean_up_connections(void)
4894 {
4895     if (cib != NULL) {
4896         cib->cmds->signoff(cib);
4897         cib_delete(cib);
4898         cib = NULL;
4899     }
4900 
4901     if (st != NULL) {
4902         if (st->state != stonith_disconnected) {
4903             st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT);
4904             st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE);
4905             st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY);
4906             st->cmds->disconnect(st);
4907         }
4908         stonith_api_delete(st);
4909         st = NULL;
4910     }
4911 }
4912 
4913 /*
4914  * De-init ncurses, disconnect from the CIB manager, disconnect fencing,
4915  * deallocate memory and show usage-message if requested.
4916  *
4917  * We don't actually return, but nominally returning crm_exit_t allows a usage
4918  * like "return clean_up(exit_code);" which helps static analysis understand the
4919  * code flow.
4920  */
4921 static int
clean_up(int exit_code)4922 clean_up(int exit_code)
4923 {
4924 #if ENABLE_SNMP
4925     netsnmp_session *session = crm_snmp_init(NULL, NULL);
4926 
4927     if (session) {
4928         snmp_close(session);
4929         snmp_shutdown("snmpapp");
4930     }
4931 #endif
4932 
4933 #if CURSES_ENABLED
4934     if (curses_console_initialized) {
4935         output_format = mon_output_plain;
4936         echo();
4937         nocbreak();
4938         endwin();
4939         curses_console_initialized = FALSE;
4940     }
4941 #endif
4942 
4943     clean_up_connections();
4944 
4945     free(output_filename);
4946     free(pid_file);
4947 
4948     pe_free_working_set(mon_data_set);
4949     mon_data_set = NULL;
4950 
4951     if (exit_code == EX_USAGE) {
4952         if (output_format == mon_output_cgi) {
4953             fprintf(stdout, "Content-Type: text/plain\n"
4954                             "Status: 500\n\n");
4955         } else {
4956             return crm_help('?', EX_USAGE);
4957         }
4958     }
4959     return crm_exit(exit_code);
4960 }
4961