diff options
Diffstat (limited to 'lib/common/output_xml.c')
-rw-r--r-- | lib/common/output_xml.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c new file mode 100644 index 0000000..0972638 --- /dev/null +++ b/lib/common/output_xml.c @@ -0,0 +1,541 @@ +/* + * Copyright 2019-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib.h> + +#include <crm/common/cmdline_internal.h> +#include <crm/common/xml.h> + +static gboolean legacy_xml = FALSE; +static gboolean simple_list = FALSE; +static gboolean substitute = FALSE; + +GOptionEntry pcmk__xml_output_entries[] = { + { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml, + NULL, + NULL }, + { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list, + NULL, + NULL }, + { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute, + NULL, + NULL }, + + { NULL } +}; + +typedef struct subst_s { + const char *from; + const char *to; +} subst_t; + +static subst_t substitutions[] = { + { "Active Resources", "resources" }, + { "Allocation Scores", "allocations" }, + { "Allocation Scores and Utilization Information", "allocations_utilizations" }, + { "Cluster Summary", "summary" }, + { "Current cluster status", "cluster_status" }, + { "Executing Cluster Transition", "transition" }, + { "Failed Resource Actions", "failures" }, + { "Fencing History", "fence_history" }, + { "Full List of Resources", "resources" }, + { "Inactive Resources", "resources" }, + { "Migration Summary", "node_history" }, + { "Negative Location Constraints", "bans" }, + { "Node Attributes", "node_attributes" }, + { "Operations", "node_history" }, + { "Resource Config", "resource_config" }, + { "Resource Operations", "operations" }, + { "Revised Cluster Status", "revised_cluster_status" }, + { "Transition Summary", "actions" }, + { "Utilization Information", "utilizations" }, + + { NULL, NULL } +}; + +/* The first several elements of this struct must be the same as the first + * several elements of private_data_s in lib/common/output_html.c. That + * struct gets passed to a bunch of the pcmk__output_xml_* functions which + * assume an XML private_data_s. Keeping them laid out the same means this + * still works. + */ +typedef struct private_data_s { + /* Begin members that must match the HTML version */ + xmlNode *root; + GQueue *parent_q; + GSList *errors; + /* End members that must match the HTML version */ + bool legacy_xml; +} private_data_t; + +static void +xml_free_priv(pcmk__output_t *out) { + private_data_t *priv = NULL; + + if (out == NULL || out->priv == NULL) { + return; + } + + priv = out->priv; + + free_xml(priv->root); + g_queue_free(priv->parent_q); + g_slist_free(priv->errors); + free(priv); + out->priv = NULL; +} + +static bool +xml_init(pcmk__output_t *out) { + private_data_t *priv = NULL; + + 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; + } + + if (legacy_xml) { + priv->root = create_xml_node(NULL, "crm_mon"); + crm_xml_add(priv->root, "version", PACEMAKER_VERSION); + } else { + priv->root = create_xml_node(NULL, "pacemaker-result"); + crm_xml_add(priv->root, "api-version", PCMK__API_VERSION); + + if (out->request != NULL) { + crm_xml_add(priv->root, "request", out->request); + } + } + + priv->parent_q = g_queue_new(); + priv->errors = NULL; + g_queue_push_tail(priv->parent_q, priv->root); + + /* Copy this from the file-level variable. This means that it is only settable + * as a command line option, and that pcmk__output_new must be called after all + * command line processing is completed. + */ + priv->legacy_xml = legacy_xml; + + return true; +} + +static void +add_error_node(gpointer data, gpointer user_data) { + char *str = (char *) data; + xmlNodePtr node = (xmlNodePtr) user_data; + pcmk_create_xml_text_node(node, "error", str); +} + +static void +xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { + private_data_t *priv = NULL; + xmlNodePtr node; + + CRM_ASSERT(out != NULL); + priv = out->priv; + + /* If root is NULL, xml_init failed and we are being called from pcmk__output_free + * in the pcmk__output_new path. + */ + if (priv == NULL || priv->root == NULL) { + return; + } + + if (legacy_xml) { + GSList *node = priv->errors; + + if (exit_status != CRM_EX_OK) { + fprintf(stderr, "%s\n", crm_exit_str(exit_status)); + } + + while (node != NULL) { + fprintf(stderr, "%s\n", (char *) node->data); + node = node->next; + } + } else { + char *rc_as_str = pcmk__itoa(exit_status); + + node = create_xml_node(priv->root, "status"); + pcmk__xe_set_props(node, "code", rc_as_str, + "message", crm_exit_str(exit_status), + NULL); + + if (g_slist_length(priv->errors) > 0) { + xmlNodePtr errors_node = create_xml_node(node, "errors"); + g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); + } + + free(rc_as_str); + } + + if (print) { + char *buf = dump_xml_formatted_with_text(priv->root); + fprintf(out->dest, "%s", buf); + fflush(out->dest); + free(buf); + } + + if (copy_dest != NULL) { + *copy_dest = copy_xml(priv->root); + } +} + +static void +xml_reset(pcmk__output_t *out) { + CRM_ASSERT(out != NULL); + + out->dest = freopen(NULL, "w", out->dest); + CRM_ASSERT(out->dest != NULL); + + xml_free_priv(out); + xml_init(out); +} + +static void +xml_subprocess_output(pcmk__output_t *out, int exit_status, + const char *proc_stdout, const char *proc_stderr) { + xmlNodePtr node, child_node; + char *rc_as_str = NULL; + + CRM_ASSERT(out != NULL); + + rc_as_str = pcmk__itoa(exit_status); + + node = pcmk__output_xml_create_parent(out, "command", + "code", rc_as_str, + NULL); + + if (proc_stdout != NULL) { + child_node = pcmk_create_xml_text_node(node, "output", proc_stdout); + crm_xml_add(child_node, "source", "stdout"); + } + + if (proc_stderr != NULL) { + child_node = pcmk_create_xml_text_node(node, "output", proc_stderr); + crm_xml_add(child_node, "source", "stderr"); + } + + free(rc_as_str); +} + +static void +xml_version(pcmk__output_t *out, bool extended) { + CRM_ASSERT(out != NULL); + + pcmk__output_create_xml_node(out, "version", + "program", "Pacemaker", + "version", PACEMAKER_VERSION, + "author", "Andrew Beekhof and the " + "Pacemaker project contributors", + "build", BUILD_VERSION, + "features", CRM_FEATURES, + NULL); +} + +G_GNUC_PRINTF(2, 3) +static void +xml_err(pcmk__output_t *out, const char *format, ...) { + private_data_t *priv = NULL; + int len = 0; + char *buf = NULL; + va_list ap; + + CRM_ASSERT(out != NULL && out->priv != NULL); + priv = out->priv; + + va_start(ap, format); + len = vasprintf(&buf, format, ap); + CRM_ASSERT(len > 0); + va_end(ap); + + priv->errors = g_slist_append(priv->errors, buf); +} + +G_GNUC_PRINTF(2, 3) +static int +xml_info(pcmk__output_t *out, const char *format, ...) { + return pcmk_rc_no_output; +} + +static void +xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { + xmlNodePtr parent = NULL; + xmlNodePtr cdata_node = NULL; + + CRM_ASSERT(out != NULL); + + parent = pcmk__output_create_xml_node(out, name, NULL); + cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf)); + xmlAddChild(parent, cdata_node); +} + +G_GNUC_PRINTF(4, 5) +static void +xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, + const char *format, ...) { + va_list ap; + char *name = NULL; + char *buf = NULL; + int len; + + CRM_ASSERT(out != NULL); + + va_start(ap, format); + len = vasprintf(&buf, format, ap); + CRM_ASSERT(len >= 0); + va_end(ap); + + if (substitute) { + for (subst_t *s = substitutions; s->from != NULL; s++) { + if (!strcmp(s->from, buf)) { + name = g_strdup(s->to); + break; + } + } + } + + if (name == NULL) { + name = g_ascii_strdown(buf, -1); + } + + if (legacy_xml || simple_list) { + pcmk__output_xml_create_parent(out, name, NULL); + } else { + pcmk__output_xml_create_parent(out, "list", + "name", name, + NULL); + } + + g_free(name); + free(buf); +} + +G_GNUC_PRINTF(3, 4) +static void +xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { + xmlNodePtr item_node = NULL; + va_list ap; + char *buf = NULL; + int len; + + CRM_ASSERT(out != NULL); + + va_start(ap, format); + len = vasprintf(&buf, format, ap); + CRM_ASSERT(len >= 0); + va_end(ap); + + item_node = pcmk__output_create_xml_text_node(out, "item", buf); + + if (name != NULL) { + crm_xml_add(item_node, "name", name); + } + + free(buf); +} + +static void +xml_increment_list(pcmk__output_t *out) { + /* This function intentially left blank */ +} + +static void +xml_end_list(pcmk__output_t *out) { + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + priv = out->priv; + + if (priv->legacy_xml || simple_list) { + g_queue_pop_tail(priv->parent_q); + } else { + char *buf = NULL; + xmlNodePtr node; + + node = g_queue_pop_tail(priv->parent_q); + buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); + crm_xml_add(node, "count", buf); + free(buf); + } +} + +static bool +xml_is_quiet(pcmk__output_t *out) { + return false; +} + +static void +xml_spacer(pcmk__output_t *out) { + /* This function intentionally left blank */ +} + +static void +xml_progress(pcmk__output_t *out, bool end) { + /* This function intentionally left blank */ +} + +pcmk__output_t * +pcmk__mk_xml_output(char **argv) { + pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); + + if (retval == NULL) { + return NULL; + } + + retval->fmt_name = "xml"; + retval->request = 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); + + 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); + + 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); + xmlNodeSetContent(node, (pcmkXmlStr) content); + return node; +} + +void +pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) { + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + CRM_ASSERT(parent != NULL); + CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); + + priv = out->priv; + + g_queue_push_tail(priv->parent_q, parent); +} + +void +pcmk__output_xml_pop_parent(pcmk__output_t *out) { + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return); + + priv = out->priv; + + CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); + g_queue_pop_tail(priv->parent_q); +} + +xmlNodePtr +pcmk__output_xml_peek_parent(pcmk__output_t *out) { + private_data_t *priv = NULL; + + CRM_ASSERT(out != NULL && out->priv != NULL); + CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL); + + priv = out->priv; + + /* If queue is empty NULL will be returned */ + return g_queue_peek_tail(priv->parent_q); +} |