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_utilization
  16. cluster_option_value
  17. pcmk__cluster_option
  18. pcmk__print_option_metadata
  19. pcmk__validate_cluster_options

   1 /*
   2  * Copyright 2004-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 #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 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()
     /* [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(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                       pcmk__cli_option_t *long_options, const char *app_desc)
 107 {
 108     if (short_options) {
 109         crm_short_options = strdup(short_options);
 110 
 111     } else if (long_options) {
 112         int lpc = 0;
 113         int opt_string_len = 0;
 114         char *local_short_options = NULL;
 115 
 116         for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
 117             if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
 118                 local_short_options = pcmk__realloc(local_short_options,
 119                                                     opt_string_len + 4);
 120                 local_short_options[opt_string_len++] = long_options[lpc].val;
 121                 /* getopt(3) says: Two colons mean an option takes an optional arg; */
 122                 if (long_options[lpc].has_arg == optional_argument) {
 123                     local_short_options[opt_string_len++] = ':';
 124                 }
 125                 if (long_options[lpc].has_arg >= required_argument) {
 126                     local_short_options[opt_string_len++] = ':';
 127                 }
 128                 local_short_options[opt_string_len] = 0;
 129             }
 130         }
 131         crm_short_options = local_short_options;
 132         crm_trace("Generated short option string: '%s'", local_short_options);
 133     }
 134 
 135     if (long_options) {
 136         crm_long_options = long_options;
 137     }
 138     if (app_desc) {
 139         crm_app_description = app_desc;
 140     }
 141     if (app_usage) {
 142         crm_app_usage = app_usage;
 143     }
 144 }
 145 
 146 int
 147 pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
     /* [previous][next][first][last][top][bottom][index][help] */
 148 {
 149 #ifdef HAVE_GETOPT_H
 150     static struct option *long_opts = NULL;
 151 
 152     if (long_opts == NULL && crm_long_options) {
 153         long_opts = create_long_opts(crm_long_options);
 154     }
 155 
 156     *index = 0;
 157     if (long_opts) {
 158         int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
 159 
 160         switch (flag) {
 161             case 0:
 162                 if (long_opts[*index].val) {
 163                     return long_opts[*index].val;
 164                 } else if (longname) {
 165                     *longname = long_opts[*index].name;
 166                 } else {
 167                     crm_notice("Unhandled option --%s", long_opts[*index].name);
 168                     return flag;
 169                 }
 170             case -1:           /* End of option processing */
 171                 break;
 172             case ':':
 173                 crm_trace("Missing argument");
 174                 pcmk__cli_help('?', CRM_EX_USAGE);
 175                 break;
 176             case '?':
 177                 pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE));
 178                 break;
 179         }
 180         return flag;
 181     }
 182 #endif
 183 
 184     if (crm_short_options) {
 185         return getopt(argc, argv, crm_short_options);
 186     }
 187 
 188     return -1;
 189 }
 190 
 191 void
 192 pcmk__cli_help(char cmd, crm_exit_t exit_code)
     /* [previous][next][first][last][top][bottom][index][help] */
 193 {
 194     int i = 0;
 195     FILE *stream = (exit_code ? stderr : stdout);
 196 
 197     if (cmd == 'v' || cmd == '$') {
 198         fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
 199         fprintf(stream, "Written by Andrew Beekhof\n");
 200         goto out;
 201     }
 202 
 203     if (cmd == '!') {
 204         fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
 205         goto out;
 206     }
 207 
 208     fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
 209 
 210     if (crm_app_usage) {
 211         fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
 212     }
 213 
 214     if (crm_long_options) {
 215         fprintf(stream, "Options:\n");
 216         for (i = 0; crm_long_options[i].name != NULL; i++) {
 217             if (crm_long_options[i].flags & pcmk__option_hidden) {
 218 
 219             } else if (crm_long_options[i].flags & pcmk__option_paragraph) {
 220                 fprintf(stream, "%s\n\n", crm_long_options[i].desc);
 221 
 222             } else if (crm_long_options[i].flags & pcmk__option_example) {
 223                 fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
 224 
 225             } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
 226                 fprintf(stream, "%s\n", crm_long_options[i].desc);
 227 
 228             } else {
 229                 /* is val printable as char ? */
 230                 if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
 231                     fprintf(stream, " -%c,", crm_long_options[i].val);
 232                 } else {
 233                     fputs("    ", stream);
 234                 }
 235                 fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
 236                         crm_long_options[i].has_arg == optional_argument ? "[=value]" :
 237                         crm_long_options[i].has_arg == required_argument ? "=value" : "",
 238                         crm_long_options[i].desc ? crm_long_options[i].desc : "");
 239             }
 240         }
 241 
 242     } else if (crm_short_options) {
 243         fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
 244         for (i = 0; crm_short_options[i] != 0; i++) {
 245             int has_arg = no_argument /* 0 */;
 246 
 247             if (crm_short_options[i + 1] == ':') {
 248                 if (crm_short_options[i + 2] == ':')
 249                     has_arg = optional_argument /* 2 */;
 250                 else
 251                     has_arg = required_argument /* 1 */;
 252             }
 253 
 254             fprintf(stream, " -%c %s\n", crm_short_options[i],
 255                     has_arg == optional_argument ? "[value]" :
 256                     has_arg == required_argument ? "{value}" : "");
 257             i += has_arg;
 258         }
 259     }
 260 
 261     fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
 262 
 263   out:
 264     crm_exit(exit_code);
 265     while(1); // above does not return
 266 }
 267 
 268 
 269 /*
 270  * Environment variable option handling
 271  */
 272 
 273 /*!
 274  * \internal
 275  * \brief Get the value of a Pacemaker environment variable option
 276  *
 277  * If an environment variable option is set, with either a PCMK_ or (for
 278  * backward compatibility) HA_ prefix, log and return the value.
 279  *
 280  * \param[in] option  Environment variable name (without prefix)
 281  *
 282  * \return Value of environment variable option
 283  */
 284 const char *
 285 pcmk__env_option(const char *option)
     /* [previous][next][first][last][top][bottom][index][help] */
 286 {
 287     char env_name[NAME_MAX];
 288     const char *value = NULL;
 289 
 290     snprintf(env_name, NAME_MAX, "PCMK_%s", option);
 291     value = getenv(env_name);
 292     if (value != NULL) {
 293         crm_trace("Found %s = %s", env_name, value);
 294         return value;
 295     }
 296 
 297     snprintf(env_name, NAME_MAX, "HA_%s", option);
 298     value = getenv(env_name);
 299     if (value != NULL) {
 300         crm_trace("Found %s = %s", env_name, value);
 301         return value;
 302     }
 303 
 304     crm_trace("Nothing found for %s", option);
 305     return NULL;
 306 }
 307 
 308 /*!
 309  * \brief Set or unset a Pacemaker environment variable option
 310  *
 311  * Set an environment variable option with both a PCMK_ and (for
 312  * backward compatibility) HA_ prefix.
 313  *
 314  * \param[in] option  Environment variable name (without prefix)
 315  * \param[in] value   New value (or NULL to unset)
 316  */
 317 void
 318 pcmk__set_env_option(const char *option, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 319 {
 320     char env_name[NAME_MAX];
 321 
 322     snprintf(env_name, NAME_MAX, "PCMK_%s", option);
 323     if (value) {
 324         crm_trace("Setting %s to %s", env_name, value);
 325         setenv(env_name, value, 1);
 326     } else {
 327         crm_trace("Unsetting %s", env_name);
 328         unsetenv(env_name);
 329     }
 330 
 331     snprintf(env_name, NAME_MAX, "HA_%s", option);
 332     if (value) {
 333         crm_trace("Setting %s to %s", env_name, value);
 334         setenv(env_name, value, 1);
 335     } else {
 336         crm_trace("Unsetting %s", env_name);
 337         unsetenv(env_name);
 338     }
 339 }
 340 
 341 /*!
 342  * \internal
 343  * \brief Check whether Pacemaker environment variable option is enabled
 344  *
 345  * Given a Pacemaker environment variable option that can either be boolean
 346  * or a list of daemon names, return true if the option is enabled for a given
 347  * daemon.
 348  *
 349  * \param[in] daemon   Daemon name
 350  * \param[in] option   Pacemaker environment variable name
 351  *
 352  * \return true if variable is enabled for daemon, otherwise false
 353  */
 354 bool
 355 pcmk__env_option_enabled(const char *daemon, const char *option)
     /* [previous][next][first][last][top][bottom][index][help] */
 356 {
 357     const char *value = pcmk__env_option(option);
 358 
 359     return (value != NULL) && (crm_is_true(value) || strstr(value, daemon));
 360 }
 361 
 362 
 363 /*
 364  * Cluster option handling
 365  */
 366 
 367 bool
 368 pcmk__valid_interval_spec(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 369 {
 370     (void) crm_parse_interval_spec(value);
 371     return errno == 0;
 372 }
 373 
 374 bool
 375 pcmk__valid_boolean(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 376 {
 377     int tmp;
 378 
 379     return crm_str_to_boolean(value, &tmp) == 1;
 380 }
 381 
 382 bool
 383 pcmk__valid_number(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 384 {
 385     if (value == NULL) {
 386         return false;
 387 
 388     } else if (pcmk_str_is_minus_infinity(value) ||
 389                pcmk_str_is_infinity(value)) {
 390         return true;
 391     }
 392 
 393     return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok;
 394 }
 395 
 396 bool
 397 pcmk__valid_positive_number(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 398 {
 399     long long num = 0LL;
 400 
 401     return pcmk_str_is_infinity(value)
 402            || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0));
 403 }
 404 
 405 bool
 406 pcmk__valid_quorum(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 407 {
 408     return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
 409 }
 410 
 411 bool
 412 pcmk__valid_script(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 413 {
 414     struct stat st;
 415 
 416     if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) {
 417         return true;
 418     }
 419 
 420     if (stat(value, &st) != 0) {
 421         crm_err("Script %s does not exist", value);
 422         return false;
 423     }
 424 
 425     if (S_ISREG(st.st_mode) == 0) {
 426         crm_err("Script %s is not a regular file", value);
 427         return false;
 428     }
 429 
 430     if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
 431         crm_err("Script %s is not executable", value);
 432         return false;
 433     }
 434 
 435     return true;
 436 }
 437 
 438 bool
 439 pcmk__valid_utilization(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 440 {
 441     char *end = NULL;
 442     long number = strtol(value, &end, 10);
 443 
 444     if (end && (end[0] != '%')) {
 445         return false;
 446     }
 447     return number >= 0;
 448 }
 449 
 450 /*!
 451  * \internal
 452  * \brief Check a table of configured options for a particular option
 453  *
 454  * \param[in] options    Name/value pairs for configured options
 455  * \param[in] validate   If not NULL, validator function for option value
 456  * \param[in] name       Option name to look for
 457  * \param[in] old_name   Alternative option name to look for
 458  * \param[in] def_value  Default to use if option not configured
 459  *
 460  * \return Option value (from supplied options table or default value)
 461  */
 462 static const char *
 463 cluster_option_value(GHashTable *options, bool (*validate)(const char *),
     /* [previous][next][first][last][top][bottom][index][help] */
 464                      const char *name, const char *old_name,
 465                      const char *def_value)
 466 {
 467     const char *value = NULL;
 468     char *new_value = NULL;
 469 
 470     CRM_ASSERT(name != NULL);
 471 
 472     if (options) {
 473         value = g_hash_table_lookup(options, name);
 474 
 475         if ((value == NULL) && old_name) {
 476             value = g_hash_table_lookup(options, old_name);
 477             if (value != NULL) {
 478                 pcmk__config_warn("Support for legacy name '%s' for cluster "
 479                                   "option '%s' is deprecated and will be "
 480                                   "removed in a future release",
 481                                   old_name, name);
 482 
 483                 // Inserting copy with current name ensures we only warn once
 484                 new_value = strdup(value);
 485                 g_hash_table_insert(options, strdup(name), new_value);
 486                 value = new_value;
 487             }
 488         }
 489 
 490         if (value && validate && (validate(value) == FALSE)) {
 491             pcmk__config_err("Using default value for cluster option '%s' "
 492                              "because '%s' is invalid", name, value);
 493             value = NULL;
 494         }
 495 
 496         if (value) {
 497             return value;
 498         }
 499     }
 500 
 501     // No value found, use default
 502     value = def_value;
 503 
 504     if (value == NULL) {
 505         crm_trace("No value or default provided for cluster option '%s'",
 506                   name);
 507         return NULL;
 508     }
 509 
 510     if (validate) {
 511         CRM_CHECK(validate(value) != FALSE,
 512                   crm_err("Bug: default value for cluster option '%s' is invalid", name);
 513                   return NULL);
 514     }
 515 
 516     crm_trace("Using default value '%s' for cluster option '%s'",
 517               value, name);
 518     if (options) {
 519         new_value = strdup(value);
 520         g_hash_table_insert(options, strdup(name), new_value);
 521         value = new_value;
 522     }
 523     return value;
 524 }
 525 
 526 /*!
 527  * \internal
 528  * \brief Get the value of a cluster option
 529  *
 530  * \param[in] options      Name/value pairs for configured options
 531  * \param[in] option_list  Possible cluster options
 532  * \param[in] name         (Primary) option name to look for
 533  *
 534  * \return Option value
 535  */
 536 const char *
 537 pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list,
     /* [previous][next][first][last][top][bottom][index][help] */
 538                      int len, const char *name)
 539 {
 540     const char *value = NULL;
 541 
 542     for (int lpc = 0; lpc < len; lpc++) {
 543         if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
 544             value = cluster_option_value(options, option_list[lpc].is_valid,
 545                                          option_list[lpc].name,
 546                                          option_list[lpc].alt_name,
 547                                          option_list[lpc].default_value);
 548             return value;
 549         }
 550     }
 551     CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
 552     return NULL;
 553 }
 554 
 555 void
 556 pcmk__print_option_metadata(const char *name, const char *desc_short,
     /* [previous][next][first][last][top][bottom][index][help] */
 557                             const char *desc_long,
 558                             pcmk__cluster_option_t *option_list, int len)
 559 {
 560     int lpc = 0;
 561 
 562     fprintf(stdout, "<?xml version=\"1.0\"?>"
 563             "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
 564             "<resource-agent name=\"%s\">\n"
 565             "  <version>%s</version>\n"
 566             "  <longdesc lang=\"en\">%s</longdesc>\n"
 567             "  <shortdesc lang=\"en\">%s</shortdesc>\n"
 568             "  <parameters>\n", name, PCMK_OCF_VERSION, desc_long, desc_short);
 569 
 570     for (lpc = 0; lpc < len; lpc++) {
 571         if ((option_list[lpc].description_long == NULL)
 572             && (option_list[lpc].description_short == NULL)) {
 573             continue;
 574         }
 575 
 576         fprintf(stdout, "    <parameter name=\"%s\">\n"
 577                 "      <shortdesc lang=\"en\">%s</shortdesc>\n"
 578                 "      <longdesc lang=\"en\">%s%s%s</longdesc>\n",
 579                 option_list[lpc].name,
 580                 option_list[lpc].description_short,
 581                 option_list[lpc].description_long?
 582                     option_list[lpc].description_long :
 583                     option_list[lpc].description_short,
 584                 (option_list[lpc].values? "  Allowed values: " : ""),
 585                 (option_list[lpc].values? option_list[lpc].values : ""));
 586 
 587         if (option_list[lpc].values && !strcmp(option_list[lpc].type, "select")) {
 588             char *str = strdup(option_list[lpc].values);
 589             char delim[] = ", ";
 590             char *ptr = strtok(str, delim);
 591 
 592             fprintf(stdout, "      <content type=\"%s\" default=\"%s\">\n",
 593                 option_list[lpc].type,
 594                 option_list[lpc].default_value
 595             );
 596 
 597             while (ptr != NULL) {
 598                 fprintf(stdout, "        <option value=\"%s\" />\n", ptr);
 599                 ptr = strtok(NULL, delim);
 600             }
 601 
 602             fprintf(stdout, "      </content>\n");
 603             free(str);
 604 
 605         } else {
 606             fprintf(stdout, "      <content type=\"%s\" default=\"%s\"/>\n",
 607                 option_list[lpc].type,
 608                 option_list[lpc].default_value
 609             );
 610         }
 611 
 612         fprintf(stdout, "    </parameter>\n");
 613     }
 614     fprintf(stdout, "  </parameters>\n</resource-agent>\n");
 615 }
 616 
 617 void
 618 pcmk__validate_cluster_options(GHashTable *options,
     /* [previous][next][first][last][top][bottom][index][help] */
 619                                pcmk__cluster_option_t *option_list, int len)
 620 {
 621     for (int lpc = 0; lpc < len; lpc++) {
 622         cluster_option_value(options, option_list[lpc].is_valid,
 623                              option_list[lpc].name,
 624                              option_list[lpc].alt_name,
 625                              option_list[lpc].default_value);
 626     }
 627 }

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