diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h index f29ba3f14d..f15b1fd1b8 100644 --- a/include/crm/common/options_internal.h +++ b/include/crm/common/options_internal.h @@ -1,163 +1,158 @@ /* * Copyright 2006-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__OPTIONS_INTERNAL__H # define PCMK__OPTIONS_INTERNAL__H # ifndef PCMK__CONFIG_H # define PCMK__CONFIG_H # include // HAVE_GETOPT, _Noreturn # endif # include // GHashTable # include // bool /* * Command-line option handling * * This will all eventually go away as everything is converted to use GOption */ # ifdef HAVE_GETOPT_H # include # else # define no_argument 0 # define required_argument 1 # endif enum pcmk__cli_option_flags { pcmk__option_default = (1 << 0), pcmk__option_hidden = (1 << 1), pcmk__option_paragraph = (1 << 2), pcmk__option_example = (1 << 3), }; typedef struct pcmk__cli_option_s { /* Fields from 'struct option' in getopt.h */ /* name of long option */ const char *name; /* * one of no_argument, required_argument, and optional_argument: * whether option takes an argument */ int has_arg; /* if not NULL, set *flag to val when option found */ int *flag; /* if flag not NULL, value to set *flag to; else return value */ int val; /* Custom fields */ const char *desc; long flags; } pcmk__cli_option_t; -void pcmk__set_cli_options(const char *short_options, const char *usage, - const pcmk__cli_option_t *long_options, - const char *app_desc); -int pcmk__next_cli_option(int argc, char **argv, int *index, - const char **longname); _Noreturn void pcmk__cli_help(char cmd, crm_exit_t exit_code); void pcmk__cli_option_cleanup(void); /* * Environment variable option handling */ const char *pcmk__env_option(const char *option); void pcmk__set_env_option(const char *option, const char *value); bool pcmk__env_option_enabled(const char *daemon, const char *option); /* * Cluster option handling */ typedef struct pcmk__cluster_option_s { const char *name; const char *alt_name; const char *type; const char *values; const char *default_value; bool (*is_valid)(const char *); const char *description_short; const char *description_long; } pcmk__cluster_option_t; const char *pcmk__cluster_option(GHashTable *options, const pcmk__cluster_option_t *option_list, int len, const char *name); gchar *pcmk__format_option_metadata(const char *name, const char *desc_short, const char *desc_long, pcmk__cluster_option_t *option_list, int len); void pcmk__validate_cluster_options(GHashTable *options, pcmk__cluster_option_t *option_list, int len); bool pcmk__valid_interval_spec(const char *value); bool pcmk__valid_boolean(const char *value); bool pcmk__valid_number(const char *value); bool pcmk__valid_positive_number(const char *value); bool pcmk__valid_quorum(const char *value); bool pcmk__valid_script(const char *value); bool pcmk__valid_percentage(const char *value); // from watchdog.c long pcmk__get_sbd_timeout(void); bool pcmk__get_sbd_sync_resource_startup(void); long pcmk__auto_watchdog_timeout(void); bool pcmk__valid_sbd_timeout(const char *value); // Constants for environment variable names #define PCMK__ENV_BLACKBOX "blackbox" #define PCMK__ENV_CLUSTER_TYPE "cluster_type" #define PCMK__ENV_DEBUG "debug" #define PCMK__ENV_LOGFACILITY "logfacility" #define PCMK__ENV_LOGFILE "logfile" #define PCMK__ENV_LOGPRIORITY "logpriority" #define PCMK__ENV_MCP "mcp" #define PCMK__ENV_NODE_START_STATE "node_start_state" #define PCMK__ENV_PHYSICAL_HOST "physical_host" #define PCMK__ENV_QUORUM_TYPE "quorum_type" #define PCMK__ENV_SHUTDOWN_DELAY "shutdown_delay" #define PCMK__ENV_STDERR "stderr" // Constants for cluster option names #define PCMK__OPT_NODE_HEALTH_BASE "node-health-base" #define PCMK__OPT_NODE_HEALTH_GREEN "node-health-green" #define PCMK__OPT_NODE_HEALTH_RED "node-health-red" #define PCMK__OPT_NODE_HEALTH_STRATEGY "node-health-strategy" #define PCMK__OPT_NODE_HEALTH_YELLOW "node-health-yellow" // Constants for meta-attribute names #define PCMK__META_ALLOW_UNHEALTHY_NODES "allow-unhealthy-nodes" // Constants for enumerated values for various options #define PCMK__VALUE_CLUSTER "cluster" #define PCMK__VALUE_CUSTOM "custom" #define PCMK__VALUE_FENCING "fencing" #define PCMK__VALUE_GREEN "green" #define PCMK__VALUE_LOCAL "local" #define PCMK__VALUE_MIGRATE_ON_RED "migrate-on-red" #define PCMK__VALUE_NONE "none" #define PCMK__VALUE_NOTHING "nothing" #define PCMK__VALUE_ONLY_GREEN "only-green" #define PCMK__VALUE_PROGRESSIVE "progressive" #define PCMK__VALUE_QUORUM "quorum" #define PCMK__VALUE_RED "red" #define PCMK__VALUE_UNFENCING "unfencing" #define PCMK__VALUE_YELLOW "yellow" #endif // PCMK__OPTIONS_INTERNAL__H diff --git a/lib/common/options.c b/lib/common/options.c index 215d9e0633..0aae9eab2e 100644 --- a/lib/common/options.c +++ b/lib/common/options.c @@ -1,669 +1,517 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #ifdef HAVE_GETOPT_H # include #endif #include /* * Command-line option handling */ static char *crm_short_options = NULL; -static const pcmk__cli_option_t *crm_long_options = NULL; -static const char *crm_app_description = NULL; -static const char *crm_app_usage = NULL; void pcmk__cli_option_cleanup(void) { free(crm_short_options); crm_short_options = NULL; } -static struct option * -create_long_opts(const pcmk__cli_option_t *long_options) -{ - struct option *long_opts = NULL; - -#ifdef HAVE_GETOPT_H - int index = 0, lpc = 0; - - /* - * A previous, possibly poor, choice of '?' as the short form of --help - * means that getopt_long() returns '?' for both --help and for "unknown option" - * - * This dummy entry allows us to differentiate between the two in - * pcmk__next_cli_option() and exit with the correct error code. - */ - long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option)); - long_opts[index].name = "__dummmy__"; - long_opts[index].has_arg = 0; - long_opts[index].flag = 0; - long_opts[index].val = '_'; - index++; - - // cppcheck seems not to understand the abort-logic in pcmk__realloc - // cppcheck-suppress memleak - for (lpc = 0; long_options[lpc].name != NULL; lpc++) { - if (long_options[lpc].name[0] == '-') { - continue; - } - - long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option)); - /*fprintf(stderr, "Creating %d %s = %c\n", index, - * long_options[lpc].name, long_options[lpc].val); */ - long_opts[index].name = long_options[lpc].name; - long_opts[index].has_arg = long_options[lpc].has_arg; - long_opts[index].flag = long_options[lpc].flag; - long_opts[index].val = long_options[lpc].val; - index++; - } - - /* Now create the list terminator */ - long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option)); - long_opts[index].name = NULL; - long_opts[index].has_arg = 0; - long_opts[index].flag = 0; - long_opts[index].val = 0; -#endif - - return long_opts; -} - -/*! - * \internal - * \brief Define the command-line options a daemon or tool accepts - * - * \param[in] short_options getopt(3)-style short option list - * \param[in] app_usage summary of how command is invoked (for help) - * \param[in] long_options definition of options accepted - * \param[in] app_desc brief command description (for help) - */ -void -pcmk__set_cli_options(const char *short_options, const char *app_usage, - const pcmk__cli_option_t *long_options, - const char *app_desc) -{ - if (short_options) { - crm_short_options = strdup(short_options); - - } else if (long_options) { - int lpc = 0; - int opt_string_len = 0; - char *local_short_options = NULL; - - for (lpc = 0; long_options[lpc].name != NULL; lpc++) { - if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) { - local_short_options = pcmk__realloc(local_short_options, - opt_string_len + 4); - local_short_options[opt_string_len++] = long_options[lpc].val; - /* getopt(3) says: Two colons mean an option takes an optional arg; */ - if (long_options[lpc].has_arg == optional_argument) { - local_short_options[opt_string_len++] = ':'; - } - if (long_options[lpc].has_arg >= required_argument) { - local_short_options[opt_string_len++] = ':'; - } - local_short_options[opt_string_len] = 0; - } - } - crm_short_options = local_short_options; - crm_trace("Generated short option string: '%s'", local_short_options); - } - - if (long_options) { - crm_long_options = long_options; - } - if (app_desc) { - crm_app_description = app_desc; - } - if (app_usage) { - crm_app_usage = app_usage; - } -} - -int -pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname) -{ -#ifdef HAVE_GETOPT_H - static struct option *long_opts = NULL; - - if (long_opts == NULL && crm_long_options) { - long_opts = create_long_opts(crm_long_options); - } - - *index = 0; - if (long_opts) { - int flag = getopt_long(argc, argv, crm_short_options, long_opts, index); - - switch (flag) { - case 0: - if (long_opts[*index].val) { - return long_opts[*index].val; - } else if (longname) { - *longname = long_opts[*index].name; - } else { - crm_notice("Unhandled option --%s", long_opts[*index].name); - return flag; - } - break; - - case -1: /* End of option processing */ - break; - case ':': - crm_trace("Missing argument"); - pcmk__cli_help('?', CRM_EX_USAGE); - break; - case '?': - pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE)); - break; - } - return flag; - } -#endif - - if (crm_short_options) { - return getopt(argc, argv, crm_short_options); - } - - return -1; -} - void pcmk__cli_help(char cmd, crm_exit_t exit_code) { FILE *stream = (exit_code ? stderr : stdout); if (cmd == 'v' || cmd == '$') { fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION); fprintf(stream, "Written by Andrew Beekhof and " "the Pacemaker project contributors\n"); } else if (cmd == '!') { fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } crm_exit(exit_code); while(1); // above does not return } /* * Environment variable option handling */ /*! * \internal * \brief Get the value of a Pacemaker environment variable option * * If an environment variable option is set, with either a PCMK_ or (for * backward compatibility) HA_ prefix, log and return the value. * * \param[in] option Environment variable name (without prefix) * * \return Value of environment variable option, or NULL in case of * option name too long or value not found */ const char * pcmk__env_option(const char *option) { const char *const prefixes[] = {"PCMK_", "HA_"}; char env_name[NAME_MAX]; const char *value = NULL; CRM_CHECK(!pcmk__str_empty(option), return NULL); for (int i = 0; i < PCMK__NELEM(prefixes); i++) { int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option); if (rv < 0) { crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option, strerror(errno)); return NULL; } if (rv >= sizeof(env_name)) { crm_trace("\"%s%s\" is too long", prefixes[i], option); continue; } value = getenv(env_name); if (value != NULL) { crm_trace("Found %s = %s", env_name, value); return value; } } crm_trace("Nothing found for %s", option); return NULL; } /*! * \brief Set or unset a Pacemaker environment variable option * * Set an environment variable option with both a PCMK_ and (for * backward compatibility) HA_ prefix. * * \param[in] option Environment variable name (without prefix) * \param[in] value New value (or NULL to unset) */ void pcmk__set_env_option(const char *option, const char *value) { const char *const prefixes[] = {"PCMK_", "HA_"}; char env_name[NAME_MAX]; CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL), return); for (int i = 0; i < PCMK__NELEM(prefixes); i++) { int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option); if (rv < 0) { crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option, strerror(errno)); return; } if (rv >= sizeof(env_name)) { crm_trace("\"%s%s\" is too long", prefixes[i], option); continue; } if (value != NULL) { crm_trace("Setting %s to %s", env_name, value); rv = setenv(env_name, value, 1); } else { crm_trace("Unsetting %s", env_name); rv = unsetenv(env_name); } if (rv < 0) { crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un", env_name, strerror(errno)); } } } /*! * \internal * \brief Check whether Pacemaker environment variable option is enabled * * Given a Pacemaker environment variable option that can either be boolean * or a list of daemon names, return true if the option is enabled for a given * daemon. * * \param[in] daemon Daemon name (can be NULL) * \param[in] option Pacemaker environment variable name * * \return true if variable is enabled for daemon, otherwise false */ bool pcmk__env_option_enabled(const char *daemon, const char *option) { const char *value = pcmk__env_option(option); return (value != NULL) && (crm_is_true(value) || ((daemon != NULL) && (strstr(value, daemon) != NULL))); } /* * Cluster option handling */ bool pcmk__valid_interval_spec(const char *value) { (void) crm_parse_interval_spec(value); return errno == 0; } bool pcmk__valid_boolean(const char *value) { int tmp; return crm_str_to_boolean(value, &tmp) == 1; } bool pcmk__valid_number(const char *value) { if (value == NULL) { return false; } else if (pcmk_str_is_minus_infinity(value) || pcmk_str_is_infinity(value)) { return true; } return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok; } bool pcmk__valid_positive_number(const char *value) { long long num = 0LL; return pcmk_str_is_infinity(value) || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0)); } bool pcmk__valid_quorum(const char *value) { return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL); } bool pcmk__valid_script(const char *value) { struct stat st; if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) { return true; } if (stat(value, &st) != 0) { crm_err("Script %s does not exist", value); return false; } if (S_ISREG(st.st_mode) == 0) { crm_err("Script %s is not a regular file", value); return false; } if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) { crm_err("Script %s is not executable", value); return false; } return true; } bool pcmk__valid_percentage(const char *value) { char *end = NULL; long number = strtol(value, &end, 10); if (end && (end[0] != '%')) { return false; } return number >= 0; } /*! * \internal * \brief Check a table of configured options for a particular option * * \param[in,out] options Name/value pairs for configured options * \param[in] validate If not NULL, validator function for option value * \param[in] name Option name to look for * \param[in] old_name Alternative option name to look for * \param[in] def_value Default to use if option not configured * * \return Option value (from supplied options table or default value) */ static const char * cluster_option_value(GHashTable *options, bool (*validate)(const char *), const char *name, const char *old_name, const char *def_value) { const char *value = NULL; char *new_value = NULL; CRM_ASSERT(name != NULL); if (options) { value = g_hash_table_lookup(options, name); if ((value == NULL) && old_name) { value = g_hash_table_lookup(options, old_name); if (value != NULL) { pcmk__config_warn("Support for legacy name '%s' for cluster " "option '%s' is deprecated and will be " "removed in a future release", old_name, name); // Inserting copy with current name ensures we only warn once new_value = strdup(value); g_hash_table_insert(options, strdup(name), new_value); value = new_value; } } if (value && validate && (validate(value) == FALSE)) { pcmk__config_err("Using default value for cluster option '%s' " "because '%s' is invalid", name, value); value = NULL; } if (value) { return value; } } // No value found, use default value = def_value; if (value == NULL) { crm_trace("No value or default provided for cluster option '%s'", name); return NULL; } if (validate) { CRM_CHECK(validate(value) != FALSE, crm_err("Bug: default value for cluster option '%s' is invalid", name); return NULL); } crm_trace("Using default value '%s' for cluster option '%s'", value, name); if (options) { new_value = strdup(value); g_hash_table_insert(options, strdup(name), new_value); value = new_value; } return value; } /*! * \internal * \brief Get the value of a cluster option * * \param[in,out] options Name/value pairs for configured options * \param[in] option_list Possible cluster options * \param[in] len Length of \p option_list * \param[in] name (Primary) option name to look for * * \return Option value */ const char * pcmk__cluster_option(GHashTable *options, const pcmk__cluster_option_t *option_list, int len, const char *name) { const char *value = NULL; for (int lpc = 0; lpc < len; lpc++) { if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) { value = cluster_option_value(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); return value; } } CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name)); return NULL; } /*! * \internal * \brief Add a description element to a meta-data string * * \param[in,out] s Meta-data string to add to * \param[in] tag Name of element to add ("longdesc" or "shortdesc") * \param[in] desc Textual description to add * \param[in] values If not \p NULL, the allowed values for the parameter * \param[in] spaces If not \p NULL, spaces to insert at the beginning of * each line */ static void add_desc(GString *s, const char *tag, const char *desc, const char *values, const char *spaces) { char *escaped_en = crm_xml_escape(desc); if (spaces != NULL) { g_string_append(s, spaces); } pcmk__g_strcat(s, "<", tag, " lang=\"en\">", escaped_en, NULL); if (values != NULL) { pcmk__g_strcat(s, " Allowed values: ", values, NULL); } pcmk__g_strcat(s, "\n", NULL); #ifdef ENABLE_NLS { static const char *locale = NULL; char *localized = crm_xml_escape(_(desc)); if (strcmp(escaped_en, localized) != 0) { if (locale == NULL) { locale = strtok(setlocale(LC_ALL, NULL), "_"); } if (spaces != NULL) { g_string_append(s, spaces); } pcmk__g_strcat(s, "<", tag, " lang=\"", locale, "\">", localized, NULL); if (values != NULL) { pcmk__g_strcat(s, _(" Allowed values: "), _(values), NULL); } pcmk__g_strcat(s, "\n", NULL); } free(localized); } #endif free(escaped_en); } gchar * pcmk__format_option_metadata(const char *name, const char *desc_short, const char *desc_long, pcmk__cluster_option_t *option_list, int len) { /* big enough to hold "pacemaker-schedulerd metadata" output */ GString *s = g_string_sized_new(13000); pcmk__g_strcat(s, "\n" "\n" " " PCMK_OCF_VERSION "\n", NULL); add_desc(s, "longdesc", desc_long, NULL, " "); add_desc(s, "shortdesc", desc_short, NULL, " "); g_string_append(s, " \n"); for (int lpc = 0; lpc < len; lpc++) { const char *opt_name = option_list[lpc].name; const char *opt_type = option_list[lpc].type; const char *opt_values = option_list[lpc].values; const char *opt_default = option_list[lpc].default_value; const char *opt_desc_short = option_list[lpc].description_short; const char *opt_desc_long = option_list[lpc].description_long; // The standard requires long and short parameter descriptions CRM_ASSERT((opt_desc_short != NULL) || (opt_desc_long != NULL)); if (opt_desc_short == NULL) { opt_desc_short = opt_desc_long; } else if (opt_desc_long == NULL) { opt_desc_long = opt_desc_short; } // The standard requires a parameter type CRM_ASSERT(opt_type != NULL); pcmk__g_strcat(s, " \n", NULL); add_desc(s, "longdesc", opt_desc_long, opt_values, " "); add_desc(s, "shortdesc", opt_desc_short, NULL, " "); pcmk__g_strcat(s, " \n"); while (ptr != NULL) { pcmk__g_strcat(s, " \n"); free(str); } else { g_string_append(s, "/>\n"); } g_string_append(s, " \n"); } g_string_append(s, " \n\n"); return g_string_free(s, FALSE); } void pcmk__validate_cluster_options(GHashTable *options, pcmk__cluster_option_t *option_list, int len) { for (int lpc = 0; lpc < len; lpc++) { cluster_option_value(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } }