diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index 34c68219b5..0594444e2e 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,563 +1,602 @@ /* * 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 static gboolean legacy_xml = FALSE; static gboolean simple_list = 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 }, { NULL } }; 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; } 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 (legacy_xml || 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; - free_xml(priv->root); - g_queue_free(priv->parent_q); + if (has_root_node(out)) { + 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->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(); 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, 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 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) { + if (priv == NULL) { return; } + add_root_node(out); + if (legacy_xml || 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 (legacy_xml || priv->legacy_xml || simple_list) { pcmk__output_xml_create_parent(out, name, NULL); } else { pcmk__output_xml_create_parent(out, PCMK_XE_LIST, PCMK_XA_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, 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->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, PCMK_XA_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); + 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); 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); }