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

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