diff --git a/lib/common/output_html.c b/lib/common/output_html.c index cf4fc2bdd6..44d869c1e7 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -1,483 +1,489 @@ /* * Copyright 2019-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 #include #include #include #include #include #include static const char *stylesheet_default = "." PCMK__VALUE_BOLD " { font-weight: bold }\n" "." PCMK_VALUE_ONLINE " { color: green }\n" "." PCMK_VALUE_OFFLINE " { color: red }\n" "." PCMK__VALUE_MAINT " { color: blue }\n" "." PCMK_VALUE_STANDBY " { color: blue }\n" "." PCMK__VALUE_HEALTH_RED " { color: red }\n" "." PCMK__VALUE_HEALTH_YELLOW " { color: GoldenRod }\n" "." PCMK__VALUE_RSC_FAILED " { color: red }\n" "." PCMK__VALUE_RSC_FAILURE_IGNORED " { color: DarkGreen }\n" "." PCMK__VALUE_RSC_MANAGED " { color: blue }\n" "." PCMK__VALUE_RSC_MULTIPLE " { color: orange }\n" "." PCMK__VALUE_RSC_OK " { color: green }\n" "." PCMK__VALUE_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 = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; - xmlFreeNode(priv->root); + free_xml(priv->root); + /* The elements of parent_q are xmlNodes that are a part of the + * priv->root document, so the above line already frees them. Don't + * call g_queue_free_full here. + */ g_queue_free(priv->parent_q); - g_slist_free(priv->errors); + g_slist_free_full(priv->errors, free); free(priv); out->priv = NULL; } static bool html_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != 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, PCMK_XA_LANG, PCMK__VALUE_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 = NULL; htmlNodePtr head_node = NULL; htmlNodePtr charset_node = NULL; CRM_ASSERT(out != NULL); priv = out->priv; /* 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 = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL); 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, PCMK__XE_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, PCMK__XE_DIV, NULL, NULL, rc_buf); if (proc_stdout != NULL) { pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stdout"); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, PCMK__VALUE_OUTPUT, proc_stdout); } if (proc_stderr != NULL) { pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stderr"); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, PCMK__VALUE_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, PCMK__XE_DIV, NULL, NULL, "Program: Pacemaker"); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Version: " PACEMAKER_VERSION); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Author: Andrew Beekhof and " "the Pacemaker project contributors"); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Build: " BUILD_VERSION); pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Features: " 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, PCMK_XA_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, PCMK_XA_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. */ + /* Remove the
        tag, but do not free this result - it's still + * part of the document. + */ 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 = pcmk__quote_cmdline(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->transient = 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); CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL); node = pcmk__output_create_xml_text_node(out, element_name, text); if (class_name != NULL) { crm_xml_add(node, PCMK_XA_CLASS, class_name); } if (id != NULL) { crm_xml_add(node, PCMK_XA_ID, id); } return node; } void pcmk__html_add_header(const char *name, ...) { htmlNodePtr header_node; va_list ap; va_start(ap, name); header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL); 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_text.c b/lib/common/output_text.c index 3712f519f0..54934bb06d 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,512 +1,520 @@ /* * Copyright 2019-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 #include #include #include #include #include "crmcommon_private.h" // @COMPAT Drop at 3.0.0 static gboolean fancy = FALSE; // @COMPAT Drop at 3.0.0 GOptionEntry pcmk__text_output_entries[] = { { "text-fancy", 0, G_OPTION_FLAG_HIDDEN, 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; bool fancy; } private_data_t; +static void +free_list_data(gpointer data) { + text_list_data_t *list_data = data; + + free(list_data->singular_noun); + free(list_data->plural_noun); +} + static void text_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; - g_queue_free(priv->parent_q); + g_queue_free_full(priv->parent_q, free_list_data); free(priv); out->priv = NULL; } static bool text_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); /* If text_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->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) { CRM_ASSERT(out != NULL && out->dest != NULL); fflush(out->dest); } static void text_reset(pcmk__output_t *out) { private_data_t *priv = NULL; bool old_fancy = false; CRM_ASSERT(out != NULL); if (out->dest != stdout) { out->dest = freopen(NULL, "w", out->dest); } CRM_ASSERT(out->dest != NULL); // Save priv->fancy before free/init sequence overwrites it priv = out->priv; old_fancy = priv->fancy; text_free_priv(out); text_init(out); priv = out->priv; priv->fancy = old_fancy; } 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 && out->dest != 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 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; } G_GNUC_PRINTF(2, 3) static int text_transient(pcmk__output_t *out, const char *format, ...) { return pcmk_rc_no_output; } 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 || priv->fancy) && (format != NULL)) { 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, ...) { private_data_t *priv = NULL; va_list ap; CRM_ASSERT(out != NULL); priv = out->priv; va_start(ap, format); if (fancy || priv->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); + free_list_data(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 = pcmk__quote_cmdline(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->transient = text_transient; 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; } /*! * \internal * \brief Check whether fancy output is enabled for a text output object * * This returns \c false if the output object is not of text format. * * \param[in] out Output object * * \return \c true if \p out has fancy output enabled, or \c false otherwise */ bool pcmk__output_text_get_fancy(pcmk__output_t *out) { CRM_ASSERT(out != NULL); if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); return priv->fancy; } return false; } /*! * \internal * \brief Enable or disable fancy output for a text output object * * This does nothing if the output object is not of text format. * * \param[in,out] out Output object * \param[in] enabled Whether fancy output should be enabled for \p out */ void pcmk__output_text_set_fancy(pcmk__output_t *out, bool enabled) { CRM_ASSERT(out != NULL); if (pcmk__str_eq(out->fmt_name, "text", pcmk__str_none)) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); priv->fancy = enabled; } } 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); CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return); 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) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(pcmk__str_eq(out->fmt_name, "text", pcmk__str_none), return); priv = out->priv; if (fancy || priv->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 HAVE_SSCANF_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 ccc307fe9c..fd7bea964e 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,617 +1,624 @@ /* * Copyright 2019-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 #include #include #include #include #include #include #include #include #include // pcmk__xml2fd typedef struct subst_s { const char *from; const char *to; } subst_t; static const subst_t substitutions[] = { { "Active Resources", PCMK_XE_RESOURCES, }, { "Assignment Scores", PCMK_XE_ALLOCATIONS, }, { "Assignment Scores and Utilization Information", PCMK_XE_ALLOCATIONS_UTILIZATIONS, }, { "Cluster Summary", PCMK_XE_SUMMARY, }, { "Current cluster status", PCMK_XE_CLUSTER_STATUS, }, { "Executing Cluster Transition", PCMK_XE_TRANSITION, }, { "Failed Resource Actions", PCMK_XE_FAILURES, }, { "Fencing History", PCMK_XE_FENCE_HISTORY, }, { "Full List of Resources", PCMK_XE_RESOURCES, }, { "Inactive Resources", PCMK_XE_RESOURCES, }, { "Migration Summary", PCMK_XE_NODE_HISTORY, }, { "Negative Location Constraints", PCMK_XE_BANS, }, { "Node Attributes", PCMK_XE_NODE_ATTRIBUTES, }, { "Operations", PCMK_XE_NODE_HISTORY, }, { "Resource Config", PCMK_XE_RESOURCE_CONFIG, }, { "Resource Operations", PCMK_XE_OPERATIONS, }, { "Revised Cluster Status", PCMK_XE_REVISED_CLUSTER_STATUS, }, { "Timings", PCMK_XE_TIMINGS, }, { "Transition Summary", PCMK_XE_ACTIONS, }, { "Utilization Information", PCMK_XE_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; bool list_element; } private_data_t; static bool has_root_node(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); priv = out->priv; return priv != NULL && priv->root != NULL; } static void add_root_node(pcmk__output_t *out) { private_data_t *priv = NULL; /* has_root_node will assert if out is NULL, so no need to do it here */ if (has_root_node(out)) { return; } priv = out->priv; if (priv->legacy_xml) { priv->root = create_xml_node(NULL, PCMK_XE_CRM_MON); crm_xml_add(priv->root, PCMK_XA_VERSION, PACEMAKER_VERSION); } else { priv->root = create_xml_node(NULL, PCMK_XE_PACEMAKER_RESULT); crm_xml_add(priv->root, PCMK_XA_API_VERSION, PCMK__API_VERSION); crm_xml_add(priv->root, PCMK_XA_REQUEST, pcmk__s(out->request, "libpacemaker")); } priv->parent_q = g_queue_new(); g_queue_push_tail(priv->parent_q, priv->root); } static void xml_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; if (has_root_node(out)) { free_xml(priv->root); + /* The elements of parent_q are xmlNodes that are a part of the + * priv->root document, so the above line already frees them. Don't + * call g_queue_free_full here. + */ g_queue_free(priv->parent_q); } - g_slist_free(priv->errors); + g_slist_free_full(priv->errors, free); free(priv); out->priv = NULL; } static bool xml_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != 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; } priv->errors = NULL; 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, PCMK_XE_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 (priv == NULL) { return; } add_root_node(out); if (priv->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, PCMK_XE_STATUS); pcmk__xe_set_props(node, PCMK_XA_CODE, rc_as_str, PCMK_XA_MESSAGE, crm_exit_str(exit_status), NULL); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = create_xml_node(node, PCMK_XE_ERRORS); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } free(rc_as_str); } if (print) { pcmk__xml2fd(fileno(out->dest), priv->root); } 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, PCMK_XE_COMMAND, PCMK_XA_CODE, rc_as_str, NULL); if (proc_stdout != NULL) { child_node = pcmk_create_xml_text_node(node, PCMK_XE_OUTPUT, proc_stdout); crm_xml_add(child_node, PCMK_XA_SOURCE, "stdout"); } if (proc_stderr != NULL) { child_node = pcmk_create_xml_text_node(node, PCMK_XE_OUTPUT, proc_stderr); crm_xml_add(child_node, PCMK_XA_SOURCE, "stderr"); } free(rc_as_str); } static void xml_version(pcmk__output_t *out, bool extended) { const char *author = "Andrew Beekhof and the Pacemaker project " "contributors"; CRM_ASSERT(out != NULL); pcmk__output_create_xml_node(out, PCMK_XE_VERSION, PCMK_XA_PROGRAM, "Pacemaker", PCMK_XA_VERSION, PACEMAKER_VERSION, PCMK_XA_AUTHOR, author, PCMK_XA_BUILD, BUILD_VERSION, PCMK_XA_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; add_root_node(out); 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); if (parent == NULL) { return; } cdata_node = xmlNewCDataBlock(parent->doc, (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; private_data_t *priv = NULL; 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); for (const subst_t *s = substitutions; s->from != NULL; s++) { if (strcmp(s->from, buf) == 0) { name = g_strdup(s->to); break; } } if (name == NULL) { name = g_ascii_strdown(buf, -1); } if (priv->list_element) { pcmk__output_xml_create_parent(out, PCMK_XE_LIST, PCMK_XA_NAME, name, NULL); } else { pcmk__output_xml_create_parent(out, 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, PCMK_XE_ITEM, buf); if (name != NULL) { crm_xml_add(item_node, PCMK_XA_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->list_element) { char *buf = NULL; xmlNodePtr node; + /* Do not free node here - it's still part of the document */ node = g_queue_pop_tail(priv->parent_q); buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); crm_xml_add(node, PCMK_XA_COUNT, buf); free(buf); } else { + /* Do not free this result - it's still part of the document */ g_queue_pop_tail(priv->parent_q); } } 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 = pcmk__quote_cmdline(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->transient = 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); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return 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_copy(pcmk__output_t *out, xmlNodePtr node) { private_data_t *priv = NULL; xmlNodePtr parent = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_ASSERT(node != NULL); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); add_root_node(out); priv = out->priv; parent = g_queue_peek_tail(priv->parent_q); // Shouldn't happen unless the caller popped priv->root CRM_CHECK(parent != NULL, return); add_node_copy(parent, 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_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); add_root_node(out); 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); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); node = pcmk__output_create_xml_node(out, name, NULL); pcmk__xe_set_content(node, 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); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); add_root_node(out); 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); CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); add_root_node(out); priv = out->priv; CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); + /* Do not free this result - it's still part of the document */ 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_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); add_root_node(out); priv = out->priv; /* If queue is empty NULL will be returned */ return g_queue_peek_tail(priv->parent_q); } void pcmk__output_set_legacy_xml(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) { return; } CRM_ASSERT(out->priv != NULL); priv = out->priv; priv->legacy_xml = true; } void pcmk__output_enable_list_element(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); if (!pcmk__str_eq(out->fmt_name, "xml", pcmk__str_none)) { return; } CRM_ASSERT(out->priv != NULL); priv = out->priv; priv->list_element = true; } diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c index dad8dd7c77..e1d34ad8df 100644 --- a/tools/crm_mon_curses.c +++ b/tools/crm_mon_curses.c @@ -1,486 +1,494 @@ /* * Copyright 2019-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 #include #include #include #include #include #include #include // stonith__history_description() #include #include #include #include "crm_mon.h" #if CURSES_ENABLED 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 +free_list_data(gpointer data) { + curses_list_data_t *list_data = data; + + free(list_data->singular_noun); + free(list_data->plural_noun); +} + static void curses_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; - g_queue_free(priv->parent_q); + g_queue_free_full(priv->parent_q, free_list_data); free(priv); out->priv = NULL; } static bool curses_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != 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. * This function is empty because we create a text object instead of a console * object if version is requested, so this is never called. */ static void curses_ver(pcmk__output_t *out, bool extended) { CRM_ASSERT(out != NULL); } 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); + free_list_data(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 && 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 = pcmk__quote_cmdline(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->transient = 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 = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; 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, pcmk_sched_in_maintenance)) { 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, pcmk_sched_stop_all)) { 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", "pcmk_scheduler_t *", "enum pcmk_pacemakerd_state", "crm_exit_t", "stonith_history_t *", "enum pcmk__fence_history", "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; clear(); rc = pcmk__cluster_status_text(out, args); refresh(); return rc; } PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool", "const char *", "uint32_t") static int stonith_event_console(pcmk__output_t *out, va_list args) { stonith_history_t *event = va_arg(args, stonith_history_t *); bool full_history = va_arg(args, int); bool completed_only G_GNUC_UNUSED = va_arg(args, int); const char *succeeded = va_arg(args, const char *); uint32_t show_opts = va_arg(args, uint32_t); gchar *desc = stonith__history_description(event, full_history, succeeded, show_opts); curses_indented_printf(out, "%s\n", desc); g_free(desc); 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 }