1    	/*
2    	 * Copyright 2019-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdarg.h>
13   	#include <stdbool.h>
14   	#include <stdint.h>
15   	#include <stdlib.h>
16   	
17   	#include <crm/crm.h>
18   	#include <crm/common/output.h>
19   	#include <crm/stonith-ng.h>
20   	#include <crm/fencing/internal.h>   // stonith__history_description()
21   	#include <crm/pengine/internal.h>
22   	#include <glib.h>
23   	#include <pacemaker-internal.h>
24   	
25   	#include "crm_mon.h"
26   	
27   	#if PCMK__ENABLE_CURSES
28   	
29   	typedef struct {
30   	    unsigned int len;
31   	    char *singular_noun;
32   	    char *plural_noun;
33   	} curses_list_data_t;
34   	
35   	typedef struct {
36   	    GQueue *parent_q;
37   	} private_data_t;
38   	
39   	static void
40   	free_list_data(gpointer data) {
41   	    curses_list_data_t *list_data = data;
42   	
43   	    free(list_data->singular_noun);
44   	    free(list_data->plural_noun);
45   	    free(list_data);
46   	}
47   	
48   	static void
49   	curses_free_priv(pcmk__output_t *out) {
50   	    private_data_t *priv = NULL;
51   	
(1) Event path: Condition "out == NULL", taking false branch.
(2) Event path: Condition "out->priv == NULL", taking false branch.
52   	    if (out == NULL || out->priv == NULL) {
53   	        return;
54   	    }
55   	
56   	    priv = out->priv;
57   	
58   	    g_queue_free_full(priv->parent_q, free_list_data);
CID (unavailable; MK=3c5157a730494726918f797285b54c7e) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(3) Event assign_union_field: The union field "in" of "_pp" is written.
(4) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
59   	    g_clear_pointer(&out->priv, free);
60   	}
61   	
62   	static bool
63   	curses_init(pcmk__output_t *out) {
64   	    private_data_t *priv = NULL;
65   	
66   	    pcmk__assert(out != NULL);
67   	
68   	    /* If curses_init was previously called on this output struct, just return. */
69   	    if (out->priv != NULL) {
70   	        return true;
71   	    } else {
72   	        out->priv = calloc(1, sizeof(private_data_t));
73   	        if (out->priv == NULL) {
74   	            return false;
75   	        }
76   	
77   	        priv = out->priv;
78   	    }
79   	
80   	    priv->parent_q = g_queue_new();
81   	
82   	    initscr();
83   	    cbreak();
84   	    noecho();
85   	
86   	    return true;
87   	}
88   	
89   	static void
90   	curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
91   	    pcmk__assert(out != NULL);
92   	
93   	    echo();
94   	    nocbreak();
95   	    endwin();
96   	}
97   	
98   	static void
99   	curses_reset(pcmk__output_t *out) {
100  	    pcmk__assert(out != NULL);
101  	
102  	    curses_free_priv(out);
103  	    curses_init(out);
104  	}
105  	
106  	static void
107  	curses_subprocess_output(pcmk__output_t *out, int exit_status,
108  	                         const char *proc_stdout, const char *proc_stderr) {
109  	    pcmk__assert(out != NULL);
110  	
111  	    if (proc_stdout != NULL) {
112  	        printw("%s\n", proc_stdout);
113  	    }
114  	
115  	    if (proc_stderr != NULL) {
116  	        printw("%s\n", proc_stderr);
117  	    }
118  	
119  	    clrtoeol();
120  	    refresh();
121  	}
122  	
123  	/* curses_version is defined in curses.h, so we can't use that name here.
124  	 * This function is empty because we create a text object instead of a console
125  	 * object if version is requested, so this is never called.
126  	 */
127  	static void
128  	curses_ver(pcmk__output_t *out)
129  	{
130  	    pcmk__assert(out != NULL);
131  	}
132  	
133  	G_GNUC_PRINTF(2, 3)
134  	static void
135  	curses_error(pcmk__output_t *out, const char *format, ...) {
136  	    va_list ap;
137  	
138  	    pcmk__assert(out != NULL);
139  	
140  	    /* Informational output does not get indented, to separate it from other
141  	     * potentially indented list output.
142  	     */
143  	    va_start(ap, format);
144  	    vw_printw(stdscr, format, ap);
145  	    va_end(ap);
146  	
147  	    /* Add a newline. */
148  	    addch('\n');
149  	
150  	    clrtoeol();
151  	    refresh();
152  	    sleep(2);
153  	}
154  	
155  	G_GNUC_PRINTF(2, 3)
156  	static int
157  	curses_info(pcmk__output_t *out, const char *format, ...) {
158  	    va_list ap;
159  	
160  	    pcmk__assert(out != NULL);
161  	
162  	    if (out->is_quiet(out)) {
163  	        return pcmk_rc_no_output;
164  	    }
165  	
166  	    /* Informational output does not get indented, to separate it from other
167  	     * potentially indented list output.
168  	     */
169  	    va_start(ap, format);
170  	    vw_printw(stdscr, format, ap);
171  	    va_end(ap);
172  	
173  	    /* Add a newline. */
174  	    addch('\n');
175  	
176  	    clrtoeol();
177  	    refresh();
178  	    return pcmk_rc_ok;
179  	}
180  	
181  	static void
182  	curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
183  	    pcmk__assert(out != NULL);
184  	    curses_indented_printf(out, "%s", buf);
185  	}
186  	
187  	G_GNUC_PRINTF(4, 5)
188  	static void
189  	curses_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
190  	                  const char *format, ...) {
191  	    private_data_t *priv = NULL;
192  	    curses_list_data_t *new_list = NULL;
193  	    va_list ap;
194  	
195  	    pcmk__assert((out != NULL) && (out->priv != NULL));
196  	    priv = out->priv;
197  	
198  	    /* Empty formats can be used to create a new level of indentation, but without
199  	     * displaying some sort of list header.  In that case we need to not do any of
200  	     * this stuff. vw_printw will act weird if told to print a NULL.
201  	     */
202  	    if (format != NULL) {
203  	        va_start(ap, format);
204  	
205  	        curses_indented_vprintf(out, format, ap);
206  	        printw(":\n");
207  	
208  	        va_end(ap);
209  	    }
210  	
211  	    new_list = pcmk__assert_alloc(1, sizeof(curses_list_data_t));
212  	    new_list->len = 0;
213  	    new_list->singular_noun = pcmk__str_copy(singular_noun);
214  	    new_list->plural_noun = pcmk__str_copy(plural_noun);
215  	
216  	    g_queue_push_tail(priv->parent_q, new_list);
217  	}
218  	
219  	G_GNUC_PRINTF(3, 4)
220  	static void
221  	curses_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
222  	    va_list ap;
223  	
224  	    pcmk__assert(out != NULL);
225  	
226  	    va_start(ap, format);
227  	
228  	    if (id != NULL) {
229  	        curses_indented_printf(out, "%s: ", id);
230  	        vw_printw(stdscr, format, ap);
231  	    } else {
232  	        curses_indented_vprintf(out, format, ap);
233  	    }
234  	
235  	    addch('\n');
236  	    va_end(ap);
237  	
238  	    out->increment_list(out);
239  	}
240  	
241  	static void
242  	curses_increment_list(pcmk__output_t *out) {
243  	    private_data_t *priv = NULL;
244  	    gpointer tail;
245  	
246  	    pcmk__assert((out != NULL) && (out->priv != NULL));
247  	    priv = out->priv;
248  	
249  	    tail = g_queue_peek_tail(priv->parent_q);
250  	    pcmk__assert(tail != NULL);
251  	    ((curses_list_data_t *) tail)->len++;
252  	}
253  	
254  	static void
255  	curses_end_list(pcmk__output_t *out) {
256  	    private_data_t *priv = NULL;
257  	    curses_list_data_t *node = NULL;
258  	
259  	    pcmk__assert((out != NULL) && (out->priv != NULL));
260  	    priv = out->priv;
261  	
262  	    node = g_queue_pop_tail(priv->parent_q);
263  	
264  	    if (node->singular_noun != NULL && node->plural_noun != NULL) {
265  	        if (node->len == 1) {
266  	            curses_indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
267  	        } else {
268  	            curses_indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
269  	        }
270  	    }
271  	
272  	    free_list_data(node);
273  	}
274  	
275  	static bool
276  	curses_is_quiet(pcmk__output_t *out) {
277  	    pcmk__assert(out != NULL);
278  	    return out->quiet;
279  	}
280  	
281  	static void
282  	curses_spacer(pcmk__output_t *out) {
283  	    pcmk__assert(out != NULL);
284  	    addch('\n');
285  	}
286  	
287  	static void
288  	curses_progress(pcmk__output_t *out, bool end) {
289  	    pcmk__assert(out != NULL);
290  	
291  	    if (end) {
292  	        printw(".\n");
293  	    } else {
294  	        addch('.');
295  	    }
296  	}
297  	
298  	static void
299  	curses_prompt(const char *prompt, bool do_echo, char **dest)
300  	{
301  	    int rc = OK;
302  	
303  	    pcmk__assert((prompt != NULL) && (dest != NULL));
304  	
305  	    /* This is backwards from the text version of this function on purpose.  We
306  	     * disable echo by default in curses_init, so we need to enable it here if
307  	     * asked for.
308  	     */
309  	    if (do_echo) {
310  	        rc = echo();
311  	    }
312  	
313  	    if (rc == OK) {
314  	        printw("%s: ", prompt);
315  	
316  	        if (*dest != NULL) {
317  	            free(*dest);
318  	        }
319  	
320  	        *dest = pcmk__assert_alloc(1024, sizeof(char));
321  	        /* On older systems, scanw is defined as taking a char * for its first argument,
322  	         * while newer systems rightly want a const char *.  Accomodate both here due
323  	         * to building with -Werror.
324  	         */
325  	        rc = scanw((NCURSES_CONST char *) "%1023s", *dest);
326  	        addch('\n');
327  	    }
328  	
329  	    if (rc < 1) {
330  	        g_clear_pointer(dest, free);
331  	    }
332  	
333  	    if (do_echo) {
334  	        noecho();
335  	    }
336  	}
337  	
338  	pcmk__output_t *
339  	crm_mon_mk_curses_output(char **argv) {
340  	    pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
341  	
342  	    if (retval == NULL) {
343  	        return NULL;
344  	    }
345  	
346  	    retval->fmt_name = "console";
347  	    retval->request = pcmk__quote_cmdline(argv);
348  	
349  	    retval->init = curses_init;
350  	    retval->free_priv = curses_free_priv;
351  	    retval->finish = curses_finish;
352  	    retval->reset = curses_reset;
353  	
354  	    retval->register_message = pcmk__register_message;
355  	    retval->message = pcmk__call_message;
356  	
357  	    retval->subprocess_output = curses_subprocess_output;
358  	    retval->version = curses_ver;
359  	    retval->err = curses_error;
360  	    retval->info = curses_info;
361  	    retval->transient = curses_info;
362  	    retval->output_xml = curses_output_xml;
363  	
364  	    retval->begin_list = curses_begin_list;
365  	    retval->list_item = curses_list_item;
366  	    retval->increment_list = curses_increment_list;
367  	    retval->end_list = curses_end_list;
368  	
369  	    retval->is_quiet = curses_is_quiet;
370  	    retval->spacer = curses_spacer;
371  	    retval->progress = curses_progress;
372  	    retval->prompt = curses_prompt;
373  	
374  	    return retval;
375  	}
376  	
377  	G_GNUC_PRINTF(2, 0)
378  	void
379  	curses_formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
380  	    vw_printw(stdscr, format, args);
381  	
382  	    clrtoeol();
383  	    refresh();
384  	}
385  	
386  	G_GNUC_PRINTF(2, 3)
387  	void
388  	curses_formatted_printf(pcmk__output_t *out, const char *format, ...) {
389  	    va_list ap;
390  	
391  	    va_start(ap, format);
392  	    curses_formatted_vprintf(out, format, ap);
393  	    va_end(ap);
394  	}
395  	
396  	G_GNUC_PRINTF(2, 0)
397  	void
398  	curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
399  	    int level = 0;
400  	    private_data_t *priv = NULL;
401  	
402  	    pcmk__assert((out != NULL) && (out->priv != NULL));
403  	
404  	    priv = out->priv;
405  	
406  	    level = g_queue_get_length(priv->parent_q);
407  	
408  	    for (int i = 0; i < level; i++) {
409  	        printw("  ");
410  	    }
411  	
412  	    if (level > 0) {
413  	        printw("* ");
414  	    }
415  	
416  	    curses_formatted_vprintf(out, format, args);
417  	}
418  	
419  	G_GNUC_PRINTF(2, 3)
420  	void
421  	curses_indented_printf(pcmk__output_t *out, const char *format, ...) {
422  	    va_list ap;
423  	
424  	    va_start(ap, format);
425  	    curses_indented_vprintf(out, format, ap);
426  	    va_end(ap);
427  	}
428  	
429  	PCMK__OUTPUT_ARGS("maint-mode", "uint64_t")
430  	static int
431  	cluster_maint_mode_console(pcmk__output_t *out, va_list args) {
432  	    uint64_t flags = va_arg(args, uint64_t);
433  	
434  	    if (pcmk__is_set(flags, pcmk__sched_in_maintenance)) {
435  	        curses_formatted_printf(out, "\n              *** Resource management is DISABLED ***\n");
436  	        curses_formatted_printf(out, "  The cluster will not attempt to start, stop or recover services\n");
437  	        return pcmk_rc_ok;
438  	    } else if (pcmk__is_set(flags, pcmk__sched_stop_all)) {
439  	        curses_formatted_printf(out, "\n    *** Resource management is DISABLED ***\n");
440  	        curses_formatted_printf(out, "  The cluster will keep all resources stopped\n");
441  	        return pcmk_rc_ok;
442  	    } else {
443  	        return pcmk_rc_no_output;
444  	    }
445  	}
446  	
447  	PCMK__OUTPUT_ARGS("cluster-status", "pcmk_scheduler_t *",
448  	                  "enum pcmk_pacemakerd_state", "crm_exit_t",
449  	                  "stonith_history_t *", "enum pcmk__fence_history", "uint32_t",
450  	                  "uint32_t", "const char *", "GList *", "GList *")
451  	static int
452  	cluster_status_console(pcmk__output_t *out, va_list args) {
453  	    int rc = pcmk_rc_no_output;
454  	
455  	    clear();
456  	    rc = pcmk__cluster_status_text(out, args);
457  	    refresh();
458  	    return rc;
459  	}
460  	
461  	PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
462  	                  "const char *", "uint32_t")
463  	static int
464  	stonith_event_console(pcmk__output_t *out, va_list args)
465  	{
466  	    stonith_history_t *event = va_arg(args, stonith_history_t *);
467  	    bool full_history = va_arg(args, int);
468  	    bool completed_only G_GNUC_UNUSED = va_arg(args, int);
469  	    const char *succeeded = va_arg(args, const char *);
470  	    uint32_t show_opts = va_arg(args, uint32_t);
471  	
472  	    gchar *desc = stonith__history_description(event, full_history, succeeded,
473  	                                               show_opts);
474  	
475  	
476  	    curses_indented_printf(out, "%s\n", desc);
477  	    g_free(desc);
478  	    return pcmk_rc_ok;
479  	}
480  	
481  	static pcmk__message_entry_t fmt_functions[] = {
482  	    { "cluster-status", "console", cluster_status_console },
483  	    { "maint-mode", "console", cluster_maint_mode_console },
484  	    { "stonith-event", "console", stonith_event_console },
485  	
486  	    { NULL, NULL, NULL }
487  	};
488  	
489  	#endif
490  	
491  	void
492  	crm_mon_register_messages(pcmk__output_t *out) {
493  	#if PCMK__ENABLE_CURSES
494  	    pcmk__register_messages(out, fmt_functions);
495  	#endif
496  	}
497