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

   1 /*
   2  * Copyright 2004-2024 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/common/xml.h>
  16 #include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
  17 #include "crmcommon_private.h"
  18 
  19 static int show_xml_node(pcmk__output_t *out, GString *buffer,
  20                          const char *prefix, const xmlNode *data, int depth,
  21                          uint32_t options);
  22 
  23 // Log an XML library error
  24 void
  25 pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
  26 {
  27     va_list ap;
  28 
  29     va_start(ap, fmt);
  30     pcmk__if_tracing(
  31         {
  32             PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
  33                                crm_abort(__FILE__, __PRETTY_FUNCTION__,
  34                                          __LINE__, "xml library error", TRUE,
  35                                          TRUE),
  36                                "XML Error: ", fmt, ap);
  37         },
  38         {
  39             PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
  40         }
  41     );
  42     va_end(ap);
  43 }
  44 
  45 /*!
  46  * \internal
  47  * \brief Output an XML comment with depth-based indentation
  48  *
  49  * \param[in,out] out      Output object
  50  * \param[in]     data     XML node to output
  51  * \param[in]     depth    Current indentation level
  52  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  53  *
  54  * \return Standard Pacemaker return code
  55  *
  56  * \note This currently produces output only for text-like output objects.
  57  */
  58 static int
  59 show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
  60                  uint32_t options)
  61 {
  62     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
  63         int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  64 
  65         return out->info(out, "%*s<!--%s-->",
  66                          width, "", (const char *) data->content);
  67     }
  68     return pcmk_rc_no_output;
  69 }
  70 
  71 /*!
  72  * \internal
  73  * \brief Output an XML element in a formatted way
  74  *
  75  * \param[in,out] out      Output object
  76  * \param[in,out] buffer   Where to build output strings
  77  * \param[in]     prefix   String to prepend to every line of output
  78  * \param[in]     data     XML node to output
  79  * \param[in]     depth    Current indentation level
  80  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  81  *
  82  * \return Standard Pacemaker return code
  83  *
  84  * \note This is a recursive helper function for \p show_xml_node().
  85  * \note This currently produces output only for text-like output objects.
  86  * \note \p buffer may be overwritten many times. The caller is responsible for
  87  *       freeing it using \p g_string_free() but should not rely on its
  88  *       contents.
  89  */
  90 static int
  91 show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
  92                  const xmlNode *data, int depth, uint32_t options)
  93 {
  94     int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
  95     int rc = pcmk_rc_no_output;
  96 
  97     if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
  98         const char *hidden = crm_element_value(data, PCMK__XA_HIDDEN);
  99 
 100         g_string_truncate(buffer, 0);
 101 
 102         for (int lpc = 0; lpc < spaces; lpc++) {
 103             g_string_append_c(buffer, ' ');
 104         }
 105         pcmk__g_strcat(buffer, "<", data->name, NULL);
 106 
 107         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 108              attr = attr->next) {
 109             xml_node_private_t *nodepriv = attr->_private;
 110             const char *p_name = (const char *) attr->name;
 111             const char *p_value = pcmk__xml_attr_value(attr);
 112             gchar *p_copy = NULL;
 113 
 114             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 115                 continue;
 116             }
 117 
 118             if ((hidden != NULL) && (p_name[0] != '\0')
 119                 && (strstr(hidden, p_name) != NULL)) {
 120 
 121                 p_value = "*****";
 122 
 123             } else {
 124                 p_copy = pcmk__xml_escape(p_value, true);
 125                 p_value = p_copy;
 126             }
 127 
 128             pcmk__g_strcat(buffer, " ", p_name, "=\"",
 129                            pcmk__s(p_value, "<null>"), "\"", NULL);
 130             g_free(p_copy);
 131         }
 132 
 133         if ((data->children != NULL)
 134             && pcmk_is_set(options, pcmk__xml_fmt_children)) {
 135             g_string_append_c(buffer, '>');
 136 
 137         } else {
 138             g_string_append(buffer, "/>");
 139         }
 140 
 141         rc = out->info(out, "%s%s%s",
 142                        pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
 143                        buffer->str);
 144     }
 145 
 146     if (data->children == NULL) {
 147         return rc;
 148     }
 149 
 150     if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
 151         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 152              child = pcmk__xml_next(child)) {
 153 
 154             int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
 155                                         options
 156                                         |pcmk__xml_fmt_open
 157                                         |pcmk__xml_fmt_close);
 158             rc = pcmk__output_select_rc(rc, temp_rc);
 159         }
 160     }
 161 
 162     if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
 163         int temp_rc = out->info(out, "%s%s%*s</%s>",
 164                                 pcmk__s(prefix, ""),
 165                                 pcmk__str_empty(prefix)? "" : " ",
 166                                 spaces, "", data->name);
 167         rc = pcmk__output_select_rc(rc, temp_rc);
 168     }
 169 
 170     return rc;
 171 }
 172 
 173 /*!
 174  * \internal
 175  * \brief Output an XML element or comment in a formatted way
 176  *
 177  * \param[in,out] out      Output object
 178  * \param[in,out] buffer   Where to build output strings
 179  * \param[in]     prefix   String to prepend to every line of output
 180  * \param[in]     data     XML node to log
 181  * \param[in]     depth    Current indentation level
 182  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 183  *
 184  * \return Standard Pacemaker return code
 185  *
 186  * \note This is a recursive helper function for \p pcmk__xml_show().
 187  * \note This currently produces output only for text-like output objects.
 188  * \note \p buffer may be overwritten many times. The caller is responsible for
 189  *       freeing it using \p g_string_free() but should not rely on its
 190  *       contents.
 191  */
 192 static int
 193 show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
 194               const xmlNode *data, int depth, uint32_t options)
 195 {
 196     switch (data->type) {
 197         case XML_COMMENT_NODE:
 198             return show_xml_comment(out, data, depth, options);
 199         case XML_ELEMENT_NODE:
 200             return show_xml_element(out, buffer, prefix, data, depth, options);
 201         default:
 202             return pcmk_rc_no_output;
 203     }
 204 }
 205 
 206 /*!
 207  * \internal
 208  * \brief Output an XML element or comment in a formatted way
 209  *
 210  * \param[in,out] out        Output object
 211  * \param[in]     prefix     String to prepend to every line of output
 212  * \param[in]     data       XML node to output
 213  * \param[in]     depth      Current nesting level
 214  * \param[in]     options    Group of \p pcmk__xml_fmt_options flags
 215  *
 216  * \return Standard Pacemaker return code
 217  *
 218  * \note This currently produces output only for text-like output objects.
 219  */
 220 int
 221 pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
     /* [previous][next][first][last][top][bottom][index][help] */
 222                int depth, uint32_t options)
 223 {
 224     int rc = pcmk_rc_no_output;
 225     GString *buffer = NULL;
 226 
 227     pcmk__assert(out != NULL);
 228     CRM_CHECK(depth >= 0, depth = 0);
 229 
 230     if (data == NULL) {
 231         return rc;
 232     }
 233 
 234     /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
 235      * recursive calls
 236      */
 237     buffer = g_string_sized_new(1024);
 238     rc = show_xml_node(out, buffer, prefix, data, depth, options);
 239     g_string_free(buffer, TRUE);
 240 
 241     return rc;
 242 }
 243 
 244 /*!
 245  * \internal
 246  * \brief Output XML portions that have been marked as changed
 247  *
 248  * \param[in,out] out      Output object
 249  * \param[in]     data     XML node to output
 250  * \param[in]     depth    Current indentation level
 251  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 252  *
 253  * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing
 254  *       changes to \p data and its children.
 255  * \note This currently produces output only for text-like output objects.
 256  */
 257 static int
 258 show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
     /* [previous][next][first][last][top][bottom][index][help] */
 259                            uint32_t options)
 260 {
 261     /* @COMPAT: When log_data_element() is removed, we can remove the options
 262      * argument here and instead hard-code pcmk__xml_log_pretty.
 263      */
 264     xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
 265     int rc = pcmk_rc_no_output;
 266     int temp_rc = pcmk_rc_no_output;
 267 
 268     if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
 269         // Newly created
 270         return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
 271                               options
 272                               |pcmk__xml_fmt_open
 273                               |pcmk__xml_fmt_children
 274                               |pcmk__xml_fmt_close);
 275     }
 276 
 277     if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 278         // Modified or moved
 279         bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 280         int spaces = pretty? (2 * depth) : 0;
 281         const char *prefix = PCMK__XML_PREFIX_MODIFIED;
 282 
 283         if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 284             prefix = PCMK__XML_PREFIX_MOVED;
 285         }
 286 
 287         // Log opening tag
 288         rc = pcmk__xml_show(out, prefix, data, depth,
 289                             options|pcmk__xml_fmt_open);
 290 
 291         // Log changes to attributes
 292         for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 293              attr = attr->next) {
 294             const char *name = (const char *) attr->name;
 295 
 296             nodepriv = attr->_private;
 297 
 298             if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
 299                 const char *value = pcmk__xml_attr_value(attr);
 300 
 301                 temp_rc = out->info(out, "%s %*s @%s=%s",
 302                                     PCMK__XML_PREFIX_DELETED, spaces, "", name,
 303                                     value);
 304 
 305             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
 306                 const char *value = pcmk__xml_attr_value(attr);
 307 
 308                 if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 309                     prefix = PCMK__XML_PREFIX_CREATED;
 310 
 311                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
 312                     prefix = PCMK__XML_PREFIX_MODIFIED;
 313 
 314                 } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
 315                     prefix = PCMK__XML_PREFIX_MOVED;
 316 
 317                 } else {
 318                     prefix = PCMK__XML_PREFIX_MODIFIED;
 319                 }
 320 
 321                 temp_rc = out->info(out, "%s %*s @%s=%s",
 322                                     prefix, spaces, "", name, value);
 323             }
 324             rc = pcmk__output_select_rc(rc, temp_rc);
 325         }
 326 
 327         // Log changes to children
 328         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 329              child = pcmk__xml_next(child)) {
 330             temp_rc = show_xml_changes_recursive(out, child, depth + 1,
 331                                                  options);
 332             rc = pcmk__output_select_rc(rc, temp_rc);
 333         }
 334 
 335         // Log closing tag
 336         temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
 337                                  options|pcmk__xml_fmt_close);
 338         return pcmk__output_select_rc(rc, temp_rc);
 339     }
 340 
 341     // This node hasn't changed, but check its children
 342     for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
 343          child = pcmk__xml_next(child)) {
 344         temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
 345         rc = pcmk__output_select_rc(rc, temp_rc);
 346     }
 347     return rc;
 348 }
 349 
 350 /*!
 351  * \internal
 352  * \brief Output changes to an XML node and any children
 353  *
 354  * \param[in,out] out  Output object
 355  * \param[in]     xml  XML node to output
 356  *
 357  * \return Standard Pacemaker return code
 358  *
 359  * \note This currently produces output only for text-like output objects.
 360  */
 361 int
 362 pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 363 {
 364     xml_doc_private_t *docpriv = NULL;
 365     int rc = pcmk_rc_no_output;
 366     int temp_rc = pcmk_rc_no_output;
 367 
 368     pcmk__assert((out != NULL) && (xml != NULL) && (xml->doc != NULL));
 369 
 370     docpriv = xml->doc->_private;
 371     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
 372         return rc;
 373     }
 374 
 375     for (const GList *iter = docpriv->deleted_objs; iter != NULL;
 376          iter = iter->next) {
 377         const pcmk__deleted_xml_t *deleted_obj = iter->data;
 378 
 379         if (deleted_obj->position >= 0) {
 380             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
 381                                 deleted_obj->path, deleted_obj->position);
 382         } else {
 383             temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
 384                                 deleted_obj->path);
 385         }
 386         rc = pcmk__output_select_rc(rc, temp_rc);
 387     }
 388 
 389     temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
 390     return pcmk__output_select_rc(rc, temp_rc);
 391 }

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