root/lib/common/options_display.c

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

DEFINITIONS

This source file includes following definitions.
  1. add_possible_values_default
  2. add_option_metadata_default
  3. PCMK__OUTPUT_ARGS
  4. add_desc_xml
  5. add_possible_values_xml
  6. map_legacy_option_type
  7. add_option_metadata_xml
  8. PCMK__OUTPUT_ARGS
  9. pcmk__register_option_messages

   1 /*
   2  * Copyright 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 
  12 #include <glib.h>   // GSList, GString
  13 
  14 #include "crmcommon_private.h"
  15 
  16 /*!
  17  * \internal
  18  * \brief Output an option's possible values
  19  *
  20  * \param[in,out] out     Output object
  21  * \param[in]     option  Option whose possible values to add
  22  */
  23 static void
  24 add_possible_values_default(pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
  25                             const pcmk__cluster_option_t *option)
  26 {
  27     const char *id = _("Possible values");
  28     GString *buf = g_string_sized_new(256);
  29 
  30     pcmk__assert(option->type != NULL);
  31 
  32     if (pcmk_is_set(option->flags, pcmk__opt_generated)) {
  33         id = _("Possible values (generated by Pacemaker)");
  34     }
  35 
  36     if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) {
  37         const char *delim = ", ";
  38         bool found_default = (option->default_value == NULL);
  39         char *str = pcmk__str_copy(option->values);
  40 
  41         for (const char *value = strtok(str, delim); value != NULL;
  42              value = strtok(NULL, delim)) {
  43 
  44             if (buf->len > 0) {
  45                 g_string_append(buf, delim);
  46             }
  47             g_string_append_c(buf, '"');
  48             g_string_append(buf, value);
  49             g_string_append_c(buf, '"');
  50 
  51             if (!found_default && (strcmp(value, option->default_value) == 0)) {
  52                 found_default = true;
  53                 g_string_append(buf, _(" (default)"));
  54             }
  55         }
  56         free(str);
  57 
  58     } else if (option->default_value != NULL) {
  59         pcmk__g_strcat(buf,
  60                        option->type, _(" (default: \""), option->default_value,
  61                        "\")", NULL);
  62 
  63     } else {
  64         pcmk__g_strcat(buf, option->type, _(" (no default)"), NULL);
  65     }
  66 
  67     out->list_item(out, id, "%s", buf->str);
  68     g_string_free(buf, TRUE);
  69 }
  70 
  71 /*!
  72  * \internal
  73  * \brief Output a single option's metadata
  74  *
  75  * \param[in,out] out     Output object
  76  * \param[in]     option  Option to add
  77  */
  78 static void
  79 add_option_metadata_default(pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
  80                             const pcmk__cluster_option_t *option)
  81 {
  82     const char *desc_short = option->description_short;
  83     const char *desc_long = option->description_long;
  84 
  85     pcmk__assert((desc_short != NULL) || (desc_long != NULL));
  86 
  87     if (desc_short == NULL) {
  88         desc_short = desc_long;
  89         desc_long = NULL;
  90     }
  91 
  92     out->list_item(out, option->name, "%s", _(desc_short));
  93 
  94     out->begin_list(out, NULL, NULL, NULL);
  95 
  96     if (desc_long != NULL) {
  97         out->list_item(out, NULL, "%s", _(desc_long));
  98     }
  99     add_possible_values_default(out, option);
 100     out->end_list(out);
 101 }
 102 
 103 /*!
 104  * \internal
 105  * \brief Output the metadata for a list of options
 106  *
 107  * \param[in,out] out   Output object
 108  * \param[in]     args  Message-specific arguments
 109  *
 110  * \return Standard Pacemaker return code
 111  *
 112  * \note \p args should contain the following:
 113  *       -# Fake resource agent name for the option list (ignored)
 114  *       -# Short description of option list
 115  *       -# Long description of option list
 116  *       -# Filter: Group of <tt>enum pcmk__opt_flags</tt>; output an option
 117  *          only if its \c flags member has all these flags set
 118  *       -# <tt>NULL</tt>-terminated list of options whose metadata to format
 119  *       -# All: If \c true, output all options; otherwise, exclude advanced and
 120  *          deprecated options unless \c pcmk__opt_advanced and
 121  *          \c pcmk__opt_deprecated flags (respectively) are set in the filter.
 122  */
 123 PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 124                   "uint32_t", "const pcmk__cluster_option_t *", "bool")
 125 static int
 126 option_list_default(pcmk__output_t *out, va_list args)
 127 {
 128     const char *name G_GNUC_UNUSED = va_arg(args, const char *);
 129     const char *desc_short = va_arg(args, const char *);
 130     const char *desc_long = va_arg(args, const char *);
 131     const uint32_t filter = va_arg(args, uint32_t);
 132     const pcmk__cluster_option_t *option_list =
 133         va_arg(args, pcmk__cluster_option_t *);
 134     const bool all = (bool) va_arg(args, int);
 135 
 136     const bool show_deprecated = all
 137                                  || pcmk_is_set(filter, pcmk__opt_deprecated);
 138     const bool show_advanced = all || pcmk_is_set(filter, pcmk__opt_advanced);
 139     bool old_fancy = false;
 140 
 141     GSList *deprecated = NULL;
 142     GSList *advanced = NULL;
 143 
 144     pcmk__assert((out != NULL) && (desc_short != NULL)
 145                  && (desc_long != NULL) && (option_list != NULL));
 146 
 147     old_fancy = pcmk__output_text_get_fancy(out);
 148     pcmk__output_text_set_fancy(out, true);
 149 
 150     out->info(out, "%s", _(desc_short));
 151     out->spacer(out);
 152     out->info(out, "%s", _(desc_long));
 153     out->begin_list(out, NULL, NULL, NULL);
 154 
 155     for (const pcmk__cluster_option_t *option = option_list;
 156          option->name != NULL; option++) {
 157 
 158         // Store deprecated and advanced options to display later if appropriate
 159         if (pcmk_all_flags_set(option->flags, filter)) {
 160             if (pcmk_is_set(option->flags, pcmk__opt_deprecated)) {
 161                 if (show_deprecated) {
 162                     deprecated = g_slist_prepend(deprecated, (gpointer) option);
 163                 }
 164 
 165             } else if (pcmk_is_set(option->flags, pcmk__opt_advanced)) {
 166                 if (show_advanced) {
 167                     advanced = g_slist_prepend(advanced, (gpointer) option);
 168                 }
 169 
 170             } else {
 171                 out->spacer(out);
 172                 add_option_metadata_default(out, option);
 173             }
 174         }
 175     }
 176 
 177     if (advanced != NULL) {
 178         advanced = g_slist_reverse(advanced);
 179 
 180         out->spacer(out);
 181         out->begin_list(out, NULL, NULL, _("ADVANCED OPTIONS"));
 182         for (const GSList *iter = advanced; iter != NULL; iter = iter->next) {
 183             const pcmk__cluster_option_t *option = iter->data;
 184 
 185             out->spacer(out);
 186             add_option_metadata_default(out, option);
 187         }
 188         out->end_list(out);
 189         g_slist_free(advanced);
 190     }
 191 
 192     if (deprecated != NULL) {
 193         deprecated = g_slist_reverse(deprecated);
 194 
 195         out->spacer(out);
 196         out->begin_list(out, NULL, NULL,
 197                         _("DEPRECATED OPTIONS (will be removed in a future "
 198                           "release)"));
 199         for (const GSList *iter = deprecated; iter != NULL; iter = iter->next) {
 200             const pcmk__cluster_option_t *option = iter->data;
 201 
 202             out->spacer(out);
 203             add_option_metadata_default(out, option);
 204         }
 205         out->end_list(out);
 206         g_slist_free(deprecated);
 207     }
 208 
 209     out->end_list(out);
 210     pcmk__output_text_set_fancy(out, old_fancy);
 211     return pcmk_rc_ok;
 212 }
 213 
 214 /*!
 215  * \internal
 216  * \brief Add a description element to an OCF-like metadata XML node
 217  *
 218  * Include a translation based on the current locale if \c ENABLE_NLS is
 219  * defined.
 220  *
 221  * \param[in,out] out       Output object
 222  * \param[in]     for_long  If \c true, add long description; otherwise, add
 223  *                          short description
 224  * \param[in]     desc      Textual description to add
 225  */
 226 static void
 227 add_desc_xml(pcmk__output_t *out, bool for_long, const char *desc)
     /* [previous][next][first][last][top][bottom][index][help] */
 228 {
 229     const char *tag = (for_long? PCMK_XE_LONGDESC : PCMK_XE_SHORTDESC);
 230     xmlNode *node = pcmk__output_create_xml_text_node(out, tag, desc);
 231 
 232     crm_xml_add(node, PCMK_XA_LANG, PCMK__VALUE_EN);
 233 
 234 #ifdef ENABLE_NLS
 235     {
 236         static const char *locale = NULL;
 237 
 238         if (strcmp(desc, _(desc)) == 0) {
 239             return;
 240         }
 241 
 242         if (locale == NULL) {
 243             locale = strtok(setlocale(LC_ALL, NULL), "_");
 244         }
 245         node = pcmk__output_create_xml_text_node(out, tag, _(desc));
 246         crm_xml_add(node, PCMK_XA_LANG, locale);
 247     }
 248 #endif
 249 }
 250 
 251 /*!
 252  * \internal
 253  * \brief Output an option's possible values
 254  *
 255  * Add a \c PCMK_XE_OPTION element for each of the option's possible values.
 256  *
 257  * \param[in,out] out     Output object
 258  * \param[in]     option  Option whose possible values to add
 259  */
 260 static void
 261 add_possible_values_xml(pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
 262                         const pcmk__cluster_option_t *option)
 263 {
 264     if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) {
 265         const char *delim = ", ";
 266         char *str = pcmk__str_copy(option->values);
 267         const char *ptr = strtok(str, delim);
 268 
 269         while (ptr != NULL) {
 270             pcmk__output_create_xml_node(out, PCMK_XE_OPTION,
 271                                          PCMK_XA_VALUE, ptr,
 272                                          NULL);
 273             ptr = strtok(NULL, delim);
 274         }
 275         free(str);
 276     }
 277 }
 278 
 279 /*!
 280  * \internal
 281  * \brief Map an option type to one suitable for daemon metadata
 282  *
 283  * \param[in] type  Option type to map
 284  *
 285  * \return String suitable for daemon metadata to display as an option type
 286  */
 287 static const char *
 288 map_legacy_option_type(const char *type)
     /* [previous][next][first][last][top][bottom][index][help] */
 289 {
 290     // @COMPAT Drop this function when we drop daemon metadata commands
 291     if (pcmk__str_any_of(type, PCMK_VALUE_DURATION, PCMK_VALUE_TIMEOUT, NULL)) {
 292         return PCMK__VALUE_TIME;
 293 
 294     } else if (pcmk__str_any_of(type,
 295                                 PCMK_VALUE_NONNEGATIVE_INTEGER,
 296                                 PCMK_VALUE_SCORE, NULL)) {
 297         return PCMK_VALUE_INTEGER;
 298 
 299     } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_none)) {
 300         return PCMK_VALUE_STRING;
 301 
 302     } else {
 303         return type;
 304     }
 305 }
 306 
 307 /*!
 308  * \internal
 309  * \brief Add a \c PCMK_XE_PARAMETER element to an OCF-like metadata XML node
 310  *
 311  * \param[in,out] out     Output object
 312  * \param[in]     option  Option to add as a \c PCMK_XE_PARAMETER element
 313  */
 314 static void
 315 add_option_metadata_xml(pcmk__output_t *out,
     /* [previous][next][first][last][top][bottom][index][help] */
 316                         const pcmk__cluster_option_t *option)
 317 {
 318     const char *type = option->type;
 319     const char *desc_long = option->description_long;
 320     const char *desc_short = option->description_short;
 321     const bool advanced = pcmk_is_set(option->flags, pcmk__opt_advanced);
 322     const bool deprecated = pcmk_is_set(option->flags, pcmk__opt_deprecated);
 323     const bool generated = pcmk_is_set(option->flags, pcmk__opt_generated);
 324 
 325     // OCF requires "1"/"0" and does not allow "true"/"false
 326     // @COMPAT Variables no longer needed after we drop legacy mode
 327     const char *advanced_s = advanced? "1" : "0";
 328     const char *generated_s = generated? "1" : "0";
 329 
 330     // @COMPAT For daemon metadata only; drop when daemon metadata is dropped
 331     const bool legacy = pcmk__output_get_legacy_xml(out);
 332     char *desc_long_legacy = NULL;
 333     GString *desc_short_legacy = NULL;
 334 
 335     // The standard requires a parameter type
 336     pcmk__assert(type != NULL);
 337 
 338     // The standard requires long and short parameter descriptions
 339     pcmk__assert((desc_long != NULL) || (desc_short != NULL));
 340 
 341     if (desc_long == NULL) {
 342         desc_long = desc_short;
 343     } else if (desc_short == NULL) {
 344         desc_short = desc_long;
 345     }
 346 
 347     if (legacy) {
 348         // This is ugly but it will go away at a major release bump
 349         type = map_legacy_option_type(type);
 350 
 351         if (option->values != NULL) {
 352             desc_long_legacy = crm_strdup_printf("%s  Allowed values: %s",
 353                                                  desc_long, option->values);
 354             desc_long = desc_long_legacy;
 355         }
 356 
 357         if (deprecated || advanced) {
 358             const size_t init_sz = 1023;
 359 
 360             if (desc_long != option->description_long) {
 361                 /* desc_long was NULL and got assigned desc_short, which was
 362                  * non-empty. Let desc_long have the "real" description, and put
 363                  * the flag in desc_short.
 364                  */
 365                 desc_short = "";
 366             } else {
 367                 desc_short = pcmk__s(option->description_short, "");
 368             }
 369 
 370             if (deprecated) {
 371                 pcmk__add_separated_word(&desc_short_legacy, init_sz,
 372                                          "*** Deprecated ***", NULL);
 373             }
 374             if (advanced) {
 375                 pcmk__add_separated_word(&desc_short_legacy, init_sz,
 376                                          "*** Advanced Use Only ***", NULL);
 377             }
 378             pcmk__add_separated_word(&desc_short_legacy, 0, desc_short, NULL);
 379 
 380             desc_short = desc_short_legacy->str;
 381         }
 382 
 383         /* These must be NULL when used as attribute values later.
 384          * PCMK_XA_ADVANCED and PCMK_XA_GENERATED break validation for some
 385          * legacy tools.
 386          */
 387         advanced_s = NULL;
 388         generated_s = NULL;
 389     }
 390 
 391     pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER,
 392                                    PCMK_XA_NAME, option->name,
 393                                    PCMK_XA_ADVANCED, advanced_s,
 394                                    PCMK_XA_GENERATED, generated_s,
 395                                    NULL);
 396 
 397     if (deprecated && !legacy) {
 398         // No need yet to support "replaced-with" or "desc"; add if needed
 399         pcmk__output_create_xml_node(out, PCMK_XE_DEPRECATED, NULL);
 400     }
 401     add_desc_xml(out, true, desc_long);
 402     add_desc_xml(out, false, desc_short);
 403 
 404     pcmk__output_xml_create_parent(out, PCMK_XE_CONTENT,
 405                                    PCMK_XA_TYPE, type,
 406                                    PCMK_XA_DEFAULT, option->default_value,
 407                                    NULL);
 408 
 409     add_possible_values_xml(out, option);
 410 
 411     pcmk__output_xml_pop_parent(out);
 412     pcmk__output_xml_pop_parent(out);
 413 
 414     free(desc_long_legacy);
 415     if (desc_short_legacy != NULL) {
 416         g_string_free(desc_short_legacy, TRUE);
 417     }
 418 }
 419 
 420 /*!
 421  * \internal
 422  * \brief Output the metadata for a list of options as OCF-like XML
 423  *
 424  * \param[in,out] out   Output object
 425  * \param[in]     args  Message-specific arguments
 426  *
 427  * \return Standard Pacemaker return code
 428  *
 429  * \note \p args should contain the following:
 430  *       -# Fake resource agent name for the option list
 431  *       -# Short description of option list
 432  *       -# Long description of option list
 433  *       -# Filter: Group of <tt>enum pcmk__opt_flags</tt>; output an option
 434  *          only if its \c flags member has all these flags set
 435  *       -# <tt>NULL</tt>-terminated list of options whose metadata to format
 436  *       -# Whether to output all options (ignored, treated as \c true)
 437  */
 438 PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 439                   "uint32_t", "const pcmk__cluster_option_t *", "bool")
 440 static int
 441 option_list_xml(pcmk__output_t *out, va_list args)
 442 {
 443     const char *name = va_arg(args, const char *);
 444     const char *desc_short = va_arg(args, const char *);
 445     const char *desc_long = va_arg(args, const char *);
 446     const uint32_t filter = va_arg(args, uint32_t);
 447     const pcmk__cluster_option_t *option_list =
 448         va_arg(args, pcmk__cluster_option_t *);
 449 
 450     pcmk__assert((out != NULL) && (name != NULL) && (desc_short != NULL)
 451                  && (desc_long != NULL) && (option_list != NULL));
 452 
 453     pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT,
 454                                    PCMK_XA_NAME, name,
 455                                    PCMK_XA_VERSION, PACEMAKER_VERSION,
 456                                    NULL);
 457 
 458     pcmk__output_create_xml_text_node(out, PCMK_XE_VERSION, PCMK_OCF_VERSION);
 459     add_desc_xml(out, true, desc_long);
 460     add_desc_xml(out, false, desc_short);
 461 
 462     pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETERS, NULL);
 463 
 464     for (const pcmk__cluster_option_t *option = option_list;
 465          option->name != NULL; option++) {
 466 
 467         if (pcmk_all_flags_set(option->flags, filter)) {
 468             add_option_metadata_xml(out, option);
 469         }
 470     }
 471 
 472     pcmk__output_xml_pop_parent(out);
 473     pcmk__output_xml_pop_parent(out);
 474     return pcmk_rc_ok;
 475 }
 476 
 477 static pcmk__message_entry_t fmt_functions[] = {
 478     { "option-list", "default", option_list_default },
 479     { "option-list", "xml", option_list_xml },
 480 
 481     { NULL, NULL, NULL }
 482 };
 483 
 484 /*!
 485  * \internal
 486  * \brief Register the formatting functions for option lists
 487  *
 488  * \param[in,out] out  Output object
 489  */
 490 void
 491 pcmk__register_option_messages(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 492     pcmk__register_messages(out, fmt_functions);
 493 }

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