/* * Copyright 2024 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. */ #include #include // GSList, GString #include "crmcommon_private.h" /*! * \internal * \brief Output an option's possible values * * \param[in,out] out Output object * \param[in] option Option whose possible values to add */ static void add_possible_values_default(pcmk__output_t *out, const pcmk__cluster_option_t *option) { const char *id = _("Possible values"); GString *buf = g_string_sized_new(256); CRM_ASSERT(option->type != NULL); if (pcmk_is_set(option->flags, pcmk__opt_generated)) { id = _("Possible values (generated by Pacemaker)"); } if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) { const char *delim = ", "; bool found_default = (option->default_value == NULL); char *str = pcmk__str_copy(option->values); for (const char *value = strtok(str, delim); value != NULL; value = strtok(NULL, delim)) { if (buf->len > 0) { g_string_append(buf, delim); } g_string_append_c(buf, '"'); g_string_append(buf, value); g_string_append_c(buf, '"'); if (!found_default && (strcmp(value, option->default_value) == 0)) { found_default = true; g_string_append(buf, _(" (default)")); } } free(str); } else if (option->default_value != NULL) { pcmk__g_strcat(buf, option->type, _(" (default: \""), option->default_value, "\")", NULL); } else { pcmk__g_strcat(buf, option->type, _(" (no default)"), NULL); } out->list_item(out, id, "%s", buf->str); g_string_free(buf, TRUE); } /*! * \internal * \brief Output a single option's metadata * * \param[in,out] out Output object * \param[in] option Option to add */ static void add_option_metadata_default(pcmk__output_t *out, const pcmk__cluster_option_t *option) { const char *desc_short = option->description_short; const char *desc_long = option->description_long; CRM_ASSERT((desc_short != NULL) || (desc_long != NULL)); if (desc_short == NULL) { desc_short = desc_long; desc_long = NULL; } out->list_item(out, option->name, "%s", _(desc_short)); out->begin_list(out, NULL, NULL, NULL); if (desc_long != NULL) { out->list_item(out, NULL, "%s", _(desc_long)); } add_possible_values_default(out, option); out->end_list(out); } /*! * \internal * \brief Output the metadata for a list of options * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Fake resource agent name for the option list (ignored) * -# Short description of option list * -# Long description of option list * -# Filter: Group of enum pcmk__opt_flags; output an option * only if its \c flags member has all these flags set * -# NULL-terminated list of options whose metadata to format * -# All: If \c true, output all options; otherwise, exclude advanced and * deprecated options unless \c pcmk__opt_advanced and * \c pcmk__opt_deprecated flags (respectively) are set in the filter. */ PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *", "uint32_t", "const pcmk__cluster_option_t *", "bool") static int option_list_default(pcmk__output_t *out, va_list args) { const char *name G_GNUC_UNUSED = va_arg(args, const char *); const char *desc_short = va_arg(args, const char *); const char *desc_long = va_arg(args, const char *); const uint32_t filter = va_arg(args, uint32_t); const pcmk__cluster_option_t *option_list = va_arg(args, pcmk__cluster_option_t *); const bool all = (bool) va_arg(args, int); const bool show_deprecated = all || pcmk_is_set(filter, pcmk__opt_deprecated); const bool show_advanced = all || pcmk_is_set(filter, pcmk__opt_advanced); bool old_fancy = false; GSList *deprecated = NULL; GSList *advanced = NULL; CRM_ASSERT((out != NULL) && (desc_short != NULL) && (desc_long != NULL) && (option_list != NULL)); old_fancy = pcmk__output_text_get_fancy(out); pcmk__output_text_set_fancy(out, true); out->info(out, "%s", _(desc_short)); out->spacer(out); out->info(out, "%s", _(desc_long)); out->begin_list(out, NULL, NULL, NULL); for (const pcmk__cluster_option_t *option = option_list; option->name != NULL; option++) { // Store deprecated and advanced options to display later if appropriate if (pcmk_all_flags_set(option->flags, filter)) { if (pcmk_is_set(option->flags, pcmk__opt_deprecated)) { if (show_deprecated) { deprecated = g_slist_prepend(deprecated, (gpointer) option); } } else if (pcmk_is_set(option->flags, pcmk__opt_advanced)) { if (show_advanced) { advanced = g_slist_prepend(advanced, (gpointer) option); } } else { out->spacer(out); add_option_metadata_default(out, option); } } } if (advanced != NULL) { advanced = g_slist_reverse(advanced); out->spacer(out); out->begin_list(out, NULL, NULL, _("ADVANCED OPTIONS")); for (const GSList *iter = advanced; iter != NULL; iter = iter->next) { const pcmk__cluster_option_t *option = iter->data; out->spacer(out); add_option_metadata_default(out, option); } out->end_list(out); g_slist_free(advanced); } if (deprecated != NULL) { deprecated = g_slist_reverse(deprecated); out->spacer(out); out->begin_list(out, NULL, NULL, _("DEPRECATED OPTIONS (will be removed in a future " "release)")); for (const GSList *iter = deprecated; iter != NULL; iter = iter->next) { const pcmk__cluster_option_t *option = iter->data; out->spacer(out); add_option_metadata_default(out, option); } out->end_list(out); g_slist_free(deprecated); } out->end_list(out); pcmk__output_text_set_fancy(out, old_fancy); return pcmk_rc_ok; } /*! * \internal * \brief Add a description element to an OCF-like metadata XML node * * Include a translation based on the current locale if \c ENABLE_NLS is * defined. * * \param[in,out] out Output object * \param[in] for_long If \c true, add long description; otherwise, add * short description * \param[in] desc Textual description to add */ static void add_desc_xml(pcmk__output_t *out, bool for_long, const char *desc) { const char *tag = (for_long? PCMK_XE_LONGDESC : PCMK_XE_SHORTDESC); xmlNode *node = pcmk__output_create_xml_text_node(out, tag, desc); crm_xml_add(node, PCMK_XA_LANG, PCMK__VALUE_EN); #ifdef ENABLE_NLS { static const char *locale = NULL; if (strcmp(desc, _(desc)) == 0) { return; } if (locale == NULL) { locale = strtok(setlocale(LC_ALL, NULL), "_"); } node = pcmk__output_create_xml_text_node(out, tag, _(desc)); crm_xml_add(node, PCMK_XA_LANG, locale); } #endif } /*! * \internal * \brief Output an option's possible values * * Add a \c PCMK_XE_OPTION element for each of the option's possible values. * * \param[in,out] out Output object * \param[in] option Option whose possible values to add */ static void add_possible_values_xml(pcmk__output_t *out, const pcmk__cluster_option_t *option) { if ((option->values != NULL) && (strcmp(option->type, "select") == 0)) { const char *delim = ", "; char *str = pcmk__str_copy(option->values); const char *ptr = strtok(str, delim); while (ptr != NULL) { pcmk__output_create_xml_node(out, PCMK_XE_OPTION, PCMK_XA_VALUE, ptr, NULL); ptr = strtok(NULL, delim); } free(str); } } /*! * \internal * \brief Map an option type to one suitable for daemon metadata * * \param[in] type Option type to map * * \return String suitable for daemon metadata to display as an option type */ static const char * map_legacy_option_type(const char *type) { // @COMPAT Drop this function when we drop daemon metadata commands if (pcmk__str_any_of(type, PCMK_VALUE_DURATION, PCMK_VALUE_TIMEOUT, NULL)) { return PCMK__VALUE_TIME; } else if (pcmk__str_any_of(type, PCMK_VALUE_NONNEGATIVE_INTEGER, PCMK_VALUE_SCORE, NULL)) { return PCMK_VALUE_INTEGER; } else if (pcmk__str_eq(type, PCMK_VALUE_VERSION, pcmk__str_none)) { return PCMK_VALUE_STRING; } else { return type; } } /*! * \internal * \brief Add a \c PCMK_XE_PARAMETER element to an OCF-like metadata XML node * * \param[in,out] out Output object * \param[in] option Option to add as a \c PCMK_XE_PARAMETER element */ static void add_option_metadata_xml(pcmk__output_t *out, const pcmk__cluster_option_t *option) { const char *type = option->type; const char *desc_long = option->description_long; const char *desc_short = option->description_short; const bool advanced = pcmk_is_set(option->flags, pcmk__opt_advanced); const bool deprecated = pcmk_is_set(option->flags, pcmk__opt_deprecated); const bool generated = pcmk_is_set(option->flags, pcmk__opt_generated); // OCF requires "1"/"0" and does not allow "true"/"false // @COMPAT Variables no longer needed after we drop legacy mode const char *advanced_s = advanced? "1" : "0"; const char *generated_s = generated? "1" : "0"; // @COMPAT For daemon metadata only; drop when daemon metadata is dropped const bool legacy = pcmk__output_get_legacy_xml(out); char *desc_long_legacy = NULL; GString *desc_short_legacy = NULL; // The standard requires a parameter type CRM_ASSERT(type != NULL); // The standard requires long and short parameter descriptions CRM_ASSERT((desc_long != NULL) || (desc_short != NULL)); if (desc_long == NULL) { desc_long = desc_short; } else if (desc_short == NULL) { desc_short = desc_long; } if (legacy) { // This is ugly but it will go away at a major release bump type = map_legacy_option_type(type); if (option->values != NULL) { desc_long_legacy = crm_strdup_printf("%s Allowed values: %s", desc_long, option->values); desc_long = desc_long_legacy; } if (deprecated || advanced) { const size_t init_sz = 1023; if (desc_long != option->description_long) { /* desc_long was NULL and got assigned desc_short, which was * non-empty. Let desc_long have the "real" description, and put * the flag in desc_short. */ desc_short = ""; } else { desc_short = pcmk__s(option->description_short, ""); } if (deprecated) { pcmk__add_separated_word(&desc_short_legacy, init_sz, "*** Deprecated ***", NULL); } if (advanced) { pcmk__add_separated_word(&desc_short_legacy, init_sz, "*** Advanced Use Only ***", NULL); } pcmk__add_separated_word(&desc_short_legacy, 0, desc_short, NULL); desc_short = desc_short_legacy->str; } /* These must be NULL when used as attribute values later. * PCMK_XA_ADVANCED and PCMK_XA_GENERATED break validation for some * legacy tools. */ advanced_s = NULL; generated_s = NULL; } pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETER, PCMK_XA_NAME, option->name, PCMK_XA_ADVANCED, advanced_s, PCMK_XA_GENERATED, generated_s, NULL); if (deprecated && !legacy) { // No need yet to support "replaced-with" or "desc"; add if needed pcmk__output_create_xml_node(out, PCMK_XE_DEPRECATED, NULL); } add_desc_xml(out, true, desc_long); add_desc_xml(out, false, desc_short); pcmk__output_xml_create_parent(out, PCMK_XE_CONTENT, PCMK_XA_TYPE, type, PCMK_XA_DEFAULT, option->default_value, NULL); add_possible_values_xml(out, option); pcmk__output_xml_pop_parent(out); pcmk__output_xml_pop_parent(out); free(desc_long_legacy); if (desc_short_legacy != NULL) { g_string_free(desc_short_legacy, TRUE); } } /*! * \internal * \brief Output the metadata for a list of options as OCF-like XML * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Fake resource agent name for the option list * -# Short description of option list * -# Long description of option list * -# Filter: Group of enum pcmk__opt_flags; output an option * only if its \c flags member has all these flags set * -# NULL-terminated list of options whose metadata to format * -# Whether to output all options (ignored, treated as \c true) */ PCMK__OUTPUT_ARGS("option-list", "const char *", "const char *", "const char *", "uint32_t", "const pcmk__cluster_option_t *", "bool") static int option_list_xml(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *desc_short = va_arg(args, const char *); const char *desc_long = va_arg(args, const char *); const uint32_t filter = va_arg(args, uint32_t); const pcmk__cluster_option_t *option_list = va_arg(args, pcmk__cluster_option_t *); CRM_ASSERT((out != NULL) && (name != NULL) && (desc_short != NULL) && (desc_long != NULL) && (option_list != NULL)); pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_AGENT, PCMK_XA_NAME, name, PCMK_XA_VERSION, PACEMAKER_VERSION, NULL); pcmk__output_create_xml_text_node(out, PCMK_XE_VERSION, PCMK_OCF_VERSION); add_desc_xml(out, true, desc_long); add_desc_xml(out, false, desc_short); pcmk__output_xml_create_parent(out, PCMK_XE_PARAMETERS, NULL); for (const pcmk__cluster_option_t *option = option_list; option->name != NULL; option++) { if (pcmk_all_flags_set(option->flags, filter)) { add_option_metadata_xml(out, option); } } pcmk__output_xml_pop_parent(out); pcmk__output_xml_pop_parent(out); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "option-list", "default", option_list_default }, { "option-list", "xml", option_list_xml }, { NULL, NULL, NULL } }; /*! * \internal * \brief Register the formatting functions for option lists * * \param[in,out] out Output object */ void pcmk__register_option_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); }