root/lib/common/options.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__cli_option_cleanup
  2. create_long_opts
  3. pcmk__set_cli_options
  4. pcmk__next_cli_option
  5. pcmk__cli_help
  6. pcmk__env_option
  7. pcmk__set_env_option
  8. pcmk__env_option_enabled
  9. pcmk__valid_interval_spec
  10. pcmk__valid_boolean
  11. pcmk__valid_number
  12. pcmk__valid_positive_number
  13. pcmk__valid_quorum
  14. pcmk__valid_script
  15. pcmk__valid_percentage
  16. cluster_option_value
  17. pcmk__cluster_option
  18. add_desc
  19. pcmk__format_option_metadata
  20. pcmk__validate_cluster_options

   1 /*
   2  * Copyright 2004-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 #ifndef _GNU_SOURCE
  11 #  define _GNU_SOURCE
  12 #endif
  13 
  14 #include <crm_internal.h>
  15 
  16 #include <stdio.h>
  17 #include <string.h>
  18 #include <stdlib.h>
  19 #include <sys/types.h>
  20 #include <sys/stat.h>
  21 
  22 #ifdef HAVE_GETOPT_H
  23 #  include <getopt.h>
  24 #endif
  25 
  26 #include <crm/crm.h>
  27 
  28 
  29 /*
  30  * Command-line option handling
  31  */
  32 
  33 static char *crm_short_options = NULL;
  34 static const pcmk__cli_option_t *crm_long_options = NULL;
  35 static const char *crm_app_description = NULL;
  36 static const char *crm_app_usage = NULL;
  37 
  38 void
  39 pcmk__cli_option_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  40 {
  41     free(crm_short_options);
  42     crm_short_options = NULL;
  43 }
  44 
  45 static struct option *
  46 create_long_opts(const pcmk__cli_option_t *long_options)
     /* [previous][next][first][last][top][bottom][index][help] */
  47 {
  48     struct option *long_opts = NULL;
  49 
  50 #ifdef HAVE_GETOPT_H
  51     int index = 0, lpc = 0;
  52 
  53     /*
  54      * A previous, possibly poor, choice of '?' as the short form of --help
  55      * means that getopt_long() returns '?' for both --help and for "unknown option"
  56      *
  57      * This dummy entry allows us to differentiate between the two in
  58      * pcmk__next_cli_option() and exit with the correct error code.
  59      */
  60     long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
  61     long_opts[index].name = "__dummmy__";
  62     long_opts[index].has_arg = 0;
  63     long_opts[index].flag = 0;
  64     long_opts[index].val = '_';
  65     index++;
  66 
  67     // cppcheck seems not to understand the abort-logic in pcmk__realloc
  68     // cppcheck-suppress memleak
  69     for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
  70         if (long_options[lpc].name[0] == '-') {
  71             continue;
  72         }
  73 
  74         long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
  75         /*fprintf(stderr, "Creating %d %s = %c\n", index,
  76          * long_options[lpc].name, long_options[lpc].val);      */
  77         long_opts[index].name = long_options[lpc].name;
  78         long_opts[index].has_arg = long_options[lpc].has_arg;
  79         long_opts[index].flag = long_options[lpc].flag;
  80         long_opts[index].val = long_options[lpc].val;
  81         index++;
  82     }
  83 
  84     /* Now create the list terminator */
  85     long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
  86     long_opts[index].name = NULL;
  87     long_opts[index].has_arg = 0;
  88     long_opts[index].flag = 0;
  89     long_opts[index].val = 0;
  90 #endif
  91 
  92     return long_opts;
  93 }
  94 
  95 /*!
  96  * \internal
  97  * \brief Define the command-line options a daemon or tool accepts
  98  *
  99  * \param[in] short_options  getopt(3)-style short option list
 100  * \param[in] app_usage      summary of how command is invoked (for help)
 101  * \param[in] long_options   definition of options accepted
 102  * \param[in] app_desc       brief command description (for help)
 103  */
 104 void
 105 pcmk__set_cli_options(const char *short_options, const char *app_usage,
     /* [previous][next][first][last][top][bottom][index][help] */
 106                       const pcmk__cli_option_t *long_options,
 107                       const char *app_desc)
 108 {
 109     if (short_options) {
 110         crm_short_options = strdup(short_options);
 111 
 112     } else if (long_options) {
 113         int lpc = 0;
 114         int opt_string_len = 0;
 115         char *local_short_options = NULL;
 116 
 117         for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
 118             if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
 119                 local_short_options = pcmk__realloc(local_short_options,
 120                                                     opt_string_len + 4);
 121                 local_short_options[opt_string_len++] = long_options[lpc].val;
 122                 /* getopt(3) says: Two colons mean an option takes an optional arg; */
 123                 if (long_options[lpc].has_arg == optional_argument) {
 124                     local_short_options[opt_string_len++] = ':';
 125                 }
 126                 if (long_options[lpc].has_arg >= required_argument) {
 127                     local_short_options[opt_string_len++] = ':';
 128                 }
 129                 local_short_options[opt_string_len] = 0;
 130             }
 131         }
 132         crm_short_options = local_short_options;
 133         crm_trace("Generated short option string: '%s'", local_short_options);
 134     }
 135 
 136     if (long_options) {
 137         crm_long_options = long_options;
 138     }
 139     if (app_desc) {
 140         crm_app_description = app_desc;
 141     }
 142     if (app_usage) {
 143         crm_app_usage = app_usage;
 144     }
 145 }
 146 
 147 int
 148 pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
     /* [previous][next][first][last][top][bottom][index][help] */
 149 {
 150 #ifdef HAVE_GETOPT_H
 151     static struct option *long_opts = NULL;
 152 
 153     if (long_opts == NULL && crm_long_options) {
 154         long_opts = create_long_opts(crm_long_options);
 155     }
 156 
 157     *index = 0;
 158     if (long_opts) {
 159         int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
 160 
 161         switch (flag) {
 162             case 0:
 163                 if (long_opts[*index].val) {
 164                     return long_opts[*index].val;
 165                 } else if (longname) {
 166                     *longname = long_opts[*index].name;
 167                 } else {
 168                     crm_notice("Unhandled option --%s", long_opts[*index].name);
 169                     return flag;
 170                 }
 171                 break;
 172 
 173             case -1:           /* End of option processing */
 174                 break;
 175             case ':':
 176                 crm_trace("Missing argument");
 177                 pcmk__cli_help('?', CRM_EX_USAGE);
 178                 break;
 179             case '?':
 180                 pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE));
 181                 break;
 182         }
 183         return flag;
 184     }
 185 #endif
 186 
 187     if (crm_short_options) {
 188         return getopt(argc, argv, crm_short_options);
 189     }
 190 
 191     return -1;
 192 }
 193 
 194 void
 195 pcmk__cli_help(char cmd, crm_exit_t exit_code)
     /* [previous][next][first][last][top][bottom][index][help] */
 196 {
 197     int i = 0;
 198     FILE *stream = (exit_code ? stderr : stdout);
 199 
 200     if (cmd == 'v' || cmd == '$') {
 201         fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
 202         fprintf(stream, "Written by Andrew Beekhof and "
 203                         "the Pacemaker project contributors\n");
 204         goto out;
 205     }
 206 
 207     if (cmd == '!') {
 208         fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
 209         goto out;
 210     }
 211 
 212     fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
 213 
 214     if (crm_app_usage) {
 215         fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
 216     }
 217 
 218     if (crm_long_options) {
 219         fprintf(stream, "Options:\n");
 220         for (i = 0; crm_long_options[i].name != NULL; i++) {
 221             if (crm_long_options[i].flags & pcmk__option_hidden) {
 222 
 223             } else if (crm_long_options[i].flags & pcmk__option_paragraph) {
 224                 fprintf(stream, "%s\n\n", crm_long_options[i].desc);
 225 
 226             } else if (crm_long_options[i].flags & pcmk__option_example) {
 227                 fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
 228 
 229             } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
 230                 fprintf(stream, "%s\n", crm_long_options[i].desc);
 231 
 232             } else {
 233                 /* is val printable as char ? */
 234                 if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
 235                     fprintf(stream, " -%c,", crm_long_options[i].val);
 236                 } else {
 237                     fputs("    ", stream);
 238                 }
 239                 fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
 240                         crm_long_options[i].has_arg == optional_argument ? "[=value]" :
 241                         crm_long_options[i].has_arg == required_argument ? "=value" : "",
 242                         crm_long_options[i].desc ? crm_long_options[i].desc : "");
 243             }
 244         }
 245 
 246     } else if (crm_short_options) {
 247         fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
 248         for (i = 0; crm_short_options[i] != 0; i++) {
 249             int has_arg = no_argument /* 0 */;
 250 
 251             if (crm_short_options[i + 1] == ':') {
 252                 if (crm_short_options[i + 2] == ':')
 253                     has_arg = optional_argument /* 2 */;
 254                 else
 255                     has_arg = required_argument /* 1 */;
 256             }
 257 
 258             fprintf(stream, " -%c %s\n", crm_short_options[i],
 259                     has_arg == optional_argument ? "[value]" :
 260                     has_arg == required_argument ? "{value}" : "");
 261             i += has_arg;
 262         }
 263     }
 264 
 265     fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
 266 
 267   out:
 268     crm_exit(exit_code);
 269     while(1); // above does not return
 270 }
 271 
 272 
 273 /*
 274  * Environment variable option handling
 275  */
 276 
 277 /*!
 278  * \internal
 279  * \brief Get the value of a Pacemaker environment variable option
 280  *
 281  * If an environment variable option is set, with either a PCMK_ or (for
 282  * backward compatibility) HA_ prefix, log and return the value.
 283  *
 284  * \param[in] option  Environment variable name (without prefix)
 285  *
 286  * \return Value of environment variable option, or NULL in case of
 287  *         option name too long or value not found
 288  */
 289 const char *
 290 pcmk__env_option(const char *option)
     /* [previous][next][first][last][top][bottom][index][help] */
 291 {
 292     const char *const prefixes[] = {"PCMK_", "HA_"};
 293     char env_name[NAME_MAX];
 294     const char *value = NULL;
 295 
 296     CRM_CHECK(!pcmk__str_empty(option), return NULL);
 297 
 298     for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
 299         int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
 300 
 301         if (rv < 0) {
 302             crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
 303                     strerror(errno));
 304             return NULL;
 305         }
 306 
 307         if (rv >= sizeof(env_name)) {
 308             crm_trace("\"%s%s\" is too long", prefixes[i], option);
 309             continue;
 310         }
 311 
 312         value = getenv(env_name);
 313         if (value != NULL) {
 314             crm_trace("Found %s = %s", env_name, value);
 315             return value;
 316         }
 317     }
 318 
 319     crm_trace("Nothing found for %s", option);
 320     return NULL;
 321 }
 322 
 323 /*!
 324  * \brief Set or unset a Pacemaker environment variable option
 325  *
 326  * Set an environment variable option with both a PCMK_ and (for
 327  * backward compatibility) HA_ prefix.
 328  *
 329  * \param[in] option  Environment variable name (without prefix)
 330  * \param[in] value   New value (or NULL to unset)
 331  */
 332 void
 333 pcmk__set_env_option(const char *option, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 334 {
 335     const char *const prefixes[] = {"PCMK_", "HA_"};
 336     char env_name[NAME_MAX];
 337 
 338     CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL),
 339               return);
 340 
 341     for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
 342         int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
 343 
 344         if (rv < 0) {
 345             crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
 346                     strerror(errno));
 347             return;
 348         }
 349 
 350         if (rv >= sizeof(env_name)) {
 351             crm_trace("\"%s%s\" is too long", prefixes[i], option);
 352             continue;
 353         }
 354 
 355         if (value != NULL) {
 356             crm_trace("Setting %s to %s", env_name, value);
 357             rv = setenv(env_name, value, 1);
 358         } else {
 359             crm_trace("Unsetting %s", env_name);
 360             rv = unsetenv(env_name);
 361         }
 362 
 363         if (rv < 0) {
 364             crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un",
 365                     env_name, strerror(errno));
 366         }
 367     }
 368 }
 369 
 370 /*!
 371  * \internal
 372  * \brief Check whether Pacemaker environment variable option is enabled
 373  *
 374  * Given a Pacemaker environment variable option that can either be boolean
 375  * or a list of daemon names, return true if the option is enabled for a given
 376  * daemon.
 377  *
 378  * \param[in] daemon   Daemon name (can be NULL)
 379  * \param[in] option   Pacemaker environment variable name
 380  *
 381  * \return true if variable is enabled for daemon, otherwise false
 382  */
 383 bool
 384 pcmk__env_option_enabled(const char *daemon, const char *option)
     /* [previous][next][first][last][top][bottom][index][help] */
 385 {
 386     const char *value = pcmk__env_option(option);
 387 
 388     return (value != NULL)
 389         && (crm_is_true(value)
 390             || ((daemon != NULL) && (strstr(value, daemon) != NULL)));
 391 }
 392 
 393 
 394 /*
 395  * Cluster option handling
 396  */
 397 
 398 bool
 399 pcmk__valid_interval_spec(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 400 {
 401     (void) crm_parse_interval_spec(value);
 402     return errno == 0;
 403 }
 404 
 405 bool
 406 pcmk__valid_boolean(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 407 {
 408     int tmp;
 409 
 410     return crm_str_to_boolean(value, &tmp) == 1;
 411 }
 412 
 413 bool
 414 pcmk__valid_number(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 415 {
 416     if (value == NULL) {
 417         return false;
 418 
 419     } else if (pcmk_str_is_minus_infinity(value) ||
 420                pcmk_str_is_infinity(value)) {
 421         return true;
 422     }
 423 
 424     return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok;
 425 }
 426 
 427 bool
 428 pcmk__valid_positive_number(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 429 {
 430     long long num = 0LL;
 431 
 432     return pcmk_str_is_infinity(value)
 433            || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0));
 434 }
 435 
 436 bool
 437 pcmk__valid_quorum(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 438 {
 439     return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
 440 }
 441 
 442 bool
 443 pcmk__valid_script(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 444 {
 445     struct stat st;
 446 
 447     if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) {
 448         return true;
 449     }
 450 
 451     if (stat(value, &st) != 0) {
 452         crm_err("Script %s does not exist", value);
 453         return false;
 454     }
 455 
 456     if (S_ISREG(st.st_mode) == 0) {
 457         crm_err("Script %s is not a regular file", value);
 458         return false;
 459     }
 460 
 461     if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
 462         crm_err("Script %s is not executable", value);
 463         return false;
 464     }
 465 
 466     return true;
 467 }
 468 
 469 bool
 470 pcmk__valid_percentage(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 471 {
 472     char *end = NULL;
 473     long number = strtol(value, &end, 10);
 474 
 475     if (end && (end[0] != '%')) {
 476         return false;
 477     }
 478     return number >= 0;
 479 }
 480 
 481 /*!
 482  * \internal
 483  * \brief Check a table of configured options for a particular option
 484  *
 485  * \param[in,out] options    Name/value pairs for configured options
 486  * \param[in]     validate   If not NULL, validator function for option value
 487  * \param[in]     name       Option name to look for
 488  * \param[in]     old_name   Alternative option name to look for
 489  * \param[in]     def_value  Default to use if option not configured
 490  *
 491  * \return Option value (from supplied options table or default value)
 492  */
 493 static const char *
 494 cluster_option_value(GHashTable *options, bool (*validate)(const char *),
     /* [previous][next][first][last][top][bottom][index][help] */
 495                      const char *name, const char *old_name,
 496                      const char *def_value)
 497 {
 498     const char *value = NULL;
 499     char *new_value = NULL;
 500 
 501     CRM_ASSERT(name != NULL);
 502 
 503     if (options) {
 504         value = g_hash_table_lookup(options, name);
 505 
 506         if ((value == NULL) && old_name) {
 507             value = g_hash_table_lookup(options, old_name);
 508             if (value != NULL) {
 509                 pcmk__config_warn("Support for legacy name '%s' for cluster "
 510                                   "option '%s' is deprecated and will be "
 511                                   "removed in a future release",
 512                                   old_name, name);
 513 
 514                 // Inserting copy with current name ensures we only warn once
 515                 new_value = strdup(value);
 516                 g_hash_table_insert(options, strdup(name), new_value);
 517                 value = new_value;
 518             }
 519         }
 520 
 521         if (value && validate && (validate(value) == FALSE)) {
 522             pcmk__config_err("Using default value for cluster option '%s' "
 523                              "because '%s' is invalid", name, value);
 524             value = NULL;
 525         }
 526 
 527         if (value) {
 528             return value;
 529         }
 530     }
 531 
 532     // No value found, use default
 533     value = def_value;
 534 
 535     if (value == NULL) {
 536         crm_trace("No value or default provided for cluster option '%s'",
 537                   name);
 538         return NULL;
 539     }
 540 
 541     if (validate) {
 542         CRM_CHECK(validate(value) != FALSE,
 543                   crm_err("Bug: default value for cluster option '%s' is invalid", name);
 544                   return NULL);
 545     }
 546 
 547     crm_trace("Using default value '%s' for cluster option '%s'",
 548               value, name);
 549     if (options) {
 550         new_value = strdup(value);
 551         g_hash_table_insert(options, strdup(name), new_value);
 552         value = new_value;
 553     }
 554     return value;
 555 }
 556 
 557 /*!
 558  * \internal
 559  * \brief Get the value of a cluster option
 560  *
 561  * \param[in,out] options      Name/value pairs for configured options
 562  * \param[in]     option_list  Possible cluster options
 563  * \param[in]     name         (Primary) option name to look for
 564  *
 565  * \return Option value
 566  */
 567 const char *
 568 pcmk__cluster_option(GHashTable *options,
     /* [previous][next][first][last][top][bottom][index][help] */
 569                      const pcmk__cluster_option_t *option_list,
 570                      int len, const char *name)
 571 {
 572     const char *value = NULL;
 573 
 574     for (int lpc = 0; lpc < len; lpc++) {
 575         if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
 576             value = cluster_option_value(options, option_list[lpc].is_valid,
 577                                          option_list[lpc].name,
 578                                          option_list[lpc].alt_name,
 579                                          option_list[lpc].default_value);
 580             return value;
 581         }
 582     }
 583     CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
 584     return NULL;
 585 }
 586 
 587 /*!
 588  * \internal
 589  * \brief Add a description element to a meta-data string
 590  *
 591  * \param[in,out] s       Meta-data string to add to
 592  * \param[in]     tag     Name of element to add ("longdesc" or "shortdesc")
 593  * \param[in]     desc    Textual description to add
 594  * \param[in]     values  If not \p NULL, the allowed values for the parameter
 595  * \param[in]     spaces  If not \p NULL, spaces to insert at the beginning of
 596  *                        each line
 597  */
 598 static void
 599 add_desc(GString *s, const char *tag, const char *desc, const char *values,
     /* [previous][next][first][last][top][bottom][index][help] */
 600          const char *spaces)
 601 {
 602     char *escaped_en = crm_xml_escape(desc);
 603 
 604     if (spaces != NULL) {
 605         g_string_append(s, spaces);
 606     }
 607     pcmk__g_strcat(s, "<", tag, " lang=\"en\">", escaped_en, NULL);
 608 
 609     if (values != NULL) {
 610         pcmk__g_strcat(s, "  Allowed values: ", values, NULL);
 611     }
 612     pcmk__g_strcat(s, "</", tag, ">\n", NULL);
 613 
 614 #ifdef ENABLE_NLS
 615     {
 616         static const char *locale = NULL;
 617 
 618         char *localized = crm_xml_escape(_(desc));
 619 
 620         if (strcmp(escaped_en, localized) != 0) {
 621             if (locale == NULL) {
 622                 locale = strtok(setlocale(LC_ALL, NULL), "_");
 623             }
 624 
 625             if (spaces != NULL) {
 626                 g_string_append(s, spaces);
 627             }
 628             pcmk__g_strcat(s, "<", tag, " lang=\"", locale, "\">", localized,
 629                            NULL);
 630 
 631             if (values != NULL) {
 632                 pcmk__g_strcat(s, _("  Allowed values: "), _(values), NULL);
 633             }
 634             pcmk__g_strcat(s, "</", tag, ">\n", NULL);
 635         }
 636         free(localized);
 637     }
 638 #endif
 639 
 640     free(escaped_en);
 641 }
 642 
 643 gchar *
 644 pcmk__format_option_metadata(const char *name, const char *desc_short,
     /* [previous][next][first][last][top][bottom][index][help] */
 645                              const char *desc_long,
 646                              pcmk__cluster_option_t *option_list, int len)
 647 {
 648     /* big enough to hold "pacemaker-schedulerd metadata" output */
 649     GString *s = g_string_sized_new(13000);
 650 
 651     pcmk__g_strcat(s,
 652                    "<?xml version=\"1.0\"?>\n"
 653                    "<resource-agent name=\"", name, "\" "
 654                                    "version=\"" PACEMAKER_VERSION "\">\n"
 655                    "  <version>" PCMK_OCF_VERSION "</version>\n", NULL);
 656 
 657     add_desc(s, "longdesc", desc_long, NULL, "  ");
 658     add_desc(s, "shortdesc", desc_short, NULL, "  ");
 659 
 660     g_string_append(s, "  <parameters>\n");
 661 
 662     for (int lpc = 0; lpc < len; lpc++) {
 663         const char *opt_name = option_list[lpc].name;
 664         const char *opt_type = option_list[lpc].type;
 665         const char *opt_values = option_list[lpc].values;
 666         const char *opt_default = option_list[lpc].default_value;
 667         const char *opt_desc_short = option_list[lpc].description_short;
 668         const char *opt_desc_long = option_list[lpc].description_long;
 669 
 670         // The standard requires long and short parameter descriptions
 671         CRM_ASSERT((opt_desc_short != NULL) || (opt_desc_long != NULL));
 672 
 673         if (opt_desc_short == NULL) {
 674             opt_desc_short = opt_desc_long;
 675         } else if (opt_desc_long == NULL) {
 676             opt_desc_long = opt_desc_short;
 677         }
 678 
 679         // The standard requires a parameter type
 680         CRM_ASSERT(opt_type != NULL);
 681 
 682         pcmk__g_strcat(s, "    <parameter name=\"", opt_name, "\">\n", NULL);
 683 
 684         add_desc(s, "longdesc", opt_desc_long, opt_values, "      ");
 685         add_desc(s, "shortdesc", opt_desc_short, NULL, "      ");
 686 
 687         pcmk__g_strcat(s, "      <content type=\"", opt_type, "\"", NULL);
 688         if (opt_default != NULL) {
 689             pcmk__g_strcat(s, " default=\"", opt_default, "\"", NULL);
 690         }
 691 
 692         if ((opt_values != NULL) && (strcmp(opt_type, "select") == 0)) {
 693             char *str = strdup(opt_values);
 694             const char *delim = ", ";
 695             char *ptr = strtok(str, delim);
 696 
 697             g_string_append(s, ">\n");
 698 
 699             while (ptr != NULL) {
 700                 pcmk__g_strcat(s, "        <option value=\"", ptr, "\" />\n",
 701                                NULL);
 702                 ptr = strtok(NULL, delim);
 703             }
 704             g_string_append_printf(s, "      </content>\n");
 705             free(str);
 706 
 707         } else {
 708             g_string_append(s, "/>\n");
 709         }
 710 
 711         g_string_append(s, "    </parameter>\n");
 712     }
 713     g_string_append(s, "  </parameters>\n</resource-agent>\n");
 714 
 715     return g_string_free(s, FALSE);
 716 }
 717 
 718 void
 719 pcmk__validate_cluster_options(GHashTable *options,
     /* [previous][next][first][last][top][bottom][index][help] */
 720                                pcmk__cluster_option_t *option_list, int len)
 721 {
 722     for (int lpc = 0; lpc < len; lpc++) {
 723         cluster_option_value(options, option_list[lpc].is_valid,
 724                              option_list[lpc].name,
 725                              option_list[lpc].alt_name,
 726                              option_list[lpc].default_value);
 727     }
 728 }

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