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

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

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