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
}