LCOV - code coverage report
Current view: top level - common - patchset_display.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 160 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 8 0.0 %

          Line data    Source code
       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 <crm/common/xml.h>
      13             : 
      14             : #include "crmcommon_private.h"
      15             : 
      16             : /*!
      17             :  * \internal
      18             :  * \brief Output an XML patchset header
      19             :  *
      20             :  * This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
      21             :  * element and its children).
      22             :  *
      23             :  * All header lines contain three integers separated by dots, of the form
      24             :  * <tt>{0}.{1}.{2}</tt>:
      25             :  * * \p {0}: \c PCMK_XA_ADMIN_EPOCH
      26             :  * * \p {1}: \c PCMK_XA_EPOCH
      27             :  * * \p {2}: \c PCMK_XA_NUM_UPDATES
      28             :  *
      29             :  * Lines containing \p "---" describe removals and end with the patch format
      30             :  * number. Lines containing \p "+++" describe additions and end with the patch
      31             :  * digest.
      32             :  *
      33             :  * \param[in,out] out       Output object
      34             :  * \param[in]     patchset  XML patchset to output
      35             :  *
      36             :  * \return Standard Pacemaker return code
      37             :  *
      38             :  * \note This function produces output only for text-like formats.
      39             :  */
      40             : static int
      41           0 : xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
      42             : {
      43           0 :     int rc = pcmk_rc_no_output;
      44           0 :     int add[] = { 0, 0, 0 };
      45           0 :     int del[] = { 0, 0, 0 };
      46             : 
      47           0 :     xml_patch_versions(patchset, add, del);
      48             : 
      49           0 :     if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
      50           0 :         const char *fmt = crm_element_value(patchset, PCMK_XA_FORMAT);
      51           0 :         const char *digest = crm_element_value(patchset, PCMK__XA_DIGEST);
      52             : 
      53           0 :         out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
      54           0 :         rc = out->info(out, "Diff: +++ %d.%d.%d %s",
      55             :                        add[0], add[1], add[2], digest);
      56             : 
      57           0 :     } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
      58           0 :         rc = out->info(out, "Local-only Change: %d.%d.%d",
      59             :                        add[0], add[1], add[2]);
      60             :     }
      61             : 
      62           0 :     return rc;
      63             : }
      64             : 
      65             : /*!
      66             :  * \internal
      67             :  * \brief Output a user-friendly form of XML additions or removals
      68             :  *
      69             :  * \param[in,out] out      Output object
      70             :  * \param[in]     prefix   String to prepend to every line of output
      71             :  * \param[in]     data     XML node to output
      72             :  * \param[in]     depth    Current indentation level
      73             :  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
      74             :  *
      75             :  * \return Standard Pacemaker return code
      76             :  *
      77             :  * \note This function produces output only for text-like formats.
      78             :  */
      79             : static int
      80           0 : xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
      81             :                                const xmlNode *data, int depth, uint32_t options)
      82             : {
      83           0 :     if ((data->children == NULL)
      84           0 :         || (crm_element_value(data, PCMK__XA_CRM_DIFF_MARKER) != NULL)) {
      85             : 
      86             :         // Found a change; clear the pcmk__xml_fmt_diff_short option if set
      87           0 :         options &= ~pcmk__xml_fmt_diff_short;
      88             : 
      89           0 :         if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
      90           0 :             prefix = PCMK__XML_PREFIX_CREATED;
      91             :         } else {    // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
      92           0 :             prefix = PCMK__XML_PREFIX_DELETED;
      93             :         }
      94             :     }
      95             : 
      96           0 :     if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
      97           0 :         int rc = pcmk_rc_no_output;
      98             : 
      99             :         // Keep looking for the actual change
     100           0 :         for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
     101           0 :              child = pcmk__xml_next(child)) {
     102           0 :             int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child,
     103             :                                                          depth + 1, options);
     104           0 :             rc = pcmk__output_select_rc(rc, temp_rc);
     105             :         }
     106           0 :         return rc;
     107             :     }
     108             : 
     109           0 :     return pcmk__xml_show(out, prefix, data, depth,
     110             :                           options
     111             :                           |pcmk__xml_fmt_open
     112             :                           |pcmk__xml_fmt_children
     113             :                           |pcmk__xml_fmt_close);
     114             : }
     115             : 
     116             : /*!
     117             :  * \internal
     118             :  * \brief Output a user-friendly form of an XML patchset (format 1)
     119             :  *
     120             :  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
     121             :  * children) into a user-friendly combined diff output.
     122             :  *
     123             :  * \param[in,out] out       Output object
     124             :  * \param[in]     patchset  XML patchset to output
     125             :  * \param[in]     options   Group of \p pcmk__xml_fmt_options flags
     126             :  *
     127             :  * \return Standard Pacemaker return code
     128             :  *
     129             :  * \note This function produces output only for text-like formats.
     130             :  */
     131             : static int
     132           0 : xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
     133             :                      uint32_t options)
     134             : {
     135           0 :     const xmlNode *removed = NULL;
     136           0 :     const xmlNode *added = NULL;
     137           0 :     const xmlNode *child = NULL;
     138           0 :     bool is_first = true;
     139           0 :     int rc = xml_show_patchset_header(out, patchset);
     140             : 
     141             :     /* It's not clear whether "- " or "+ " ever does *not* get overridden by
     142             :      * PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
     143             :      * However, v1 patchsets can only exist during rolling upgrades from
     144             :      * Pacemaker 1.1.11, so not worth worrying about.
     145             :      */
     146           0 :     removed = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED, NULL, NULL);
     147           0 :     for (child = pcmk__xml_first_child(removed); child != NULL;
     148           0 :          child = pcmk__xml_next(child)) {
     149           0 :         int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0,
     150             :                                                      options
     151             :                                                      |pcmk__xml_fmt_diff_minus);
     152           0 :         rc = pcmk__output_select_rc(rc, temp_rc);
     153             : 
     154           0 :         if (is_first) {
     155           0 :             is_first = false;
     156             :         } else {
     157           0 :             rc = pcmk__output_select_rc(rc, out->info(out, " --- "));
     158             :         }
     159             :     }
     160             : 
     161           0 :     is_first = true;
     162           0 :     added = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL, NULL);
     163           0 :     for (child = pcmk__xml_first_child(added); child != NULL;
     164           0 :          child = pcmk__xml_next(child)) {
     165           0 :         int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0,
     166             :                                                      options
     167             :                                                      |pcmk__xml_fmt_diff_plus);
     168           0 :         rc = pcmk__output_select_rc(rc, temp_rc);
     169             : 
     170           0 :         if (is_first) {
     171           0 :             is_first = false;
     172             :         } else {
     173           0 :             rc = pcmk__output_select_rc(rc, out->info(out, " +++ "));
     174             :         }
     175             :     }
     176             : 
     177           0 :     return rc;
     178             : }
     179             : 
     180             : /*!
     181             :  * \internal
     182             :  * \brief Output a user-friendly form of an XML patchset (format 2)
     183             :  *
     184             :  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
     185             :  * children) into a user-friendly combined diff output.
     186             :  *
     187             :  * \param[in,out] out       Output object
     188             :  * \param[in]     patchset  XML patchset to output
     189             :  *
     190             :  * \return Standard Pacemaker return code
     191             :  *
     192             :  * \note This function produces output only for text-like formats.
     193             :  */
     194             : static int
     195           0 : xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
     196             : {
     197           0 :     int rc = xml_show_patchset_header(out, patchset);
     198           0 :     int temp_rc = pcmk_rc_no_output;
     199             : 
     200           0 :     for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL,
     201             :                                                       NULL);
     202           0 :          change != NULL; change = pcmk__xe_next(change)) {
     203             : 
     204           0 :         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
     205           0 :         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
     206             : 
     207           0 :         if (op == NULL) {
     208           0 :             continue;
     209             :         }
     210             : 
     211           0 :         if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
     212           0 :             char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
     213             :                                              xpath);
     214             : 
     215           0 :             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
     216             :                                      pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
     217           0 :             rc = pcmk__output_select_rc(rc, temp_rc);
     218             : 
     219             :             // Overwrite all except the first two characters with spaces
     220           0 :             for (char *ch = prefix + 2; *ch != '\0'; ch++) {
     221           0 :                 *ch = ' ';
     222             :             }
     223             : 
     224           0 :             temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
     225             :                                      pcmk__xml_fmt_pretty
     226             :                                      |pcmk__xml_fmt_children
     227             :                                      |pcmk__xml_fmt_close);
     228           0 :             rc = pcmk__output_select_rc(rc, temp_rc);
     229           0 :             free(prefix);
     230             : 
     231           0 :         } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
     232           0 :             const char *position = crm_element_value(change, PCMK_XE_POSITION);
     233             : 
     234           0 :             temp_rc = out->info(out,
     235             :                                 PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
     236             :                                 xpath, position);
     237           0 :             rc = pcmk__output_select_rc(rc, temp_rc);
     238             : 
     239           0 :         } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
     240           0 :             xmlNode *clist = pcmk__xe_first_child(change, PCMK_XE_CHANGE_LIST,
     241             :                                                   NULL, NULL);
     242           0 :             GString *buffer_set = NULL;
     243           0 :             GString *buffer_unset = NULL;
     244             : 
     245           0 :             for (const xmlNode *child = pcmk__xe_first_child(clist, NULL, NULL,
     246             :                                                              NULL);
     247           0 :                  child != NULL; child = pcmk__xe_next(child)) {
     248             : 
     249           0 :                 const char *name = crm_element_value(child, PCMK_XA_NAME);
     250             : 
     251           0 :                 op = crm_element_value(child, PCMK_XA_OPERATION);
     252           0 :                 if (op == NULL) {
     253           0 :                     continue;
     254             :                 }
     255             : 
     256           0 :                 if (strcmp(op, "set") == 0) {
     257           0 :                     const char *value = crm_element_value(child, PCMK_XA_VALUE);
     258             : 
     259           0 :                     pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
     260           0 :                     pcmk__g_strcat(buffer_set, name, "=", value, NULL);
     261             : 
     262           0 :                 } else if (strcmp(op, "unset") == 0) {
     263           0 :                     pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
     264           0 :                     g_string_append(buffer_unset, name);
     265             :                 }
     266             :             }
     267             : 
     268           0 :             if (buffer_set != NULL) {
     269           0 :                 temp_rc = out->info(out, "+  %s:  %s", xpath, buffer_set->str);
     270           0 :                 rc = pcmk__output_select_rc(rc, temp_rc);
     271           0 :                 g_string_free(buffer_set, TRUE);
     272             :             }
     273             : 
     274           0 :             if (buffer_unset != NULL) {
     275           0 :                 temp_rc = out->info(out, "-- %s:  %s",
     276           0 :                                     xpath, buffer_unset->str);
     277           0 :                 rc = pcmk__output_select_rc(rc, temp_rc);
     278           0 :                 g_string_free(buffer_unset, TRUE);
     279             :             }
     280             : 
     281           0 :         } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
     282           0 :             int position = -1;
     283             : 
     284           0 :             crm_element_value_int(change, PCMK_XE_POSITION, &position);
     285           0 :             if (position >= 0) {
     286           0 :                 temp_rc = out->info(out, "-- %s (%d)", xpath, position);
     287             :             } else {
     288           0 :                 temp_rc = out->info(out, "-- %s", xpath);
     289             :             }
     290           0 :             rc = pcmk__output_select_rc(rc, temp_rc);
     291             :         }
     292             :     }
     293             : 
     294           0 :     return rc;
     295             : }
     296             : 
     297             : /*!
     298             :  * \internal
     299             :  * \brief Output a user-friendly form of an XML patchset
     300             :  *
     301             :  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
     302             :  * children) into a user-friendly combined diff output.
     303             :  *
     304             :  * \param[in,out] out   Output object
     305             :  * \param[in]     args  Message-specific arguments
     306             :  *
     307             :  * \return Standard Pacemaker return code
     308             :  *
     309             :  * \note \p args should contain the following:
     310             :  *       -# XML patchset
     311             :  */
     312             : PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     313             : static int
     314           0 : xml_patchset_default(pcmk__output_t *out, va_list args)
     315             : {
     316           0 :     const xmlNode *patchset = va_arg(args, const xmlNode *);
     317             : 
     318           0 :     int format = 1;
     319             : 
     320           0 :     if (patchset == NULL) {
     321           0 :         crm_trace("Empty patch");
     322           0 :         return pcmk_rc_no_output;
     323             :     }
     324             : 
     325           0 :     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
     326           0 :     switch (format) {
     327           0 :         case 1:
     328           0 :             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
     329           0 :         case 2:
     330           0 :             return xml_show_patchset_v2(out, patchset);
     331           0 :         default:
     332           0 :             crm_err("Unknown patch format: %d", format);
     333           0 :             return pcmk_rc_bad_xml_patch;
     334             :     }
     335             : }
     336             : 
     337             : /*!
     338             :  * \internal
     339             :  * \brief Output a user-friendly form of an XML patchset
     340             :  *
     341             :  * This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
     342             :  * children) into a user-friendly combined diff output.
     343             :  *
     344             :  * \param[in,out] out   Output object
     345             :  * \param[in]     args  Message-specific arguments
     346             :  *
     347             :  * \return Standard Pacemaker return code
     348             :  *
     349             :  * \note \p args should contain the following:
     350             :  *       -# XML patchset
     351             :  */
     352             : PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     353             : static int
     354           0 : xml_patchset_log(pcmk__output_t *out, va_list args)
     355             : {
     356             :     static struct qb_log_callsite *patchset_cs = NULL;
     357             : 
     358           0 :     const xmlNode *patchset = va_arg(args, const xmlNode *);
     359             : 
     360           0 :     uint8_t log_level = pcmk__output_get_log_level(out);
     361           0 :     int format = 1;
     362             : 
     363           0 :     if (log_level == LOG_NEVER) {
     364           0 :         return pcmk_rc_no_output;
     365             :     }
     366             : 
     367           0 :     if (patchset == NULL) {
     368           0 :         crm_trace("Empty patch");
     369           0 :         return pcmk_rc_no_output;
     370             :     }
     371             : 
     372           0 :     if (patchset_cs == NULL) {
     373           0 :         patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
     374             :                                           log_level, __LINE__,
     375             :                                           crm_trace_nonlog);
     376             :     }
     377             : 
     378           0 :     if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
     379             :         // Nothing would be logged, so skip all the work
     380           0 :         return pcmk_rc_no_output;
     381             :     }
     382             : 
     383           0 :     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
     384           0 :     switch (format) {
     385           0 :         case 1:
     386           0 :             if (log_level < LOG_DEBUG) {
     387           0 :                 return xml_show_patchset_v1(out, patchset,
     388             :                                             pcmk__xml_fmt_pretty
     389             :                                             |pcmk__xml_fmt_diff_short);
     390             :             }
     391           0 :             return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
     392           0 :         case 2:
     393           0 :             return xml_show_patchset_v2(out, patchset);
     394           0 :         default:
     395           0 :             crm_err("Unknown patch format: %d", format);
     396           0 :             return pcmk_rc_bad_xml_patch;
     397             :     }
     398             : }
     399             : 
     400             : /*!
     401             :  * \internal
     402             :  * \brief Output an XML patchset
     403             :  *
     404             :  * This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
     405             :  * children) without modification, as a CDATA block.
     406             :  *
     407             :  * \param[in,out] out   Output object
     408             :  * \param[in]     args  Message-specific arguments
     409             :  *
     410             :  * \return Standard Pacemaker return code
     411             :  *
     412             :  * \note \p args should contain the following:
     413             :  *       -# XML patchset
     414             :  */
     415             : PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
     416             : static int
     417           0 : xml_patchset_xml(pcmk__output_t *out, va_list args)
     418             : {
     419           0 :     const xmlNode *patchset = va_arg(args, const xmlNode *);
     420             : 
     421           0 :     if (patchset != NULL) {
     422           0 :         GString *buf = g_string_sized_new(1024);
     423             : 
     424           0 :         pcmk__xml_string(patchset, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
     425             :                          0);
     426             : 
     427           0 :         out->output_xml(out, PCMK_XE_XML_PATCHSET, buf->str);
     428           0 :         g_string_free(buf, TRUE);
     429           0 :         return pcmk_rc_ok;
     430             :     }
     431           0 :     crm_trace("Empty patch");
     432           0 :     return pcmk_rc_no_output;
     433             : }
     434             : 
     435             : static pcmk__message_entry_t fmt_functions[] = {
     436             :     { "xml-patchset", "default", xml_patchset_default },
     437             :     { "xml-patchset", "log", xml_patchset_log },
     438             :     { "xml-patchset", "xml", xml_patchset_xml },
     439             : 
     440             :     { NULL, NULL, NULL }
     441             : };
     442             : 
     443             : /*!
     444             :  * \internal
     445             :  * \brief Register the formatting functions for XML patchsets
     446             :  *
     447             :  * \param[in,out] out  Output object
     448             :  */
     449             : void
     450           0 : pcmk__register_patchset_messages(pcmk__output_t *out) {
     451           0 :     pcmk__register_messages(out, fmt_functions);
     452           0 : }
     453             : 
     454             : // Deprecated functions kept only for backward API compatibility
     455             : // LCOV_EXCL_START
     456             : 
     457             : #include <crm/common/xml_compat.h>
     458             : 
     459             : void
     460             : xml_log_patchset(uint8_t log_level, const char *function,
     461             :                  const xmlNode *patchset)
     462             : {
     463             :     /* This function has some duplication relative to the message functions.
     464             :      * This way, we can maintain the const xmlNode * in the signature. The
     465             :      * message functions must be non-const. They have to support XML output
     466             :      * objects, which must make a copy of a the patchset, requiring a non-const
     467             :      * function call.
     468             :      *
     469             :      * In contrast, this legacy function doesn't need to support XML output.
     470             :      */
     471             :     static struct qb_log_callsite *patchset_cs = NULL;
     472             : 
     473             :     pcmk__output_t *out = NULL;
     474             :     int format = 1;
     475             :     int rc = pcmk_rc_no_output;
     476             : 
     477             :     switch (log_level) {
     478             :         case LOG_NEVER:
     479             :             return;
     480             :         case LOG_STDOUT:
     481             :             CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
     482             :             break;
     483             :         default:
     484             :             if (patchset_cs == NULL) {
     485             :                 patchset_cs = qb_log_callsite_get(__func__, __FILE__,
     486             :                                                   "xml-patchset", log_level,
     487             :                                                   __LINE__, crm_trace_nonlog);
     488             :             }
     489             :             if (!crm_is_callsite_active(patchset_cs, log_level,
     490             :                                         crm_trace_nonlog)) {
     491             :                 return;
     492             :             }
     493             :             CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
     494             :             pcmk__output_set_log_level(out, log_level);
     495             :             break;
     496             :     }
     497             : 
     498             :     if (patchset == NULL) {
     499             :         // Should come after the LOG_NEVER check
     500             :         crm_trace("Empty patch");
     501             :         goto done;
     502             :     }
     503             : 
     504             :     crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
     505             :     switch (format) {
     506             :         case 1:
     507             :             if (log_level < LOG_DEBUG) {
     508             :                 rc = xml_show_patchset_v1(out, patchset,
     509             :                                           pcmk__xml_fmt_pretty
     510             :                                           |pcmk__xml_fmt_diff_short);
     511             :             } else {    // Note: LOG_STDOUT > LOG_DEBUG
     512             :                 rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
     513             :             }
     514             :             break;
     515             :         case 2:
     516             :             rc = xml_show_patchset_v2(out, patchset);
     517             :             break;
     518             :         default:
     519             :             crm_err("Unknown patch format: %d", format);
     520             :             rc = pcmk_rc_bad_xml_patch;
     521             :             break;
     522             :     }
     523             : 
     524             : done:
     525             :     out->finish(out, pcmk_rc2exitc(rc), true, NULL);
     526             :     pcmk__output_free(out);
     527             : }
     528             : 
     529             : // LCOV_EXCL_STOP
     530             : // End deprecated API

Generated by: LCOV version 1.14