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

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