root/lib/common/output_text.c

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

DEFINITIONS

This source file includes following definitions.
  1. free_list_data
  2. text_free_priv
  3. text_init
  4. text_finish
  5. text_reset
  6. text_subprocess_output
  7. text_version
  8. G_GNUC_PRINTF
  9. G_GNUC_PRINTF
  10. G_GNUC_PRINTF
  11. text_output_xml
  12. G_GNUC_PRINTF
  13. G_GNUC_PRINTF
  14. text_increment_list
  15. text_end_list
  16. text_is_quiet
  17. text_spacer
  18. text_progress
  19. pcmk__mk_text_output
  20. pcmk__output_text_get_fancy
  21. pcmk__output_text_set_fancy
  22. G_GNUC_PRINTF
  23. G_GNUC_PRINTF
  24. G_GNUC_PRINTF
  25. G_GNUC_PRINTF
  26. pcmk__text_prompt

   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 <crm/common/cmdline_internal.h>
  12 
  13 #include <stdarg.h>
  14 #include <stdlib.h>
  15 #include <glib.h>
  16 #include <termios.h>
  17 
  18 #include "crmcommon_private.h"
  19 
  20 // @COMPAT Drop at 3.0.0
  21 static gboolean fancy = FALSE;
  22 
  23 // @COMPAT Drop at 3.0.0
  24 GOptionEntry pcmk__text_output_entries[] = {
  25     { "text-fancy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &fancy,
  26       "Use more highly formatted output (requires --output-as=text)",
  27       NULL },
  28 
  29     { NULL }
  30 };
  31 
  32 typedef struct text_list_data_s {
  33     unsigned int len;
  34     char *singular_noun;
  35     char *plural_noun;
  36 } text_list_data_t;
  37 
  38 typedef struct private_data_s {
  39     GQueue *parent_q;
  40     bool fancy;
  41 } private_data_t;
  42 
  43 static void
  44 free_list_data(gpointer data) {
     /* [previous][next][first][last][top][bottom][index][help] */
  45     text_list_data_t *list_data = data;
  46 
  47     free(list_data->singular_noun);
  48     free(list_data->plural_noun);
  49 }
  50 
  51 static void
  52 text_free_priv(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  53     private_data_t *priv = NULL;
  54 
  55     if (out == NULL || out->priv == NULL) {
  56         return;
  57     }
  58 
  59     priv = out->priv;
  60 
  61     g_queue_free_full(priv->parent_q, free_list_data);
  62     free(priv);
  63     out->priv = NULL;
  64 }
  65 
  66 static bool
  67 text_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  68     private_data_t *priv = NULL;
  69 
  70     CRM_ASSERT(out != NULL);
  71 
  72     /* If text_init was previously called on this output struct, just return. */
  73     if (out->priv != NULL) {
  74         return true;
  75     }
  76 
  77     out->priv = calloc(1, sizeof(private_data_t));
  78     if (out->priv == NULL) {
  79         return false;
  80     }
  81 
  82     priv = out->priv;
  83     priv->parent_q = g_queue_new();
  84     return true;
  85 }
  86 
  87 static void
  88 text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
  89     CRM_ASSERT(out != NULL && out->dest != NULL);
  90     fflush(out->dest);
  91 }
  92 
  93 static void
  94 text_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  95     private_data_t *priv = NULL;
  96     bool old_fancy = false;
  97 
  98     CRM_ASSERT(out != NULL);
  99 
 100     if (out->dest != stdout) {
 101         out->dest = freopen(NULL, "w", out->dest);
 102     }
 103 
 104     CRM_ASSERT(out->dest != NULL);
 105 
 106     // Save priv->fancy before free/init sequence overwrites it
 107     priv = out->priv;
 108     old_fancy = priv->fancy;
 109 
 110     text_free_priv(out);
 111     text_init(out);
 112 
 113     priv = out->priv;
 114     priv->fancy = old_fancy;
 115 }
 116 
 117 static void
 118 text_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 119                        const char *proc_stdout, const char *proc_stderr) {
 120     CRM_ASSERT(out != NULL);
 121 
 122     if (proc_stdout != NULL) {
 123         fprintf(out->dest, "%s\n", proc_stdout);
 124     }
 125 
 126     if (proc_stderr != NULL) {
 127         fprintf(out->dest, "%s\n", proc_stderr);
 128     }
 129 }
 130 
 131 static void
 132 text_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 133     CRM_ASSERT(out != NULL && out->dest != NULL);
 134 
 135     if (extended) {
 136         fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
 137     } else {
 138         fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION);
 139         fprintf(out->dest, "Written by Andrew Beekhof and "
 140                            "the Pacemaker project contributors\n");
 141     }
 142 }
 143 
 144 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 145 static void
 146 text_err(pcmk__output_t *out, const char *format, ...) {
 147     va_list ap;
 148     int len = 0;
 149 
 150     CRM_ASSERT(out != NULL);
 151 
 152     va_start(ap, format);
 153 
 154     /* Informational output does not get indented, to separate it from other
 155      * potentially indented list output.
 156      */
 157     len = vfprintf(stderr, format, ap);
 158     CRM_ASSERT(len >= 0);
 159     va_end(ap);
 160 
 161     /* Add a newline. */
 162     fprintf(stderr, "\n");
 163 }
 164 
 165 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 166 static int
 167 text_info(pcmk__output_t *out, const char *format, ...) {
 168     va_list ap;
 169     int len = 0;
 170 
 171     CRM_ASSERT(out != NULL);
 172 
 173     if (out->is_quiet(out)) {
 174         return pcmk_rc_no_output;
 175     }
 176 
 177     va_start(ap, format);
 178 
 179     /* Informational output does not get indented, to separate it from other
 180      * potentially indented list output.
 181      */
 182     len = vfprintf(out->dest, format, ap);
 183     CRM_ASSERT(len >= 0);
 184     va_end(ap);
 185 
 186     /* Add a newline. */
 187     fprintf(out->dest, "\n");
 188     return pcmk_rc_ok;
 189 }
 190 
 191 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 192 static int
 193 text_transient(pcmk__output_t *out, const char *format, ...)
 194 {
 195     return pcmk_rc_no_output;
 196 }
 197 
 198 static void
 199 text_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 200     CRM_ASSERT(out != NULL);
 201     pcmk__indented_printf(out, "%s", buf);
 202 }
 203 
 204 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 205 static void
 206 text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
 207                 const char *format, ...) {
 208     private_data_t *priv = NULL;
 209     text_list_data_t *new_list = NULL;
 210     va_list ap;
 211 
 212     CRM_ASSERT(out != NULL && out->priv != NULL);
 213     priv = out->priv;
 214 
 215     va_start(ap, format);
 216 
 217     if ((fancy || priv->fancy) && (format != NULL)) {
 218         pcmk__indented_vprintf(out, format, ap);
 219         fprintf(out->dest, ":\n");
 220     }
 221 
 222     va_end(ap);
 223 
 224     new_list = pcmk__assert_alloc(1, sizeof(text_list_data_t));
 225     new_list->len = 0;
 226     new_list->singular_noun = pcmk__str_copy(singular_noun);
 227     new_list->plural_noun = pcmk__str_copy(plural_noun);
 228 
 229     g_queue_push_tail(priv->parent_q, new_list);
 230 }
 231 
 232 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 233 static void
 234 text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) {
 235     private_data_t *priv = NULL;
 236     va_list ap;
 237 
 238     CRM_ASSERT(out != NULL);
 239 
 240     priv = out->priv;
 241     va_start(ap, format);
 242 
 243     if (fancy || priv->fancy) {
 244         if (id != NULL) {
 245             /* Not really a good way to do this all in one call, so make it two.
 246              * The first handles the indentation and list styling.  The second
 247              * just prints right after that one.
 248              */
 249             pcmk__indented_printf(out, "%s: ", id);
 250             vfprintf(out->dest, format, ap);
 251         } else {
 252             pcmk__indented_vprintf(out, format, ap);
 253         }
 254     } else {
 255         pcmk__indented_vprintf(out, format, ap);
 256     }
 257 
 258     fputc('\n', out->dest);
 259     fflush(out->dest);
 260     va_end(ap);
 261 
 262     out->increment_list(out);
 263 }
 264 
 265 static void
 266 text_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 267     private_data_t *priv = NULL;
 268     gpointer tail;
 269 
 270     CRM_ASSERT(out != NULL && out->priv != NULL);
 271     priv = out->priv;
 272 
 273     tail = g_queue_peek_tail(priv->parent_q);
 274     CRM_ASSERT(tail != NULL);
 275     ((text_list_data_t *) tail)->len++;
 276 }
 277 
 278 static void
 279 text_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 280     private_data_t *priv = NULL;
 281     text_list_data_t *node = NULL;
 282 
 283     CRM_ASSERT(out != NULL && out->priv != NULL);
 284     priv = out->priv;
 285 
 286     node = g_queue_pop_tail(priv->parent_q);
 287 
 288     if (node->singular_noun != NULL && node->plural_noun != NULL) {
 289         if (node->len == 1) {
 290             pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun);
 291         } else {
 292             pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun);
 293         }
 294     }
 295 
 296     free_list_data(node);
 297 }
 298 
 299 static bool
 300 text_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 301     CRM_ASSERT(out != NULL);
 302     return out->quiet;
 303 }
 304 
 305 static void
 306 text_spacer(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 307     CRM_ASSERT(out != NULL);
 308     fprintf(out->dest, "\n");
 309 }
 310 
 311 static void
 312 text_progress(pcmk__output_t *out, bool end) {
     /* [previous][next][first][last][top][bottom][index][help] */
 313     CRM_ASSERT(out != NULL);
 314 
 315     if (out->dest == stdout) {
 316         fprintf(out->dest, ".");
 317 
 318         if (end) {
 319             fprintf(out->dest, "\n");
 320         }
 321     }
 322 }
 323 
 324 pcmk__output_t *
 325 pcmk__mk_text_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 326     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 327 
 328     if (retval == NULL) {
 329         return NULL;
 330     }
 331 
 332     retval->fmt_name = "text";
 333     retval->request = pcmk__quote_cmdline(argv);
 334 
 335     retval->init = text_init;
 336     retval->free_priv = text_free_priv;
 337     retval->finish = text_finish;
 338     retval->reset = text_reset;
 339 
 340     retval->register_message = pcmk__register_message;
 341     retval->message = pcmk__call_message;
 342 
 343     retval->subprocess_output = text_subprocess_output;
 344     retval->version = text_version;
 345     retval->info = text_info;
 346     retval->transient = text_transient;
 347     retval->err = text_err;
 348     retval->output_xml = text_output_xml;
 349 
 350     retval->begin_list = text_begin_list;
 351     retval->list_item = text_list_item;
 352     retval->increment_list = text_increment_list;
 353     retval->end_list = text_end_list;
 354 
 355     retval->is_quiet = text_is_quiet;
 356     retval->spacer = text_spacer;
 357     retval->progress = text_progress;
 358     retval->prompt = pcmk__text_prompt;
 359 
 360     return retval;
 361 }
 362 
 363 /*!
 364  * \internal
 365  * \brief Check whether fancy output is enabled for a text output object
 366  *
 367  * This returns \c false if the output object is not of text format.
 368  *
 369  * \param[in] out  Output object
 370  *
 371  * \return \c true if \p out has fancy output enabled, or \c false otherwise
 372  */
 373 bool
 374 pcmk__output_text_get_fancy(pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 375 {
 376     CRM_ASSERT(out != NULL);
 377 
 378     if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
 379         private_data_t *priv = out->priv;
 380 
 381         CRM_ASSERT(priv != NULL);
 382         return priv->fancy;
 383     }
 384     return false;
 385 }
 386 
 387 /*!
 388  * \internal
 389  * \brief Enable or disable fancy output for a text output object
 390  *
 391  * This does nothing if the output object is not of text format.
 392  *
 393  * \param[in,out] out      Output object
 394  * \param[in]     enabled  Whether fancy output should be enabled for \p out
 395  */
 396 void
 397 pcmk__output_text_set_fancy(pcmk__output_t *out, bool enabled)
     /* [previous][next][first][last][top][bottom][index][help] */
 398 {
 399     CRM_ASSERT(out != NULL);
 400 
 401     if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) {
 402         private_data_t *priv = out->priv;
 403 
 404         CRM_ASSERT(priv != NULL);
 405         priv->fancy = enabled;
 406     }
 407 }
 408 
 409 G_GNUC_PRINTF(2, 0)
     /* [previous][next][first][last][top][bottom][index][help] */
 410 void
 411 pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) {
 412     int len = 0;
 413 
 414     CRM_ASSERT(out != NULL);
 415     CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
 416 
 417     len = vfprintf(out->dest, format, args);
 418     CRM_ASSERT(len >= 0);
 419 }
 420 
 421 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 422 void
 423 pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) {
 424     va_list ap;
 425 
 426     CRM_ASSERT(out != NULL);
 427 
 428     va_start(ap, format);
 429     pcmk__formatted_vprintf(out, format, ap);
 430     va_end(ap);
 431 }
 432 
 433 G_GNUC_PRINTF(2, 0)
     /* [previous][next][first][last][top][bottom][index][help] */
 434 void
 435 pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) {
 436     private_data_t *priv = NULL;
 437 
 438     CRM_ASSERT(out != NULL);
 439     CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return);
 440 
 441     priv = out->priv;
 442 
 443     if (fancy || priv->fancy) {
 444         int level = 0;
 445         private_data_t *priv = out->priv;
 446 
 447         CRM_ASSERT(priv != NULL);
 448 
 449         level = g_queue_get_length(priv->parent_q);
 450 
 451         for (int i = 0; i < level; i++) {
 452             fprintf(out->dest, "  ");
 453         }
 454 
 455         if (level > 0) {
 456             fprintf(out->dest, "* ");
 457         }
 458     }
 459 
 460     pcmk__formatted_vprintf(out, format, args);
 461 }
 462 
 463 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 464 void
 465 pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) {
 466     va_list ap;
 467 
 468     CRM_ASSERT(out != NULL);
 469 
 470     va_start(ap, format);
 471     pcmk__indented_vprintf(out, format, ap);
 472     va_end(ap);
 473 }
 474 
 475 void
 476 pcmk__text_prompt(const char *prompt, bool echo, char **dest)
     /* [previous][next][first][last][top][bottom][index][help] */
 477 {
 478     int rc = 0;
 479     struct termios settings;
 480     tcflag_t orig_c_lflag = 0;
 481 
 482     CRM_ASSERT(prompt != NULL);
 483     CRM_ASSERT(dest != NULL);
 484 
 485     if (!echo) {
 486         rc = tcgetattr(0, &settings);
 487         if (rc == 0) {
 488             orig_c_lflag = settings.c_lflag;
 489             settings.c_lflag &= ~ECHO;
 490             rc = tcsetattr(0, TCSANOW, &settings);
 491         }
 492     }
 493 
 494     if (rc == 0) {
 495         fprintf(stderr, "%s: ", prompt);
 496 
 497         if (*dest != NULL) {
 498             free(*dest);
 499             *dest = NULL;
 500         }
 501 
 502 #if HAVE_SSCANF_M
 503         rc = scanf("%ms", dest);
 504 #else
 505         *dest = pcmk__assert_alloc(1, 1024);
 506         rc = scanf("%1023s", *dest);
 507 #endif
 508         fprintf(stderr, "\n");
 509     }
 510 
 511     if (rc < 1) {
 512         free(*dest);
 513         *dest = NULL;
 514     }
 515 
 516     if (orig_c_lflag != 0) {
 517         settings.c_lflag = orig_c_lflag;
 518         /* rc = */ tcsetattr(0, TCSANOW, &settings);
 519     }
 520 }

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