root/tools/crm_mon_curses.c

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

DEFINITIONS

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

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

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