diff --git a/lib/common/options.c b/lib/common/options.c index bbd9d9b986..57654b8266 100644 --- a/lib/common/options.c +++ b/lib/common/options.c @@ -1,671 +1,672 @@ /* * 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 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() { free(crm_short_options); crm_short_options = NULL; } static struct option * create_long_opts(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, 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; } 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) { int i = 0; FILE *stream = (exit_code ? stderr : stdout); if (cmd == 'v' || cmd == '$') { fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION); - fprintf(stream, "Written by Andrew Beekhof\n"); + fprintf(stream, "Written by Andrew Beekhof and " + "the Pacemaker project contributors\n"); goto out; } if (cmd == '!') { fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); goto out; } fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description); if (crm_app_usage) { fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage); } if (crm_long_options) { fprintf(stream, "Options:\n"); for (i = 0; crm_long_options[i].name != NULL; i++) { if (crm_long_options[i].flags & pcmk__option_hidden) { } else if (crm_long_options[i].flags & pcmk__option_paragraph) { fprintf(stream, "%s\n\n", crm_long_options[i].desc); } else if (crm_long_options[i].flags & pcmk__option_example) { fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc); } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) { fprintf(stream, "%s\n", crm_long_options[i].desc); } else { /* is val printable as char ? */ if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) { fprintf(stream, " -%c,", crm_long_options[i].val); } else { fputs(" ", stream); } fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name, crm_long_options[i].has_arg == optional_argument ? "[=value]" : crm_long_options[i].has_arg == required_argument ? "=value" : "", crm_long_options[i].desc ? crm_long_options[i].desc : ""); } } } else if (crm_short_options) { fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description); for (i = 0; crm_short_options[i] != 0; i++) { int has_arg = no_argument /* 0 */; if (crm_short_options[i + 1] == ':') { if (crm_short_options[i + 2] == ':') has_arg = optional_argument /* 2 */; else has_arg = required_argument /* 1 */; } fprintf(stream, " -%c %s\n", crm_short_options[i], has_arg == optional_argument ? "[value]" : has_arg == required_argument ? "{value}" : ""); i += has_arg; } } fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT); out: 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 */ const char * pcmk__env_option(const char *option) { char env_name[NAME_MAX]; const char *value = NULL; snprintf(env_name, NAME_MAX, "PCMK_%s", option); value = getenv(env_name); if (value != NULL) { crm_trace("Found %s = %s", env_name, value); return value; } snprintf(env_name, NAME_MAX, "HA_%s", option); 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) { char env_name[NAME_MAX]; snprintf(env_name, NAME_MAX, "PCMK_%s", option); if (value) { crm_trace("Setting %s to %s", env_name, value); setenv(env_name, value, 1); } else { crm_trace("Unsetting %s", env_name); unsetenv(env_name); } snprintf(env_name, NAME_MAX, "HA_%s", option); if (value) { crm_trace("Setting %s to %s", env_name, value); setenv(env_name, value, 1); } else { crm_trace("Unsetting %s", env_name); unsetenv(env_name); } } /*! * \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 * \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) || strstr(value, daemon)); } /* * 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] 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] options Name/value pairs for configured options * \param[in] option_list Possible cluster options * \param[in] name (Primary) option name to look for * * \return Option value */ const char * pcmk__cluster_option(GHashTable *options, 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; } char * pcmk__format_option_metadata(const char *name, const char *desc_short, const char *desc_long, pcmk__cluster_option_t *option_list, int len) { #ifdef ENABLE_NLS char *locale = NULL; #endif char *escaped_long = NULL; char *escaped_short = NULL; char *retval; /* big enough to hold "pacemaker-schedulerd metadata" output */ GString *s = g_string_sized_new(13000); int lpc = 0; escaped_long = crm_xml_escape(desc_long); escaped_short = crm_xml_escape(desc_short); g_string_append_printf(s, "" "\n" "\n" " %s\n" " %s\n" " %s\n" " \n", name, PCMK_OCF_VERSION, escaped_long, escaped_short); free(escaped_long); free(escaped_short); for (lpc = 0; lpc < len; lpc++) { if ((option_list[lpc].description_long == NULL) && (option_list[lpc].description_short == NULL)) { continue; } g_string_append_printf(s, " \n", option_list[lpc].name); escaped_long = crm_xml_escape(option_list[lpc].description_long? option_list[lpc].description_long : option_list[lpc].description_short); escaped_short = crm_xml_escape(option_list[lpc].description_short); g_string_append_printf(s, " %s%s%s\n" " %s\n", escaped_long, (option_list[lpc].values? " Allowed values: " : ""), (option_list[lpc].values? option_list[lpc].values : ""), escaped_short); free(escaped_long); free(escaped_short); #ifdef ENABLE_NLS escaped_long = crm_xml_escape(option_list[lpc].description_long? _(option_list[lpc].description_long) : _(option_list[lpc].description_short)); escaped_short = crm_xml_escape(_(option_list[lpc].description_short)); locale=strtok(setlocale(LC_ALL,NULL),"_"); g_string_append_printf(s, " %s%s%s\n" " %s\n", locale, escaped_long, (option_list[lpc].values? " Allowed values: " : ""), (option_list[lpc].values? option_list[lpc].values : ""), locale, escaped_short); free(escaped_long); free(escaped_short); #endif if (option_list[lpc].values && !strcmp(option_list[lpc].type, "select")) { char *str = strdup(option_list[lpc].values); char delim[] = ", "; char *ptr = strtok(str, delim); g_string_append_printf(s, " \n", option_list[lpc].type, option_list[lpc].default_value); while (ptr != NULL) { g_string_append_printf(s, " \n"); free(str); } else { g_string_append_printf(s, " \n", option_list[lpc].type, option_list[lpc].default_value ); } g_string_append_printf(s, " \n"); } g_string_append_printf(s, " \n\n"); retval = s->str; g_string_free(s, FALSE); return retval; } 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); } } diff --git a/lib/common/output_html.c b/lib/common/output_html.c index c5427b4ece..54d3fd54ab 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -1,459 +1,461 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * Copyright 2019-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. */ #include #include #include #include #include #include #include static const char *stylesheet_default = ".bold { font-weight: bold }\n" ".maint { color: blue }\n" ".offline { color: red }\n" ".online { color: green }\n" ".rsc-failed { color: red }\n" ".rsc-failure-ignored { color: yellow }\n" ".rsc-managed { color: yellow }\n" ".rsc-multiple { color: orange }\n" ".rsc-ok { color: green }\n" ".standby { color: orange }\n" ".warning { color: red, font-weight: bold }"; static gboolean cgi_output = FALSE; static char *stylesheet_link = NULL; static char *title = NULL; static GSList *extra_headers = NULL; GOptionEntry pcmk__html_output_entries[] = { { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output, "Add CGI headers (requires --output-as=html)", NULL }, { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link, "Link to an external stylesheet (requires --output-as=html)", "URI" }, { "html-title", 0, 0, G_OPTION_ARG_STRING, &title, "Specify a page title (requires --output-as=html)", "TITLE" }, { NULL } }; /* The first several elements of this struct must be the same as the first * several elements of private_data_s in lib/common/output_xml.c. This * struct gets passed to a bunch of the pcmk__output_xml_* functions which * assume an XML private_data_s. Keeping them laid out the same means this * still works. */ typedef struct private_data_s { /* Begin members that must match the XML version */ xmlNode *root; GQueue *parent_q; GSList *errors; /* End members that must match the XML version */ } private_data_t; static void html_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } xmlFreeNode(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); out->priv = NULL; } static bool html_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If html_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); priv->root = create_xml_node(NULL, "html"); xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL); crm_xml_add(priv->root, "lang", "en"); g_queue_push_tail(priv->parent_q, priv->root); priv->errors = NULL; pcmk__output_xml_create_parent(out, "body", NULL); return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; pcmk__output_t *out = (pcmk__output_t *) user_data; out->list_item(out, NULL, "%s", str); } static void html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = out->priv; htmlNodePtr head_node = NULL; htmlNodePtr charset_node = NULL; /* If root is NULL, html_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } if (cgi_output && print) { fprintf(out->dest, "Content-Type: text/html\n\n"); } /* Add the head node last - it's not needed earlier because it doesn't contain * anything else that the user could add, and we want it done last to pick up * any options that may have been given. */ head_node = xmlNewNode(NULL, (pcmkXmlStr) "head"); if (title != NULL ) { pcmk_create_xml_text_node(head_node, "title", title); } else if (out->request != NULL) { pcmk_create_xml_text_node(head_node, "title", out->request); } charset_node = create_xml_node(head_node, "meta"); crm_xml_add(charset_node, "charset", "utf-8"); /* Add any extra header nodes the caller might have created. */ for (int i = 0; i < g_slist_length(extra_headers); i++) { xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1)); } /* Stylesheets are included two different ways. The first is via a built-in * default (see the stylesheet_default const above). The second is via the * html-stylesheet option, and this should obviously be a link to a * stylesheet. The second can override the first. At least one should be * given. */ pcmk_create_xml_text_node(head_node, "style", stylesheet_default); if (stylesheet_link != NULL) { htmlNodePtr link_node = create_xml_node(head_node, "link"); pcmk__xe_set_props(link_node, "rel", "stylesheet", "href", stylesheet_link, NULL); } xmlAddPrevSibling(priv->root->children, head_node); if (g_slist_length(priv->errors) > 0) { out->begin_list(out, "Errors", NULL, NULL); g_slist_foreach(priv->errors, add_error_node, (gpointer) out); out->end_list(out); } if (print) { htmlDocDump(out->dest, priv->root->doc); } if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode); extra_headers = NULL; } static void html_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); html_free_priv(out); html_init(out); } static void html_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { char *rc_buf = NULL; CRM_ASSERT(out != NULL); rc_buf = crm_strdup_printf("Return code: %d", exit_status); pcmk__output_create_xml_text_node(out, "h2", "Command Output"); pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf); if (proc_stdout != NULL) { pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout"); pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout); } if (proc_stderr != NULL) { pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr"); pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr); } free(rc_buf); } static void html_version(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); pcmk__output_create_xml_text_node(out, "h2", "Version Information"); pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker"); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION)); - pcmk__output_create_html_node(out, "div", NULL, NULL, "Author: Andrew Beekhof"); + pcmk__output_create_html_node(out, "div", NULL, NULL, + "Author: Andrew Beekhof and " + "the Pacemaker project contributors"); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION)); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES)); } G_GNUC_PRINTF(2, 3) static void html_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static int html_info(pcmk__output_t *out, const char *format, ...) { return pcmk_rc_no_output; } static void html_output_xml(pcmk__output_t *out, const char *name, const char *buf) { htmlNodePtr node = NULL; CRM_ASSERT(out != NULL); node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf); crm_xml_add(node, "lang", "xml"); } G_GNUC_PRINTF(4, 5) static void html_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { int q_len = 0; private_data_t *priv = NULL; xmlNodePtr node = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; /* If we are already in a list (the queue depth is always at least * one because of the element), first create a
  • element * to hold the

    and the new list. */ q_len = g_queue_get_length(priv->parent_q); if (q_len > 2) { pcmk__output_xml_create_parent(out, "li", NULL); } if (format != NULL) { va_list ap; char *buf = NULL; int len; va_start(ap, format); len = vasprintf(&buf, format, ap); va_end(ap); CRM_ASSERT(len >= 0); if (q_len > 2) { pcmk__output_create_xml_text_node(out, "h3", buf); } else { pcmk__output_create_xml_text_node(out, "h2", buf); } free(buf); } node = pcmk__output_xml_create_parent(out, "ul", NULL); g_queue_push_tail(priv->parent_q, node); } G_GNUC_PRINTF(3, 4) static void html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { htmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "li", buf); free(buf); if (name != NULL) { crm_xml_add(item_node, "class", name); } } static void html_increment_list(pcmk__output_t *out) { /* This function intentially left blank */ } static void html_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; /* Remove the
      tag. */ g_queue_pop_tail(priv->parent_q); pcmk__output_xml_pop_parent(out); /* Remove the
    • created for nested lists. */ if (g_queue_get_length(priv->parent_q) > 2) { pcmk__output_xml_pop_parent(out); } } static bool html_is_quiet(pcmk__output_t *out) { return false; } static void html_spacer(pcmk__output_t *out) { CRM_ASSERT(out != NULL); pcmk__output_create_xml_node(out, "br", NULL); } static void html_progress(pcmk__output_t *out, bool end) { /* This function intentially left blank */ } pcmk__output_t * pcmk__mk_html_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "html"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = html_init; retval->free_priv = html_free_priv; retval->finish = html_finish; retval->reset = html_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = html_subprocess_output; retval->version = html_version; retval->info = html_info; retval->err = html_err; retval->output_xml = html_output_xml; retval->begin_list = html_begin_list; retval->list_item = html_list_item; retval->increment_list = html_increment_list; retval->end_list = html_end_list; retval->is_quiet = html_is_quiet; retval->spacer = html_spacer; retval->progress = html_progress; retval->prompt = pcmk__text_prompt; return retval; } xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text) { htmlNodePtr node = NULL; CRM_ASSERT(out != NULL); node = pcmk__output_create_xml_text_node(out, element_name, text); if (class_name != NULL) { crm_xml_add(node, "class", class_name); } if (id != NULL) { crm_xml_add(node, "id", id); } return node; } void pcmk__html_add_header(const char *name, ...) { htmlNodePtr header_node; va_list ap; va_start(ap, name); header_node = xmlNewNode(NULL, (pcmkXmlStr) name); while (1) { char *key = va_arg(ap, char *); char *value; if (key == NULL) { break; } value = va_arg(ap, char *); crm_xml_add(header_node, key, value); } extra_headers = g_slist_append(extra_headers, header_node); va_end(ap); } diff --git a/lib/common/output_log.c b/lib/common/output_log.c index b2b61a7448..cae7844792 100644 --- a/lib/common/output_log.c +++ b/lib/common/output_log.c @@ -1,314 +1,315 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * Copyright 2019-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. */ #include #include #include #include #include GOptionEntry pcmk__log_output_entries[] = { { NULL } }; typedef struct private_data_s { /* gathered in log_begin_list */ GQueue/**/ *prefixes; int log_level; } private_data_t; static void log_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { /* This function intentionally left blank */ } static void log_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->prefixes); free(priv); out->priv = NULL; } static bool log_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If log_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; priv->prefixes = g_queue_new(); priv->log_level = LOG_INFO; return true; } static void log_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { /* This function intentionally left blank */ } static void log_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); log_free_priv(out); log_init(out); } static void log_version(pcmk__output_t *out, bool extended) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (extended) { do_crm_log(priv->log_level, "Pacemaker %s (Build: %s): %s", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { do_crm_log(priv->log_level, "Pacemaker %s", PACEMAKER_VERSION); - do_crm_log(priv->log_level, "Written by Andrew Beekhof"); + do_crm_log(priv->log_level, "Written by Andrew Beekhof and" + "the Pacemaker project contributors"); } } G_GNUC_PRINTF(2, 3) static void log_err(pcmk__output_t *out, const char *format, ...) { va_list ap; char* buffer = NULL; int len = 0; CRM_ASSERT(out != NULL); va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); crm_err("%s", buffer); free(buffer); } static void log_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr node = NULL; private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; node = create_xml_node(NULL, name); xmlNodeSetContent(node, (pcmkXmlStr) buf); do_crm_log_xml(priv->log_level, name, node); free(node); } G_GNUC_PRINTF(4, 5) static void log_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { int len = 0; va_list ap; char* buffer = NULL; private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Don't skip empty prefixes, * otherwise there will be mismatch * in the log_end_list */ if(strcmp(buffer, "") == 0) { /* nothing */ } g_queue_push_tail(priv->prefixes, buffer); } G_GNUC_PRINTF(3, 4) static void log_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { int len = 0; va_list ap; private_data_t *priv = NULL; char prefix[LINE_MAX] = { 0 }; int offset = 0; char* buffer = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; for (GList* gIter = priv->prefixes->head; gIter; gIter = gIter->next) { if (strcmp(prefix, "") != 0) { offset += snprintf(prefix + offset, LINE_MAX - offset, ": %s", (char *)gIter->data); } else { offset = snprintf(prefix, LINE_MAX, "%s", (char *)gIter->data); } } va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (strcmp(buffer, "") != 0) { /* We don't want empty messages */ if ((name != NULL) && (strcmp(name, "") != 0)) { if (strcmp(prefix, "") != 0) { do_crm_log(priv->log_level, "%s: %s: %s", prefix, name, buffer); } else { do_crm_log(priv->log_level, "%s: %s", name, buffer); } } else { if (strcmp(prefix, "") != 0) { do_crm_log(priv->log_level, "%s: %s", prefix, buffer); } else { do_crm_log(priv->log_level, "%s", buffer); } } } free(buffer); } static void log_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (priv->prefixes == NULL) { return; } CRM_ASSERT(priv->prefixes->tail != NULL); free((char *)priv->prefixes->tail->data); g_queue_pop_tail(priv->prefixes); } G_GNUC_PRINTF(2, 3) static int log_info(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; va_list ap; char* buffer = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); do_crm_log(priv->log_level, "%s", buffer); free(buffer); return pcmk_rc_ok; } static bool log_is_quiet(pcmk__output_t *out) { return false; } static void log_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } static void log_progress(pcmk__output_t *out, bool end) { /* This function intentionally left blank */ } static void log_prompt(const char *prompt, bool echo, char **dest) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_log_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "log"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = log_init; retval->free_priv = log_free_priv; retval->finish = log_finish; retval->reset = log_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = log_subprocess_output; retval->version = log_version; retval->info = log_info; retval->err = log_err; retval->output_xml = log_output_xml; retval->begin_list = log_begin_list; retval->list_item = log_list_item; retval->end_list = log_end_list; retval->is_quiet = log_is_quiet; retval->spacer = log_spacer; retval->progress = log_progress; retval->prompt = log_prompt; return retval; } void pcmk__output_set_log_level(pcmk__output_t *out, int log_level) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); if (!pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) { return; } priv = out->priv; priv->log_level = log_level; } diff --git a/lib/common/output_text.c b/lib/common/output_text.c index def89fa349..915e6a8258 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,433 +1,434 @@ /* * Copyright 2019-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. */ #include #include #include #include #include static gboolean fancy = FALSE; GOptionEntry pcmk__text_output_entries[] = { { "text-fancy", 0, 0, G_OPTION_ARG_NONE, &fancy, "Use more highly formatted output (requires --output-as=text)", NULL }, { NULL } }; typedef struct text_list_data_s { unsigned int len; char *singular_noun; char *plural_noun; } text_list_data_t; typedef struct private_data_s { GQueue *parent_q; } private_data_t; static void text_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->parent_q); free(priv); out->priv = NULL; } static bool text_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If text_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); return true; } static void text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { fflush(out->dest); } static void text_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); if (out->dest != stdout) { out->dest = freopen(NULL, "w", out->dest); } CRM_ASSERT(out->dest != NULL); text_free_priv(out); text_init(out); } static void text_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { CRM_ASSERT(out != NULL); if (proc_stdout != NULL) { fprintf(out->dest, "%s\n", proc_stdout); } if (proc_stderr != NULL) { fprintf(out->dest, "%s\n", proc_stderr); } } static void text_version(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); if (extended) { fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION); - fprintf(out->dest, "Written by Andrew Beekhof\n"); + fprintf(out->dest, "Written by Andrew Beekhof and " + "the Pacemaker project contributors\n"); } } G_GNUC_PRINTF(2, 3) static void text_err(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; CRM_ASSERT(out != NULL); va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(stderr, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(stderr, "\n"); } G_GNUC_PRINTF(2, 3) static int text_info(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; CRM_ASSERT(out != NULL); if (out->is_quiet(out)) { return pcmk_rc_no_output; } va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(out->dest, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(out->dest, "\n"); return pcmk_rc_ok; } static void text_output_xml(pcmk__output_t *out, const char *name, const char *buf) { CRM_ASSERT(out != NULL); pcmk__indented_printf(out, "%s", buf); } G_GNUC_PRINTF(4, 5) static void text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = NULL; text_list_data_t *new_list = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); if (fancy && format) { pcmk__indented_vprintf(out, format, ap); fprintf(out->dest, ":\n"); } va_end(ap); new_list = calloc(1, sizeof(text_list_data_t)); new_list->len = 0; pcmk__str_update(&new_list->singular_noun, singular_noun); pcmk__str_update(&new_list->plural_noun, plural_noun); g_queue_push_tail(priv->parent_q, new_list); } G_GNUC_PRINTF(3, 4) static void text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); if (fancy) { if (id != NULL) { /* Not really a good way to do this all in one call, so make it two. * The first handles the indentation and list styling. The second * just prints right after that one. */ pcmk__indented_printf(out, "%s: ", id); vfprintf(out->dest, format, ap); } else { pcmk__indented_vprintf(out, format, ap); } } else { pcmk__indented_vprintf(out, format, ap); } fputc('\n', out->dest); fflush(out->dest); va_end(ap); out->increment_list(out); } static void text_increment_list(pcmk__output_t *out) { private_data_t *priv = NULL; gpointer tail; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; tail = g_queue_peek_tail(priv->parent_q); CRM_ASSERT(tail != NULL); ((text_list_data_t *) tail)->len++; } static void text_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; text_list_data_t *node = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; node = g_queue_pop_tail(priv->parent_q); if (node->singular_noun != NULL && node->plural_noun != NULL) { if (node->len == 1) { pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun); } else { pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun); } } free(node); } static bool text_is_quiet(pcmk__output_t *out) { CRM_ASSERT(out != NULL); return out->quiet; } static void text_spacer(pcmk__output_t *out) { CRM_ASSERT(out != NULL); fprintf(out->dest, "\n"); } static void text_progress(pcmk__output_t *out, bool end) { CRM_ASSERT(out != NULL); if (out->dest == stdout) { fprintf(out->dest, "."); if (end) { fprintf(out->dest, "\n"); } } } pcmk__output_t * pcmk__mk_text_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "text"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = text_init; retval->free_priv = text_free_priv; retval->finish = text_finish; retval->reset = text_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = text_subprocess_output; retval->version = text_version; retval->info = text_info; retval->err = text_err; retval->output_xml = text_output_xml; retval->begin_list = text_begin_list; retval->list_item = text_list_item; retval->increment_list = text_increment_list; retval->end_list = text_end_list; retval->is_quiet = text_is_quiet; retval->spacer = text_spacer; retval->progress = text_progress; retval->prompt = pcmk__text_prompt; return retval; } G_GNUC_PRINTF(2, 0) void pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) { int len = 0; CRM_ASSERT(out != NULL); len = vfprintf(out->dest, format, args); CRM_ASSERT(len >= 0); } G_GNUC_PRINTF(2, 3) void pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); pcmk__formatted_vprintf(out, format, ap); va_end(ap); } G_GNUC_PRINTF(2, 0) void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { CRM_ASSERT(out != NULL); if (!pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) { return; } if (fancy) { int level = 0; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); level = g_queue_get_length(priv->parent_q); for (int i = 0; i < level; i++) { fprintf(out->dest, " "); } if (level > 0) { fprintf(out->dest, "* "); } } pcmk__formatted_vprintf(out, format, args); } G_GNUC_PRINTF(2, 3) void pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); pcmk__indented_vprintf(out, format, ap); va_end(ap); } void pcmk__text_prompt(const char *prompt, bool echo, char **dest) { int rc = 0; struct termios settings; tcflag_t orig_c_lflag = 0; CRM_ASSERT(prompt != NULL); CRM_ASSERT(dest != NULL); if (!echo) { rc = tcgetattr(0, &settings); if (rc == 0) { orig_c_lflag = settings.c_lflag; settings.c_lflag &= ~ECHO; rc = tcsetattr(0, TCSANOW, &settings); } } if (rc == 0) { fprintf(stderr, "%s: ", prompt); if (*dest != NULL) { free(*dest); *dest = NULL; } #if SSCANF_HAS_M rc = scanf("%ms", dest); #else *dest = calloc(1, 1024); rc = scanf("%1023s", *dest); #endif fprintf(stderr, "\n"); } if (rc < 1) { free(*dest); *dest = NULL; } if (orig_c_lflag != 0) { settings.c_lflag = orig_c_lflag; /* rc = */ tcsetattr(0, TCSANOW, &settings); } } diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index 6f4dd06228..c4e6df28c9 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,537 +1,538 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * Copyright 2019-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. */ #include #include #include #include #include #include #include static gboolean legacy_xml = FALSE; static gboolean simple_list = FALSE; static gboolean substitute = FALSE; GOptionEntry pcmk__xml_output_entries[] = { { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml, NULL, NULL }, { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list, NULL, NULL }, { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute, NULL, NULL }, { NULL } }; typedef struct subst_s { const char *from; const char *to; } subst_t; static subst_t substitutions[] = { { "Active Resources", "resources" }, { "Allocation Scores", "allocations" }, { "Allocation Scores and Utilization Information", "allocations_utilizations" }, { "Cluster Summary", "summary" }, { "Current cluster status", "cluster_status" }, { "Executing Cluster Transition", "transition" }, { "Failed Resource Actions", "failures" }, { "Fencing History", "fence_history" }, { "Full List of Resources", "resources" }, { "Inactive Resources", "resources" }, { "Migration Summary", "node_history" }, { "Negative Location Constraints", "bans" }, { "Node Attributes", "node_attributes" }, { "Operations", "node_history" }, { "Resource Config", "resource_config" }, { "Resource Operations", "operations" }, { "Revised Cluster Status", "revised_cluster_status" }, { "Transition Summary", "actions" }, { "Utilization Information", "utilizations" }, { NULL, NULL } }; /* The first several elements of this struct must be the same as the first * several elements of private_data_s in lib/common/output_html.c. That * struct gets passed to a bunch of the pcmk__output_xml_* functions which * assume an XML private_data_s. Keeping them laid out the same means this * still works. */ typedef struct private_data_s { /* Begin members that must match the HTML version */ xmlNode *root; GQueue *parent_q; GSList *errors; /* End members that must match the HTML version */ bool legacy_xml; } private_data_t; static void xml_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } free_xml(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); out->priv = NULL; } static bool xml_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If xml_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } if (legacy_xml) { priv->root = create_xml_node(NULL, "crm_mon"); crm_xml_add(priv->root, "version", PACEMAKER_VERSION); } else { priv->root = create_xml_node(NULL, "pacemaker-result"); crm_xml_add(priv->root, "api-version", PCMK__API_VERSION); if (out->request != NULL) { crm_xml_add(priv->root, "request", out->request); } } priv->parent_q = g_queue_new(); priv->errors = NULL; g_queue_push_tail(priv->parent_q, priv->root); /* Copy this from the file-level variable. This means that it is only settable * as a command line option, and that pcmk__output_new must be called after all * command line processing is completed. */ priv->legacy_xml = legacy_xml; return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; xmlNodePtr node = (xmlNodePtr) user_data; pcmk_create_xml_text_node(node, "error", str); } static void xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = NULL; xmlNodePtr node; CRM_ASSERT(out != NULL); priv = out->priv; /* If root is NULL, xml_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } if (legacy_xml) { GSList *node = priv->errors; if (exit_status != CRM_EX_OK) { fprintf(stderr, "%s\n", crm_exit_str(exit_status)); } while (node != NULL) { fprintf(stderr, "%s\n", (char *) node->data); node = node->next; } } else { char *rc_as_str = pcmk__itoa(exit_status); node = create_xml_node(priv->root, "status"); pcmk__xe_set_props(node, "code", rc_as_str, "message", crm_exit_str(exit_status), NULL); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = create_xml_node(node, "errors"); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } free(rc_as_str); } if (print) { char *buf = dump_xml_formatted_with_text(priv->root); fprintf(out->dest, "%s", buf); fflush(out->dest); free(buf); } if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } } static void xml_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); xml_free_priv(out); xml_init(out); } static void xml_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { xmlNodePtr node, child_node; char *rc_as_str = NULL; CRM_ASSERT(out != NULL); rc_as_str = pcmk__itoa(exit_status); node = pcmk__output_xml_create_parent(out, "command", "code", rc_as_str, NULL); if (proc_stdout != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stdout); crm_xml_add(child_node, "source", "stdout"); } if (proc_stderr != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stderr); crm_xml_add(child_node, "source", "stderr"); } pcmk__output_xml_add_node(out, node); free(rc_as_str); } static void xml_version(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); pcmk__output_create_xml_node(out, "version", "program", "Pacemaker", "version", PACEMAKER_VERSION, - "author", "Andrew Beekhof", + "author", "Andrew Beekhof and the " + "Pacemaker project contributors", "build", BUILD_VERSION, "features", CRM_FEATURES, NULL); } G_GNUC_PRINTF(2, 3) static void xml_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static int xml_info(pcmk__output_t *out, const char *format, ...) { return pcmk_rc_no_output; } static void xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr parent = NULL; xmlNodePtr cdata_node = NULL; CRM_ASSERT(out != NULL); parent = pcmk__output_create_xml_node(out, name, NULL); cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf)); xmlAddChild(parent, cdata_node); } G_GNUC_PRINTF(4, 5) static void xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { va_list ap; char *name = NULL; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (substitute) { for (subst_t *s = substitutions; s->from != NULL; s++) { if (!strcmp(s->from, buf)) { name = g_strdup(s->to); break; } } } if (name == NULL) { name = g_ascii_strdown(buf, -1); } if (legacy_xml || simple_list) { pcmk__output_xml_create_parent(out, name, NULL); } else { pcmk__output_xml_create_parent(out, "list", "name", name, NULL); } g_free(name); free(buf); } G_GNUC_PRINTF(3, 4) static void xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { xmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(out != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "item", buf); if (name != NULL) { crm_xml_add(item_node, "name", name); } free(buf); } static void xml_increment_list(pcmk__output_t *out) { /* This function intentially left blank */ } static void xml_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (priv->legacy_xml || simple_list) { g_queue_pop_tail(priv->parent_q); } else { char *buf = NULL; xmlNodePtr node; node = g_queue_pop_tail(priv->parent_q); buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); crm_xml_add(node, "count", buf); free(buf); } } static bool xml_is_quiet(pcmk__output_t *out) { return false; } static void xml_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } static void xml_progress(pcmk__output_t *out, bool end) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_xml_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "xml"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = xml_init; retval->free_priv = xml_free_priv; retval->finish = xml_finish; retval->reset = xml_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = xml_subprocess_output; retval->version = xml_version; retval->info = xml_info; retval->err = xml_err; retval->output_xml = xml_output_xml; retval->begin_list = xml_begin_list; retval->list_item = xml_list_item; retval->increment_list = xml_increment_list; retval->end_list = xml_end_list; retval->is_quiet = xml_is_quiet; retval->spacer = xml_spacer; retval->progress = xml_progress; retval->prompt = pcmk__text_prompt; return retval; } xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) { va_list args; xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); node = pcmk__output_create_xml_node(out, name, NULL); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); pcmk__output_xml_push_parent(out, node); return node; } void pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(node != NULL); if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) { return; } priv = out->priv; xmlAddChild(g_queue_peek_tail(priv->parent_q), node); } xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) { xmlNodePtr node = NULL; private_data_t *priv = NULL; va_list args; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)); priv = out->priv; node = create_xml_node(g_queue_peek_tail(priv->parent_q), name); va_start(args, name); pcmk__xe_set_propv(node, args); va_end(args); return node; } xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) { xmlNodePtr node = NULL; CRM_ASSERT(out != NULL); node = pcmk__output_create_xml_node(out, name, NULL); xmlNodeSetContent(node, (pcmkXmlStr) content); return node; } void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(parent != NULL); if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) { return; } priv = out->priv; g_queue_push_tail(priv->parent_q, parent); } void pcmk__output_xml_pop_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); if (!pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)) { return; } priv = out->priv; CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); g_queue_pop_tail(priv->parent_q); } xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL)); priv = out->priv; /* If queue is empty NULL will be returned */ return g_queue_peek_tail(priv->parent_q); } diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c index 5bad1048cf..c0ee1fde88 100644 --- a/tools/crm_mon_curses.c +++ b/tools/crm_mon_curses.c @@ -1,530 +1,531 @@ /* * Copyright 2019-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. */ #include #include #include #include #include #include #include #include #include #include #include "crm_mon.h" #if CURSES_ENABLED GOptionEntry crm_mon_curses_output_entries[] = { { NULL } }; typedef struct curses_list_data_s { unsigned int len; char *singular_noun; char *plural_noun; } curses_list_data_t; typedef struct private_data_s { GQueue *parent_q; } private_data_t; static void curses_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->parent_q); free(priv); out->priv = NULL; } static bool curses_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If curses_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); initscr(); cbreak(); noecho(); return true; } static void curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { CRM_ASSERT(out != NULL); echo(); nocbreak(); endwin(); } static void curses_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); curses_free_priv(out); curses_init(out); } static void curses_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { CRM_ASSERT(out != NULL); if (proc_stdout != NULL) { printw("%s\n", proc_stdout); } if (proc_stderr != NULL) { printw("%s\n", proc_stderr); } clrtoeol(); refresh(); } /* curses_version is defined in curses.h, so we can't use that name here. * Note that this function prints out via text, not with curses. */ static void curses_ver(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); if (extended) { printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { printf("Pacemaker %s\n", PACEMAKER_VERSION); - printf("Written by Andrew Beekhof\n"); + printf("Written by Andrew Beekhof and the " + "Pacemaker project contributors\n"); } } G_GNUC_PRINTF(2, 3) static void curses_error(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ va_start(ap, format); vw_printw(stdscr, format, ap); va_end(ap); /* Add a newline. */ addch('\n'); clrtoeol(); refresh(); sleep(2); } G_GNUC_PRINTF(2, 3) static int curses_info(pcmk__output_t *out, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); if (out->is_quiet(out)) { return pcmk_rc_no_output; } /* Informational output does not get indented, to separate it from other * potentially indented list output. */ va_start(ap, format); vw_printw(stdscr, format, ap); va_end(ap); /* Add a newline. */ addch('\n'); clrtoeol(); refresh(); return pcmk_rc_ok; } static void curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) { CRM_ASSERT(out != NULL); curses_indented_printf(out, "%s", buf); } G_GNUC_PRINTF(4, 5) static void curses_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = NULL; curses_list_data_t *new_list = NULL; va_list ap; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; /* Empty formats can be used to create a new level of indentation, but without * displaying some sort of list header. In that case we need to not do any of * this stuff. vw_printw will act weird if told to print a NULL. */ if (format != NULL) { va_start(ap, format); curses_indented_vprintf(out, format, ap); printw(":\n"); va_end(ap); } new_list = calloc(1, sizeof(curses_list_data_t)); new_list->len = 0; pcmk__str_update(&new_list->singular_noun, singular_noun); pcmk__str_update(&new_list->plural_noun, plural_noun); g_queue_push_tail(priv->parent_q, new_list); } G_GNUC_PRINTF(3, 4) static void curses_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { va_list ap; CRM_ASSERT(out != NULL); va_start(ap, format); if (id != NULL) { curses_indented_printf(out, "%s: ", id); vw_printw(stdscr, format, ap); } else { curses_indented_vprintf(out, format, ap); } addch('\n'); va_end(ap); out->increment_list(out); } static void curses_increment_list(pcmk__output_t *out) { private_data_t *priv = NULL; gpointer tail; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; tail = g_queue_peek_tail(priv->parent_q); CRM_ASSERT(tail != NULL); ((curses_list_data_t *) tail)->len++; } static void curses_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; curses_list_data_t *node = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; node = g_queue_pop_tail(priv->parent_q); if (node->singular_noun != NULL && node->plural_noun != NULL) { if (node->len == 1) { curses_indented_printf(out, "%d %s found\n", node->len, node->singular_noun); } else { curses_indented_printf(out, "%d %s found\n", node->len, node->plural_noun); } } free(node); } static bool curses_is_quiet(pcmk__output_t *out) { CRM_ASSERT(out != NULL); return out->quiet; } static void curses_spacer(pcmk__output_t *out) { CRM_ASSERT(out != NULL); addch('\n'); } static void curses_progress(pcmk__output_t *out, bool end) { CRM_ASSERT(out != NULL); if (end) { printw(".\n"); } else { addch('.'); } } static void curses_prompt(const char *prompt, bool do_echo, char **dest) { int rc = OK; CRM_ASSERT(prompt != NULL); CRM_ASSERT(dest != NULL); /* This is backwards from the text version of this function on purpose. We * disable echo by default in curses_init, so we need to enable it here if * asked for. */ if (do_echo) { rc = echo(); } if (rc == OK) { printw("%s: ", prompt); if (*dest != NULL) { free(*dest); } *dest = calloc(1, 1024); /* On older systems, scanw is defined as taking a char * for its first argument, * while newer systems rightly want a const char *. Accomodate both here due * to building with -Werror. */ rc = scanw((NCURSES_CONST char *) "%1023s", *dest); addch('\n'); } if (rc < 1) { free(*dest); *dest = NULL; } if (do_echo) { noecho(); } } pcmk__output_t * crm_mon_mk_curses_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "console"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = curses_init; retval->free_priv = curses_free_priv; retval->finish = curses_finish; retval->reset = curses_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = curses_subprocess_output; retval->version = curses_ver; retval->err = curses_error; retval->info = curses_info; retval->output_xml = curses_output_xml; retval->begin_list = curses_begin_list; retval->list_item = curses_list_item; retval->increment_list = curses_increment_list; retval->end_list = curses_end_list; retval->is_quiet = curses_is_quiet; retval->spacer = curses_spacer; retval->progress = curses_progress; retval->prompt = curses_prompt; return retval; } G_GNUC_PRINTF(2, 0) void curses_formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) { vw_printw(stdscr, format, args); clrtoeol(); refresh(); } G_GNUC_PRINTF(2, 3) void curses_formatted_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; va_start(ap, format); curses_formatted_vprintf(out, format, ap); va_end(ap); } G_GNUC_PRINTF(2, 0) void curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { int level = 0; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); level = g_queue_get_length(priv->parent_q); for (int i = 0; i < level; i++) { printw(" "); } if (level > 0) { printw("* "); } curses_formatted_vprintf(out, format, args); } G_GNUC_PRINTF(2, 3) void curses_indented_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; va_start(ap, format); curses_indented_vprintf(out, format, ap); va_end(ap); } PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int") static int cluster_maint_mode_console(pcmk__output_t *out, va_list args) { unsigned long long flags = va_arg(args, unsigned long long); if (pcmk_is_set(flags, pe_flag_maintenance_mode)) { curses_formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); curses_formatted_printf(out, " The cluster will not attempt to start, stop or recover services\n"); return pcmk_rc_ok; } else if (pcmk_is_set(flags, pe_flag_stop_everything)) { curses_formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); curses_formatted_printf(out, " The cluster will keep all resources stopped\n"); return pcmk_rc_ok; } else { return pcmk_rc_no_output; } } PCMK__OUTPUT_ARGS("cluster-status", "pe_working_set_t *", "crm_exit_t", "stonith_history_t *", "gboolean", "uint32_t", "uint32_t", "const char *", "GList *", "GList *") static int cluster_status_console(pcmk__output_t *out, va_list args) { int rc = pcmk_rc_no_output; blank_screen(); rc = pcmk__cluster_status_text(out, args); refresh(); return rc; } PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "gboolean", "gboolean") static int stonith_event_console(pcmk__output_t *out, va_list args) { stonith_history_t *event = va_arg(args, stonith_history_t *); gboolean full_history = va_arg(args, gboolean); gboolean later_succeeded = va_arg(args, gboolean); crm_time_t *crm_when = crm_time_new(NULL); char *buf = NULL; crm_time_set_timet(crm_when, &(event->completed)); buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); switch (event->state) { case st_failed: curses_indented_printf(out, "%s of %s failed%s%s%s: " "delegate=%s, client=%s, origin=%s, %s='%s' %s\n", stonith_action_str(event->action), event->target, (event->exit_reason == NULL)? "" : " (", (event->exit_reason == NULL)? "" : event->exit_reason, (event->exit_reason == NULL)? "" : ")", event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-failed", buf, later_succeeded ? " (a later attempt succeeded)" : ""); break; case st_done: curses_indented_printf(out, "%s of %s successful: delegate=%s, client=%s, origin=%s, %s='%s'\n", stonith_action_str(event->action), event->target, event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-successful", buf); break; default: curses_indented_printf(out, "%s of %s pending: client=%s, origin=%s\n", stonith_action_str(event->action), event->target, event->client, event->origin); break; } free(buf); crm_time_free(crm_when); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "cluster-status", "console", cluster_status_console }, { "maint-mode", "console", cluster_maint_mode_console }, { "stonith-event", "console", stonith_event_console }, { NULL, NULL, NULL } }; #endif void crm_mon_register_messages(pcmk__output_t *out) { #if CURSES_ENABLED pcmk__register_messages(out, fmt_functions); #endif } void blank_screen(void) { #if CURSES_ENABLED int lpc = 0; for (lpc = 0; lpc < LINES; lpc++) { move(lpc, 0); clrtoeol(); } move(0, 0); refresh(); #endif }