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

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