root/lib/common/output_xml.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. xml_free_priv
  2. xml_init
  3. add_error_node
  4. xml_finish
  5. xml_reset
  6. xml_subprocess_output
  7. xml_version
  8. G_GNUC_PRINTF
  9. G_GNUC_PRINTF
  10. xml_output_xml
  11. G_GNUC_PRINTF
  12. G_GNUC_PRINTF
  13. xml_increment_list
  14. xml_end_list
  15. xml_is_quiet
  16. xml_spacer
  17. xml_progress
  18. pcmk__mk_xml_output
  19. pcmk__output_xml_create_parent
  20. pcmk__output_xml_add_node_copy
  21. pcmk__output_create_xml_node
  22. pcmk__output_create_xml_text_node
  23. pcmk__output_xml_push_parent
  24. pcmk__output_xml_pop_parent
  25. pcmk__output_xml_peek_parent

   1 /*
   2  * Copyright 2019-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <ctype.h>
  13 #include <stdarg.h>
  14 #include <stdlib.h>
  15 #include <stdio.h>
  16 #include <crm/crm.h>
  17 #include <crm/common/output.h>
  18 #include <crm/common/xml.h>
  19 #include <crm/common/xml_internal.h>  /* pcmk__xml2fd */
  20 #include <glib.h>
  21 
  22 #include <crm/common/cmdline_internal.h>
  23 #include <crm/common/xml.h>
  24 
  25 static gboolean legacy_xml = FALSE;
  26 static gboolean simple_list = FALSE;
  27 static gboolean substitute = FALSE;
  28 
  29 GOptionEntry pcmk__xml_output_entries[] = {
  30     { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml,
  31       NULL,
  32       NULL },
  33     { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list,
  34       NULL,
  35       NULL },
  36     { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute,
  37       NULL,
  38       NULL },
  39 
  40     { NULL }
  41 };
  42 
  43 typedef struct subst_s {
  44     const char *from;
  45     const char *to;
  46 } subst_t;
  47 
  48 static subst_t substitutions[] = {
  49     { "Active Resources",                               "resources" },
  50     { "Assignment Scores",                              "allocations" },
  51     { "Assignment Scores and Utilization Information",  "allocations_utilizations" },
  52     { "Cluster Summary",                                "summary" },
  53     { "Current cluster status",                         "cluster_status" },
  54     { "Executing Cluster Transition",                   "transition" },
  55     { "Failed Resource Actions",                        "failures" },
  56     { "Fencing History",                                "fence_history" },
  57     { "Full List of Resources",                         "resources" },
  58     { "Inactive Resources",                             "resources" },
  59     { "Migration Summary",                              "node_history" },
  60     { "Negative Location Constraints",                  "bans" },
  61     { "Node Attributes",                                "node_attributes" },
  62     { "Operations",                                     "node_history" },
  63     { "Resource Config",                                "resource_config" },
  64     { "Resource Operations",                            "operations" },
  65     { "Revised Cluster Status",                         "revised_cluster_status" },
  66     { "Transition Summary",                             "actions" },
  67     { "Utilization Information",                        "utilizations" },
  68 
  69     { NULL, NULL }
  70 };
  71 
  72 /* The first several elements of this struct must be the same as the first
  73  * several elements of private_data_s in lib/common/output_html.c.  That
  74  * struct gets passed to a bunch of the pcmk__output_xml_* functions which
  75  * assume an XML private_data_s.  Keeping them laid out the same means this
  76  * still works.
  77  */
  78 typedef struct private_data_s {
  79     /* Begin members that must match the HTML version */
  80     xmlNode *root;
  81     GQueue *parent_q;
  82     GSList *errors;
  83     /* End members that must match the HTML version */
  84     bool legacy_xml;
  85 } private_data_t;
  86 
  87 static void
  88 xml_free_priv(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  89     private_data_t *priv = NULL;
  90 
  91     if (out == NULL || out->priv == NULL) {
  92         return;
  93     }
  94 
  95     priv = out->priv;
  96 
  97     free_xml(priv->root);
  98     g_queue_free(priv->parent_q);
  99     g_slist_free(priv->errors);
 100     free(priv);
 101     out->priv = NULL;
 102 }
 103 
 104 static bool
 105 xml_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 106     private_data_t *priv = NULL;
 107 
 108     CRM_ASSERT(out != NULL);
 109 
 110     /* If xml_init was previously called on this output struct, just return. */
 111     if (out->priv != NULL) {
 112         return true;
 113     } else {
 114         out->priv = calloc(1, sizeof(private_data_t));
 115         if (out->priv == NULL) {
 116             return false;
 117         }
 118 
 119         priv = out->priv;
 120     }
 121 
 122     if (legacy_xml) {
 123         priv->root = create_xml_node(NULL, "crm_mon");
 124         crm_xml_add(priv->root, "version", PACEMAKER_VERSION);
 125     } else {
 126         priv->root = create_xml_node(NULL, "pacemaker-result");
 127         crm_xml_add(priv->root, "api-version", PCMK__API_VERSION);
 128 
 129         if (out->request != NULL) {
 130             crm_xml_add(priv->root, "request", out->request);
 131         }
 132     }
 133 
 134     priv->parent_q = g_queue_new();
 135     priv->errors = NULL;
 136     g_queue_push_tail(priv->parent_q, priv->root);
 137 
 138     /* Copy this from the file-level variable.  This means that it is only settable
 139      * as a command line option, and that pcmk__output_new must be called after all
 140      * command line processing is completed.
 141      */
 142     priv->legacy_xml = legacy_xml;
 143 
 144     return true;
 145 }
 146 
 147 static void
 148 add_error_node(gpointer data, gpointer user_data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 149     char *str = (char *) data;
 150     xmlNodePtr node = (xmlNodePtr) user_data;
 151     pcmk_create_xml_text_node(node, "error", str);
 152 }
 153 
 154 static void
 155 xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
 156     private_data_t *priv = NULL;
 157     xmlNodePtr node;
 158 
 159     CRM_ASSERT(out != NULL);
 160     priv = out->priv;
 161 
 162     /* If root is NULL, xml_init failed and we are being called from pcmk__output_free
 163      * in the pcmk__output_new path.
 164      */
 165     if (priv == NULL || priv->root == NULL) {
 166         return;
 167     }
 168 
 169     if (legacy_xml) {
 170         GSList *node = priv->errors;
 171 
 172         if (exit_status != CRM_EX_OK) {
 173             fprintf(stderr, "%s\n", crm_exit_str(exit_status));
 174         }
 175 
 176         while (node != NULL) {
 177             fprintf(stderr, "%s\n", (char *) node->data);
 178             node = node->next;
 179         }
 180     } else {
 181         char *rc_as_str = pcmk__itoa(exit_status);
 182 
 183         node = create_xml_node(priv->root, "status");
 184         pcmk__xe_set_props(node, "code", rc_as_str,
 185                            "message", crm_exit_str(exit_status),
 186                            NULL);
 187 
 188         if (g_slist_length(priv->errors) > 0) {
 189             xmlNodePtr errors_node = create_xml_node(node, "errors");
 190             g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node);
 191         }
 192 
 193         free(rc_as_str);
 194     }
 195 
 196     if (print) {
 197         pcmk__xml2fd(fileno(out->dest), priv->root);
 198     }
 199 
 200     if (copy_dest != NULL) {
 201         *copy_dest = copy_xml(priv->root);
 202     }
 203 }
 204 
 205 static void
 206 xml_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 207     CRM_ASSERT(out != NULL);
 208 
 209     out->dest = freopen(NULL, "w", out->dest);
 210     CRM_ASSERT(out->dest != NULL);
 211 
 212     xml_free_priv(out);
 213     xml_init(out);
 214 }
 215 
 216 static void
 217 xml_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 218                       const char *proc_stdout, const char *proc_stderr) {
 219     xmlNodePtr node, child_node;
 220     char *rc_as_str = NULL;
 221 
 222     CRM_ASSERT(out != NULL);
 223 
 224     rc_as_str = pcmk__itoa(exit_status);
 225 
 226     node = pcmk__output_xml_create_parent(out, "command",
 227                                           "code", rc_as_str,
 228                                           NULL);
 229 
 230     if (proc_stdout != NULL) {
 231         child_node = pcmk_create_xml_text_node(node, "output", proc_stdout);
 232         crm_xml_add(child_node, "source", "stdout");
 233     }
 234 
 235     if (proc_stderr != NULL) {
 236         child_node = pcmk_create_xml_text_node(node, "output", proc_stderr);
 237         crm_xml_add(child_node, "source", "stderr");
 238     }
 239 
 240     free(rc_as_str);
 241 }
 242 
 243 static void
 244 xml_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 245     CRM_ASSERT(out != NULL);
 246 
 247     pcmk__output_create_xml_node(out, "version",
 248                                  "program", "Pacemaker",
 249                                  "version", PACEMAKER_VERSION,
 250                                  "author", "Andrew Beekhof and the "
 251                                            "Pacemaker project contributors",
 252                                  "build", BUILD_VERSION,
 253                                  "features", CRM_FEATURES,
 254                                  NULL);
 255 }
 256 
 257 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 static void
 259 xml_err(pcmk__output_t *out, const char *format, ...) {
 260     private_data_t *priv = NULL;
 261     int len = 0;
 262     char *buf = NULL;
 263     va_list ap;
 264 
 265     CRM_ASSERT(out != NULL && out->priv != NULL);
 266     priv = out->priv;
 267 
 268     va_start(ap, format);
 269     len = vasprintf(&buf, format, ap);
 270     CRM_ASSERT(len > 0);
 271     va_end(ap);
 272 
 273     priv->errors = g_slist_append(priv->errors, buf);
 274 }
 275 
 276 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 277 static int
 278 xml_info(pcmk__output_t *out, const char *format, ...) {
 279     return pcmk_rc_no_output;
 280 }
 281 
 282 static void
 283 xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 284     xmlNodePtr parent = NULL;
 285     xmlNodePtr cdata_node = NULL;
 286 
 287     CRM_ASSERT(out != NULL);
 288 
 289     parent = pcmk__output_create_xml_node(out, name, NULL);
 290     if (parent == NULL) {
 291         return;
 292     }
 293     cdata_node = xmlNewCDataBlock(parent->doc, (pcmkXmlStr) buf, strlen(buf));
 294     xmlAddChild(parent, cdata_node);
 295 }
 296 
 297 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 298 static void
 299 xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
 300                const char *format, ...) {
 301     va_list ap;
 302     char *name = NULL;
 303     char *buf = NULL;
 304     int len;
 305 
 306     CRM_ASSERT(out != NULL);
 307 
 308     va_start(ap, format);
 309     len = vasprintf(&buf, format, ap);
 310     CRM_ASSERT(len >= 0);
 311     va_end(ap);
 312 
 313     if (substitute) {
 314         for (subst_t *s = substitutions; s->from != NULL; s++) {
 315             if (!strcmp(s->from, buf)) {
 316                 name = g_strdup(s->to);
 317                 break;
 318             }
 319         }
 320     }
 321 
 322     if (name == NULL) {
 323         name = g_ascii_strdown(buf, -1);
 324     }
 325 
 326     if (legacy_xml || simple_list) {
 327         pcmk__output_xml_create_parent(out, name, NULL);
 328     } else {
 329         pcmk__output_xml_create_parent(out, "list",
 330                                        "name", name,
 331                                        NULL);
 332     }
 333 
 334     g_free(name);
 335     free(buf);
 336 }
 337 
 338 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 339 static void
 340 xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
 341     xmlNodePtr item_node = NULL;
 342     va_list ap;
 343     char *buf = NULL;
 344     int len;
 345 
 346     CRM_ASSERT(out != NULL);
 347 
 348     va_start(ap, format);
 349     len = vasprintf(&buf, format, ap);
 350     CRM_ASSERT(len >= 0);
 351     va_end(ap);
 352 
 353     item_node = pcmk__output_create_xml_text_node(out, "item", buf);
 354 
 355     if (name != NULL) {
 356         crm_xml_add(item_node, "name", name);
 357     }
 358 
 359     free(buf);
 360 }
 361 
 362 static void
 363 xml_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 364     /* This function intentially left blank */
 365 }
 366 
 367 static void
 368 xml_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 369     private_data_t *priv = NULL;
 370 
 371     CRM_ASSERT(out != NULL && out->priv != NULL);
 372     priv = out->priv;
 373 
 374     if (priv->legacy_xml || simple_list) {
 375         g_queue_pop_tail(priv->parent_q);
 376     } else {
 377         char *buf = NULL;
 378         xmlNodePtr node;
 379 
 380         node = g_queue_pop_tail(priv->parent_q);
 381         buf = crm_strdup_printf("%lu", xmlChildElementCount(node));
 382         crm_xml_add(node, "count", buf);
 383         free(buf);
 384     }
 385 }
 386 
 387 static bool
 388 xml_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 389     return false;
 390 }
 391 
 392 static void
 393 xml_spacer(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 394     /* This function intentionally left blank */
 395 }
 396 
 397 static void
 398 xml_progress(pcmk__output_t *out, bool end) {
     /* [previous][next][first][last][top][bottom][index][help] */
 399     /* This function intentionally left blank */
 400 }
 401 
 402 pcmk__output_t *
 403 pcmk__mk_xml_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 404     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 405 
 406     if (retval == NULL) {
 407         return NULL;
 408     }
 409 
 410     retval->fmt_name = "xml";
 411     retval->request = pcmk__quote_cmdline(argv);
 412 
 413     retval->init = xml_init;
 414     retval->free_priv = xml_free_priv;
 415     retval->finish = xml_finish;
 416     retval->reset = xml_reset;
 417 
 418     retval->register_message = pcmk__register_message;
 419     retval->message = pcmk__call_message;
 420 
 421     retval->subprocess_output = xml_subprocess_output;
 422     retval->version = xml_version;
 423     retval->info = xml_info;
 424     retval->transient = xml_info;
 425     retval->err = xml_err;
 426     retval->output_xml = xml_output_xml;
 427 
 428     retval->begin_list = xml_begin_list;
 429     retval->list_item = xml_list_item;
 430     retval->increment_list = xml_increment_list;
 431     retval->end_list = xml_end_list;
 432 
 433     retval->is_quiet = xml_is_quiet;
 434     retval->spacer = xml_spacer;
 435     retval->progress = xml_progress;
 436     retval->prompt = pcmk__text_prompt;
 437 
 438     return retval;
 439 }
 440 
 441 xmlNodePtr
 442 pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 443     va_list args;
 444     xmlNodePtr node = NULL;
 445 
 446     CRM_ASSERT(out != NULL);
 447     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
 448 
 449     node = pcmk__output_create_xml_node(out, name, NULL);
 450 
 451     va_start(args, name);
 452     pcmk__xe_set_propv(node, args);
 453     va_end(args);
 454 
 455     pcmk__output_xml_push_parent(out, node);
 456     return node;
 457 }
 458 
 459 void
 460 pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node) {
     /* [previous][next][first][last][top][bottom][index][help] */
 461     private_data_t *priv = NULL;
 462     xmlNodePtr parent = NULL;
 463 
 464     CRM_ASSERT(out != NULL && out->priv != NULL);
 465     CRM_ASSERT(node != NULL);
 466     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
 467 
 468     priv = out->priv;
 469     parent = g_queue_peek_tail(priv->parent_q);
 470 
 471     // Shouldn't happen unless the caller popped priv->root
 472     CRM_CHECK(parent != NULL, return);
 473 
 474     add_node_copy(parent, node);
 475 }
 476 
 477 xmlNodePtr
 478 pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 479     xmlNodePtr node = NULL;
 480     private_data_t *priv = NULL;
 481     va_list args;
 482 
 483     CRM_ASSERT(out != NULL && out->priv != NULL);
 484     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
 485 
 486     priv = out->priv;
 487 
 488     node = create_xml_node(g_queue_peek_tail(priv->parent_q), name);
 489     va_start(args, name);
 490     pcmk__xe_set_propv(node, args);
 491     va_end(args);
 492 
 493     return node;
 494 }
 495 
 496 xmlNodePtr
 497 pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) {
     /* [previous][next][first][last][top][bottom][index][help] */
 498     xmlNodePtr node = NULL;
 499 
 500     CRM_ASSERT(out != NULL);
 501     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
 502 
 503     node = pcmk__output_create_xml_node(out, name, NULL);
 504     xmlNodeSetContent(node, (pcmkXmlStr) content);
 505     return node;
 506 }
 507 
 508 void
 509 pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) {
     /* [previous][next][first][last][top][bottom][index][help] */
 510     private_data_t *priv = NULL;
 511 
 512     CRM_ASSERT(out != NULL && out->priv != NULL);
 513     CRM_ASSERT(parent != NULL);
 514     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
 515 
 516     priv = out->priv;
 517 
 518     g_queue_push_tail(priv->parent_q, parent);
 519 }
 520 
 521 void
 522 pcmk__output_xml_pop_parent(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 523     private_data_t *priv = NULL;
 524 
 525     CRM_ASSERT(out != NULL && out->priv != NULL);
 526     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return);
 527 
 528     priv = out->priv;
 529 
 530     CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0);
 531     g_queue_pop_tail(priv->parent_q);
 532 }
 533 
 534 xmlNodePtr
 535 pcmk__output_xml_peek_parent(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 536     private_data_t *priv = NULL;
 537 
 538     CRM_ASSERT(out != NULL && out->priv != NULL);
 539     CRM_CHECK(pcmk__str_any_of(out->fmt_name, "xml", "html", NULL), return NULL);
 540 
 541     priv = out->priv;
 542 
 543     /* If queue is empty NULL will be returned */
 544     return g_queue_peek_tail(priv->parent_q);
 545 }

/* [previous][next][first][last][top][bottom][index][help] */