root/tools/crm_mon_curses.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. free_list_data
  2. curses_free_priv
  3. curses_init
  4. curses_finish
  5. curses_reset
  6. curses_subprocess_output
  7. curses_ver
  8. G_GNUC_PRINTF
  9. G_GNUC_PRINTF
  10. curses_output_xml
  11. G_GNUC_PRINTF
  12. G_GNUC_PRINTF
  13. curses_increment_list
  14. curses_end_list
  15. curses_is_quiet
  16. curses_spacer
  17. curses_progress
  18. curses_prompt
  19. crm_mon_mk_curses_output
  20. G_GNUC_PRINTF
  21. G_GNUC_PRINTF
  22. G_GNUC_PRINTF
  23. G_GNUC_PRINTF
  24. PCMK__OUTPUT_ARGS
  25. PCMK__OUTPUT_ARGS
  26. PCMK__OUTPUT_ARGS
  27. crm_mon_register_messages

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

/* [previous][next][first][last][top][bottom][index][help] */