root/lib/common/xml_display.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__log_xmllib_err
  2. show_xml_comment
  3. show_xml_element
  4. show_xml_node
  5. pcmk__xml_show
  6. show_xml_changes_recursive
  7. pcmk__xml_show_changes
  8. log_data_element
  9. xml_log_changes

   1 /*
   2  * Copyright 2004-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 <libxml/tree.h>
  13 
  14 #include <crm/crm.h>
  15 #include <crm/msg_xml.h>
  16 #include <crm/common/xml.h>
  17 #include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
  18 #include "crmcommon_private.h"
  19 
  20 static int show_xml_node(pcmk__output_t *out, GString *buffer,
  21                          const char *prefix, const xmlNode *data, int depth,
  22                          uint32_t options);
  23 
  24 // Log an XML library error
  25 void
  26 pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
  27 {
  28     va_list ap;
  29 
  30     va_start(ap, fmt);
  31     pcmk__if_tracing(
  32         {
  33             PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
  34                                crm_abort(__FILE__, __PRETTY_FUNCTION__,
  35                                          __LINE__, "xml library error", TRUE,
  36                                          TRUE),
  37                                "XML Error: ", fmt, ap);
  38         },
  39         {
  40             PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
  41         }
  42     );
  43     va_end(ap);
  44 }
  45 
  46 /*!
  47  * \internal
  48  * \brief Output an XML comment with depth-based indentation
  49  *
  50  * \param[in,out] out      Output object
  51  * \param[in]     data     XML node to output
  52  * \param[in]     depth    Current indentation level
  53  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  54  *
  55  * \return Standard Pacemaker return code
  56  *
  57  * \note This currently produces output only for text-like output objects.
  58  */
  59 static int
  60 show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
  61                  uint32_t options)
  62 {
  63     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
  64         int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  65 
  66         return out->info(out, "%*s<!--%s-->",
  67                          width, "", (const char *) data->content);
  68     }
  69     return pcmk_rc_no_output;
  70 }
  71 
  72 /*!
  73  * \internal
  74  * \brief Output an XML element in a formatted way
  75  *
  76  * \param[in,out] out      Output object
  77  * \param[in,out] buffer   Where to build output strings
  78  * \param[in]     prefix   String to prepend to every line of output
  79  * \param[in]     data     XML node to output
  80  * \param[in]     depth    Current indentation level
  81  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  82  *
  83  * \return Standard Pacemaker return code
  84  *
  85  * \note This is a recursive helper function for \p show_xml_node().
  86  * \note This currently produces output only for text-like output objects.
  87  * \note \p buffer may be overwritten many times. The caller is responsible for
  88  *       freeing it using \p g_string_free() but should not rely on its
  89  *       contents.
  90  */
  91 static int
  92 show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
  93                  const xmlNode *data, int depth, uint32_t options)
  94 {
  95     const char *name = crm_element_name(data);
  96     int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  97     int rc = pcmk_rc_no_output;
  98 
  99     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
 100         const char *hidden = crm_element_value(data, "hidden");
 101 
 102         g_string_truncate(buffer, 0);
 103 
 104         for (int lpc = 0; lpc < spaces; lpc++) {
 105             g_string_append_c(buffer, ' ');
 106         }
 107         pcmk__g_strcat(buffer, "<", name, NULL);
 108 
 109         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 110              attr = attr->next) {
 111             xml_node_private_t *nodepriv = attr->_private;
 112             const char *p_name = (const char *) attr->name;
 113             const char *p_value = pcmk__xml_attr_value(attr);
 114             char *p_copy = NULL;
 115 
 116             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 117                 continue;
 118             }
 119 
 120             // @COMPAT Remove when v1 patchsets are removed
 121             if (pcmk_any_flags_set(options,
 122                                    pcmk__xml_fmt_diff_plus
 123                                    |pcmk__xml_fmt_diff_minus)
 124                 && (strcmp(XML_DIFF_MARKER, p_name) == 0)) {
 125                 continue;
 126             }
 127 
 128             if ((hidden != NULL) && (p_name[0] != '\0')
 129                 && (strstr(hidden, p_name) != NULL)) {
 130                 pcmk__str_update(&p_copy, "*****");
 131 
 132             } else {
 133                 p_copy = crm_xml_escape(p_value);
 134             }
 135 
 136             pcmk__g_strcat(buffer, " ", p_name, "=\"",
 137                            pcmk__s(p_copy, "<null>"), "\"", NULL);
 138             free(p_copy);
 139         }
 140 
 141         if (xml_has_children(data)
 142             && pcmk_is_set(options, pcmk__xml_fmt_children)) {
 143             g_string_append_c(buffer, '>');
 144 
 145         } else {
 146             g_string_append(buffer, "/>");
 147         }
 148 
 149         rc = out->info(out, "%s%s%s",
 150                        pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
 151                        buffer->str);
 152     }
 153 
 154     if (!xml_has_children(data)) {
 155         return rc;
 156     }
 157 
 158     if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
 159         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 160              child = pcmk__xml_next(child)) {
 161 
 162             int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
 163                                         options
 164                                         |pcmk__xml_fmt_open
 165                                         |pcmk__xml_fmt_close);
 166             rc = pcmk__output_select_rc(rc, temp_rc);
 167         }
 168     }
 169 
 170     if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
 171         int temp_rc = out->info(out, "%s%s%*s</%s>",
 172                                 pcmk__s(prefix, ""),
 173                                 pcmk__str_empty(prefix)? "" : " ",
 174                                 spaces, "", name);
 175         rc = pcmk__output_select_rc(rc, temp_rc);
 176     }
 177 
 178     return rc;
 179 }
 180 
 181 /*!
 182  * \internal
 183  * \brief Output an XML element or comment in a formatted way
 184  *
 185  * \param[in,out] out      Output object
 186  * \param[in,out] buffer   Where to build output strings
 187  * \param[in]     prefix   String to prepend to every line of output
 188  * \param[in]     data     XML node to log
 189  * \param[in]     depth    Current indentation level
 190  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 191  *
 192  * \return Standard Pacemaker return code
 193  *
 194  * \note This is a recursive helper function for \p pcmk__xml_show().
 195  * \note This currently produces output only for text-like output objects.
 196  * \note \p buffer may be overwritten many times. The caller is responsible for
 197  *       freeing it using \p g_string_free() but should not rely on its
 198  *       contents.
 199  */
 200 static int
 201 show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
 202               const xmlNode *data, int depth, uint32_t options)
 203 {
 204     switch (data->type) {
 205         case XML_COMMENT_NODE:
 206             return show_xml_comment(out, data, depth, options);
 207         case XML_ELEMENT_NODE:
 208             return show_xml_element(out, buffer, prefix, data, depth, options);
 209         default:
 210             return pcmk_rc_no_output;
 211     }
 212 }
 213 
 214 /*!
 215  * \internal
 216  * \brief Output an XML element or comment in a formatted way
 217  *
 218  * \param[in,out] out        Output object
 219  * \param[in]     prefix     String to prepend to every line of output
 220  * \param[in]     data       XML node to output
 221  * \param[in]     depth      Current nesting level
 222  * \param[in]     options    Group of \p pcmk__xml_fmt_options flags
 223  *
 224  * \return Standard Pacemaker return code
 225  *
 226  * \note This currently produces output only for text-like output objects.
 227  */
 228 int
 229 pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
     /* [previous][next][first][last][top][bottom][index][help] */
 230                int depth, uint32_t options)
 231 {
 232     int rc = pcmk_rc_no_output;
 233     GString *buffer = NULL;
 234 
 235     CRM_ASSERT(out != NULL);
 236     CRM_CHECK(depth >= 0, depth = 0);
 237 
 238     if (data == NULL) {
 239         return rc;
 240     }
 241 
 242     /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
 243      * recursive calls
 244      */
 245     buffer = g_string_sized_new(1024);
 246     rc = show_xml_node(out, buffer, prefix, data, depth, options);
 247     g_string_free(buffer, TRUE);
 248 
 249     return rc;
 250 }
 251 
 252 /*!
 253  * \internal
 254  * \brief Output XML portions that have been marked as changed
 255  *
 256  * \param[in,out] out      Output object
 257  * \param[in]     data     XML node to output
 258  * \param[in]     depth    Current indentation level
 259  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 260  *
 261  * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing
 262  *       changes to \p data and its children.
 263  * \note This currently produces output only for text-like output objects.
 264  */
 265 static int
 266 show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
 267                            uint32_t options)
 268 {
 269     /* @COMPAT: When log_data_element() is removed, we can remove the options
 270      * argument here and instead hard-code pcmk__xml_log_pretty.
 271      */
 272     xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
 273     int rc = pcmk_rc_no_output;
 274     int temp_rc = pcmk_rc_no_output;
 275 
 276     if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
 277         // Newly created
 278         return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
 279                               options
 280                               |pcmk__xml_fmt_open
 281                               |pcmk__xml_fmt_children
 282                               |pcmk__xml_fmt_close);
 283     }
 284 
 285     if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 286         // Modified or moved
 287         bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 288         int spaces = pretty? (2 * depth) : 0;
 289         const char *prefix = PCMK__XML_PREFIX_MODIFIED;
 290 
 291         if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 292             prefix = PCMK__XML_PREFIX_MOVED;
 293         }
 294 
 295         // Log opening tag
 296         rc = pcmk__xml_show(out, prefix, data, depth,
 297                             options|pcmk__xml_fmt_open);
 298 
 299         // Log changes to attributes
 300         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 301              attr = attr->next) {
 302             const char *name = (const char *) attr->name;
 303 
 304             nodepriv = attr->_private;
 305 
 306             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 307                 const char *value = crm_element_value(data, name);
 308 
 309                 temp_rc = out->info(out, "%s %*s @%s=%s",
 310                                     PCMK__XML_PREFIX_DELETED, spaces, "", name,
 311                                     value);
 312 
 313             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 314                 const char *value = crm_element_value(data, name);
 315 
 316                 if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 317                     prefix = PCMK__XML_PREFIX_CREATED;
 318 
 319                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
 320                     prefix = PCMK__XML_PREFIX_MODIFIED;
 321 
 322                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 323                     prefix = PCMK__XML_PREFIX_MOVED;
 324 
 325                 } else {
 326                     prefix = PCMK__XML_PREFIX_MODIFIED;
 327                 }
 328 
 329                 temp_rc = out->info(out, "%s %*s @%s=%s",
 330                                     prefix, spaces, "", name, value);
 331             }
 332             rc = pcmk__output_select_rc(rc, temp_rc);
 333         }
 334 
 335         // Log changes to children
 336         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 337              child = pcmk__xml_next(child)) {
 338             temp_rc = show_xml_changes_recursive(out, child, depth + 1,
 339                                                  options);
 340             rc = pcmk__output_select_rc(rc, temp_rc);
 341         }
 342 
 343         // Log closing tag
 344         temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
 345                                  options|pcmk__xml_fmt_close);
 346         return pcmk__output_select_rc(rc, temp_rc);
 347     }
 348 
 349     // This node hasn't changed, but check its children
 350     for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 351          child = pcmk__xml_next(child)) {
 352         temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
 353         rc = pcmk__output_select_rc(rc, temp_rc);
 354     }
 355     return rc;
 356 }
 357 
 358 /*!
 359  * \internal
 360  * \brief Output changes to an XML node and any children
 361  *
 362  * \param[in,out] out  Output object
 363  * \param[in]     xml  XML node to output
 364  *
 365  * \return Standard Pacemaker return code
 366  *
 367  * \note This currently produces output only for text-like output objects.
 368  */
 369 int
 370 pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 371 {
 372     xml_doc_private_t *docpriv = NULL;
 373     int rc = pcmk_rc_no_output;
 374     int temp_rc = pcmk_rc_no_output;
 375 
 376     CRM_ASSERT(out != NULL);
 377     CRM_ASSERT(xml != NULL);
 378     CRM_ASSERT(xml->doc != NULL);
 379 
 380     docpriv = xml->doc->_private;
 381     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 382         return rc;
 383     }
 384 
 385     for (const GList *iter = docpriv->deleted_objs; iter != NULL;
 386          iter = iter->next) {
 387         const pcmk__deleted_xml_t *deleted_obj = iter->data;
 388 
 389         if (deleted_obj->position >= 0) {
 390             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
 391                                 deleted_obj->path, deleted_obj->position);
 392         } else {
 393             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
 394                                 deleted_obj->path);
 395         }
 396         rc = pcmk__output_select_rc(rc, temp_rc);
 397     }
 398 
 399     temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
 400     return pcmk__output_select_rc(rc, temp_rc);
 401 }
 402 
 403 // Deprecated functions kept only for backward API compatibility
 404 // LCOV_EXCL_START
 405 
 406 #include <crm/common/logging_compat.h>
 407 #include <crm/common/xml_compat.h>
 408 
 409 void
 410 log_data_element(int log_level, const char *file, const char *function,
     /* [previous][next][first][last][top][bottom][index][help] */
 411                  int line, const char *prefix, const xmlNode *data, int depth,
 412                  int legacy_options)
 413 {
 414     uint32_t options = 0;
 415     pcmk__output_t *out = NULL;
 416 
 417     // Confine log_level to uint8_t range
 418     log_level = pcmk__clip_log_level(log_level);
 419 
 420     if (data == NULL) {
 421         do_crm_log(log_level, "%s%sNo data to dump as XML",
 422                    pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ");
 423         return;
 424     }
 425 
 426     switch (log_level) {
 427         case LOG_NEVER:
 428             return;
 429         case LOG_STDOUT:
 430             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 431             break;
 432         default:
 433             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 434             pcmk__output_set_log_level(out, log_level);
 435             break;
 436     }
 437 
 438     /* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and
 439      * start using the pcmk__xml_fmt_options in all the internal functions.
 440      *
 441      * xml_log_option_dirty_add and xml_log_option_diff_all are ignored by
 442      * internal code and only used here, so they don't need to be addressed.
 443      */
 444     if (pcmk_is_set(legacy_options, xml_log_option_filtered)) {
 445         options |= pcmk__xml_fmt_filtered;
 446     }
 447     if (pcmk_is_set(legacy_options, xml_log_option_formatted)) {
 448         options |= pcmk__xml_fmt_pretty;
 449     }
 450     if (pcmk_is_set(legacy_options, xml_log_option_full_fledged)) {
 451         options |= pcmk__xml_fmt_full;
 452     }
 453     if (pcmk_is_set(legacy_options, xml_log_option_open)) {
 454         options |= pcmk__xml_fmt_open;
 455     }
 456     if (pcmk_is_set(legacy_options, xml_log_option_children)) {
 457         options |= pcmk__xml_fmt_children;
 458     }
 459     if (pcmk_is_set(legacy_options, xml_log_option_close)) {
 460         options |= pcmk__xml_fmt_close;
 461     }
 462     if (pcmk_is_set(legacy_options, xml_log_option_text)) {
 463         options |= pcmk__xml_fmt_text;
 464     }
 465     if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
 466         options |= pcmk__xml_fmt_diff_plus;
 467     }
 468     if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
 469         options |= pcmk__xml_fmt_diff_minus;
 470     }
 471     if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
 472         options |= pcmk__xml_fmt_diff_short;
 473     }
 474 
 475     // Log element based on options
 476     if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
 477         CRM_CHECK(depth >= 0, depth = 0);
 478         show_xml_changes_recursive(out, data, depth, options);
 479         goto done;
 480     }
 481 
 482     if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
 483         && (!xml_has_children(data)
 484             || (crm_element_value(data, XML_DIFF_MARKER) != NULL))) {
 485 
 486         if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
 487             legacy_options |= xml_log_option_diff_all;
 488             prefix = PCMK__XML_PREFIX_CREATED;
 489 
 490         } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
 491             legacy_options |= xml_log_option_diff_all;
 492             prefix = PCMK__XML_PREFIX_DELETED;
 493         }
 494     }
 495 
 496     if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)
 497         && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
 498 
 499         if (!pcmk_any_flags_set(options,
 500                                 pcmk__xml_fmt_diff_plus
 501                                 |pcmk__xml_fmt_diff_minus)) {
 502             // Nothing will ever be logged
 503             goto done;
 504         }
 505 
 506         // Keep looking for the actual change
 507         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 508              child = pcmk__xml_next(child)) {
 509             log_data_element(log_level, file, function, line, prefix, child,
 510                              depth + 1, options);
 511         }
 512 
 513     } else {
 514         pcmk__xml_show(out, prefix, data, depth,
 515                        options
 516                        |pcmk__xml_fmt_open
 517                        |pcmk__xml_fmt_children
 518                        |pcmk__xml_fmt_close);
 519     }
 520 
 521 done:
 522     out->finish(out, CRM_EX_OK, true, NULL);
 523     pcmk__output_free(out);
 524 }
 525 
 526 void
 527 xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 528 {
 529     pcmk__output_t *out = NULL;
 530     int rc = pcmk_rc_ok;
 531 
 532     switch (log_level) {
 533         case LOG_NEVER:
 534             return;
 535         case LOG_STDOUT:
 536             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
 537             break;
 538         default:
 539             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
 540             pcmk__output_set_log_level(out, log_level);
 541             break;
 542     }
 543     rc = pcmk__xml_show_changes(out, xml);
 544     out->finish(out, pcmk_rc2exitc(rc), true, NULL);
 545     pcmk__output_free(out);
 546 }
 547 
 548 // LCOV_EXCL_STOP
 549 // End deprecated API

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