diff options
Diffstat (limited to 'tools/crm_mon_curses.c')
-rw-r--r-- | tools/crm_mon_curses.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c new file mode 100644 index 0000000..769c7c9 --- /dev/null +++ b/tools/crm_mon_curses.c @@ -0,0 +1,490 @@ +/* + * Copyright 2019-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdlib.h> +#include <crm/crm.h> +#include <crm/common/output.h> +#include <crm/common/cmdline_internal.h> +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> +#include <crm/pengine/internal.h> +#include <glib.h> +#include <pacemaker-internal.h> + +#include "crm_mon.h" + +#if CURSES_ENABLED + +GOptionEntry crm_mon_curses_output_entries[] = { + { NULL } +}; + +typedef struct curses_list_data_s { + unsigned int len; + char *singular_noun; + char *plural_noun; +} curses_list_data_t; + +typedef struct private_data_s { + GQueue *parent_q; +} private_data_t; + +static void +curses_free_priv(pcmk__output_t *out) { + private_data_t *priv = NULL; + + if (out == NULL || out->priv == NULL) { + return; + } + + priv = out->priv; + + g_queue_free(priv->parent_q); + free(priv); + out->priv = NULL; +} + +static bool +curses_init(pcmk__output_t *out) { + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL); + + /* If curses_init was previously called on this output struct, just return. */ + if (out->priv != NULL) { + return true; + } else { + out->priv = calloc(1, sizeof(private_data_t)); + if (out->priv == NULL) { + return false; + } + + priv = out->priv; + } + + priv->parent_q = g_queue_new(); + + initscr(); + cbreak(); + noecho(); + + return true; +} + +static void +curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { + CRM_ASSERT(out != NULL); + + echo(); + nocbreak(); + endwin(); +} + +static void +curses_reset(pcmk__output_t *out) { + CRM_ASSERT(out != NULL); + + curses_free_priv(out); + curses_init(out); +} + +static void +curses_subprocess_output(pcmk__output_t *out, int exit_status, + const char *proc_stdout, const char *proc_stderr) { + CRM_ASSERT(out != NULL); + + if (proc_stdout != NULL) { + printw("%s\n", proc_stdout); + } + + if (proc_stderr != NULL) { + printw("%s\n", proc_stderr); + } + + clrtoeol(); + refresh(); +} + +/* curses_version is defined in curses.h, so we can't use that name here. + * This function is empty because we create a text object instead of a console + * object if version is requested, so this is never called. + */ +static void +curses_ver(pcmk__output_t *out, bool extended) { + CRM_ASSERT(out != NULL); +} + +G_GNUC_PRINTF(2, 3) +static void +curses_error(pcmk__output_t *out, const char *format, ...) { + va_list ap; + + CRM_ASSERT(out != NULL); + + /* Informational output does not get indented, to separate it from other + * potentially indented list output. + */ + va_start(ap, format); + vw_printw(stdscr, format, ap); + va_end(ap); + + /* Add a newline. */ + addch('\n'); + + clrtoeol(); + refresh(); + sleep(2); +} + +G_GNUC_PRINTF(2, 3) +static int +curses_info(pcmk__output_t *out, const char *format, ...) { + va_list ap; + + CRM_ASSERT(out != NULL); + + if (out->is_quiet(out)) { + return pcmk_rc_no_output; + } + + /* Informational output does not get indented, to separate it from other + * potentially indented list output. + */ + va_start(ap, format); + vw_printw(stdscr, format, ap); + va_end(ap); + + /* Add a newline. */ + addch('\n'); + + clrtoeol(); + refresh(); + return pcmk_rc_ok; +} + +static void +curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) { + CRM_ASSERT(out != NULL); + curses_indented_printf(out, "%s", buf); +} + +G_GNUC_PRINTF(4, 5) +static void +curses_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, + const char *format, ...) { + private_data_t *priv = NULL; + curses_list_data_t *new_list = NULL; + va_list ap; + + CRM_ASSERT(out != NULL && out->priv != NULL); + priv = out->priv; + + /* Empty formats can be used to create a new level of indentation, but without + * displaying some sort of list header. In that case we need to not do any of + * this stuff. vw_printw will act weird if told to print a NULL. + */ + if (format != NULL) { + va_start(ap, format); + + curses_indented_vprintf(out, format, ap); + printw(":\n"); + + va_end(ap); + } + + new_list = calloc(1, sizeof(curses_list_data_t)); + new_list->len = 0; + pcmk__str_update(&new_list->singular_noun, singular_noun); + pcmk__str_update(&new_list->plural_noun, plural_noun); + + g_queue_push_tail(priv->parent_q, new_list); +} + +G_GNUC_PRINTF(3, 4) +static void +curses_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { + va_list ap; + + CRM_ASSERT(out != NULL); + + va_start(ap, format); + + if (id != NULL) { + curses_indented_printf(out, "%s: ", id); + vw_printw(stdscr, format, ap); + } else { + curses_indented_vprintf(out, format, ap); + } + + addch('\n'); + va_end(ap); + + out->increment_list(out); +} + +static void +curses_increment_list(pcmk__output_t *out) { + private_data_t *priv = NULL; + gpointer tail; + + CRM_ASSERT(out != NULL && out->priv != NULL); + priv = out->priv; + + tail = g_queue_peek_tail(priv->parent_q); + CRM_ASSERT(tail != NULL); + ((curses_list_data_t *) tail)->len++; +} + +static void +curses_end_list(pcmk__output_t *out) { + private_data_t *priv = NULL; + curses_list_data_t *node = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + priv = out->priv; + + node = g_queue_pop_tail(priv->parent_q); + + if (node->singular_noun != NULL && node->plural_noun != NULL) { + if (node->len == 1) { + curses_indented_printf(out, "%d %s found\n", node->len, node->singular_noun); + } else { + curses_indented_printf(out, "%d %s found\n", node->len, node->plural_noun); + } + } + + free(node); +} + +static bool +curses_is_quiet(pcmk__output_t *out) { + CRM_ASSERT(out != NULL); + return out->quiet; +} + +static void +curses_spacer(pcmk__output_t *out) { + CRM_ASSERT(out != NULL); + addch('\n'); +} + +static void +curses_progress(pcmk__output_t *out, bool end) { + CRM_ASSERT(out != NULL); + + if (end) { + printw(".\n"); + } else { + addch('.'); + } +} + +static void +curses_prompt(const char *prompt, bool do_echo, char **dest) +{ + int rc = OK; + + CRM_ASSERT(prompt != NULL && dest != NULL); + + /* This is backwards from the text version of this function on purpose. We + * disable echo by default in curses_init, so we need to enable it here if + * asked for. + */ + if (do_echo) { + rc = echo(); + } + + if (rc == OK) { + printw("%s: ", prompt); + + if (*dest != NULL) { + free(*dest); + } + + *dest = calloc(1, 1024); + /* On older systems, scanw is defined as taking a char * for its first argument, + * while newer systems rightly want a const char *. Accomodate both here due + * to building with -Werror. + */ + rc = scanw((NCURSES_CONST char *) "%1023s", *dest); + addch('\n'); + } + + if (rc < 1) { + free(*dest); + *dest = NULL; + } + + if (do_echo) { + noecho(); + } +} + +pcmk__output_t * +crm_mon_mk_curses_output(char **argv) { + pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); + + if (retval == NULL) { + return NULL; + } + + retval->fmt_name = "console"; + retval->request = pcmk__quote_cmdline(argv); + + retval->init = curses_init; + retval->free_priv = curses_free_priv; + retval->finish = curses_finish; + retval->reset = curses_reset; + + retval->register_message = pcmk__register_message; + retval->message = pcmk__call_message; + + retval->subprocess_output = curses_subprocess_output; + retval->version = curses_ver; + retval->err = curses_error; + retval->info = curses_info; + retval->transient = curses_info; + retval->output_xml = curses_output_xml; + + retval->begin_list = curses_begin_list; + retval->list_item = curses_list_item; + retval->increment_list = curses_increment_list; + retval->end_list = curses_end_list; + + retval->is_quiet = curses_is_quiet; + retval->spacer = curses_spacer; + retval->progress = curses_progress; + retval->prompt = curses_prompt; + + return retval; +} + +G_GNUC_PRINTF(2, 0) +void +curses_formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) { + vw_printw(stdscr, format, args); + + clrtoeol(); + refresh(); +} + +G_GNUC_PRINTF(2, 3) +void +curses_formatted_printf(pcmk__output_t *out, const char *format, ...) { + va_list ap; + + va_start(ap, format); + curses_formatted_vprintf(out, format, ap); + va_end(ap); +} + +G_GNUC_PRINTF(2, 0) +void +curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { + int level = 0; + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + + priv = out->priv; + + level = g_queue_get_length(priv->parent_q); + + for (int i = 0; i < level; i++) { + printw(" "); + } + + if (level > 0) { + printw("* "); + } + + curses_formatted_vprintf(out, format, args); +} + +G_GNUC_PRINTF(2, 3) +void +curses_indented_printf(pcmk__output_t *out, const char *format, ...) { + va_list ap; + + va_start(ap, format); + curses_indented_vprintf(out, format, ap); + va_end(ap); +} + +PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int") +static int +cluster_maint_mode_console(pcmk__output_t *out, va_list args) { + unsigned long long flags = va_arg(args, unsigned long long); + + if (pcmk_is_set(flags, pe_flag_maintenance_mode)) { + curses_formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); + curses_formatted_printf(out, " The cluster will not attempt to start, stop or recover services\n"); + return pcmk_rc_ok; + } else if (pcmk_is_set(flags, pe_flag_stop_everything)) { + curses_formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); + curses_formatted_printf(out, " The cluster will keep all resources stopped\n"); + return pcmk_rc_ok; + } else { + return pcmk_rc_no_output; + } +} + +PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", + "enum pcmk_pacemakerd_state", "crm_exit_t", + "stonith_history_t *", "enum pcmk__fence_history", "uint32_t", + "uint32_t", "const char *", "GList *", "GList *") +static int +cluster_status_console(pcmk__output_t *out, va_list args) { + int rc = pcmk_rc_no_output; + + clear(); + rc = pcmk__cluster_status_text(out, args); + refresh(); + return rc; +} + +PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool", + "const char *", "uint32_t") +static int +stonith_event_console(pcmk__output_t *out, va_list args) +{ + stonith_history_t *event = va_arg(args, stonith_history_t *); + bool full_history = va_arg(args, int); + bool completed_only G_GNUC_UNUSED = va_arg(args, int); + const char *succeeded = va_arg(args, const char *); + uint32_t show_opts = va_arg(args, uint32_t); + + gchar *desc = stonith__history_description(event, full_history, succeeded, + show_opts); + + + curses_indented_printf(out, "%s\n", desc); + g_free(desc); + return pcmk_rc_ok; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "cluster-status", "console", cluster_status_console }, + { "maint-mode", "console", cluster_maint_mode_console }, + { "stonith-event", "console", stonith_event_console }, + + { NULL, NULL, NULL } +}; + +#endif + +void +crm_mon_register_messages(pcmk__output_t *out) { +#if CURSES_ENABLED + pcmk__register_messages(out, fmt_functions); +#endif +} |