LCOV - code coverage report
Current view: top level - common - xml_io.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 260 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 16 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 <stdio.h>
      13             : #include <stdlib.h>
      14             : #include <string.h>
      15             : #include <sys/types.h>
      16             : 
      17             : #include <bzlib.h>
      18             : #include <libxml/parser.h>
      19             : #include <libxml/tree.h>
      20             : #include <libxml/xmlIO.h>               // xmlOutputBuffer*
      21             : 
      22             : #include <crm/crm.h>
      23             : #include <crm/common/xml.h>
      24             : #include <crm/common/xml_io.h>
      25             : #include "crmcommon_private.h"
      26             : 
      27             : /* @COMPAT XML_PARSE_RECOVER allows some XML errors to be silently worked around
      28             :  * by libxml2, which is potentially ambiguous and dangerous. We should drop it
      29             :  * when we can break backward compatibility with configurations that might be
      30             :  * relying on it (i.e. pacemaker 3.0.0).
      31             :  */
      32             : #define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER    (XML_PARSE_NOBLANKS)
      33             : #define PCMK__XML_PARSE_OPTS_WITH_RECOVER       (XML_PARSE_NOBLANKS \
      34             :                                                  |XML_PARSE_RECOVER)
      35             : 
      36             : /*!
      37             :  * \internal
      38             :  * \brief Read from \c stdin until EOF or error
      39             :  *
      40             :  * \return Newly allocated string containing the bytes read from \c stdin, or
      41             :  *         \c NULL on error
      42             :  *
      43             :  * \note The caller is responsible for freeing the return value using \c free().
      44             :  */
      45             : static char *
      46           0 : read_stdin(void)
      47             : {
      48           0 :     char *buf = NULL;
      49           0 :     size_t length = 0;
      50             : 
      51             :     do {
      52           0 :         buf = pcmk__realloc(buf, length + PCMK__BUFFER_SIZE + 1);
      53           0 :         length += fread(buf + length, 1, PCMK__BUFFER_SIZE, stdin);
      54           0 :     } while ((feof(stdin) == 0) && (ferror(stdin) == 0));
      55             : 
      56           0 :     if (ferror(stdin) != 0) {
      57           0 :         crm_err("Error reading input from stdin");
      58           0 :         free(buf);
      59           0 :         buf = NULL;
      60             :     } else {
      61           0 :         buf[length] = '\0';
      62             :     }
      63           0 :     clearerr(stdin);
      64           0 :     return buf;
      65             : }
      66             : 
      67             : /*!
      68             :  * \internal
      69             :  * \brief Decompress a <tt>bzip2</tt>-compressed file into a string buffer
      70             :  *
      71             :  * \param[in] filename  Name of file to decompress
      72             :  *
      73             :  * \return Newly allocated string with the decompressed contents of \p filename,
      74             :  *         or \c NULL on error.
      75             :  *
      76             :  * \note The caller is responsible for freeing the return value using \c free().
      77             :  */
      78             : static char *
      79           0 : decompress_file(const char *filename)
      80             : {
      81           0 :     char *buffer = NULL;
      82           0 :     int rc = pcmk_rc_ok;
      83           0 :     size_t length = 0;
      84           0 :     BZFILE *bz_file = NULL;
      85           0 :     FILE *input = fopen(filename, "r");
      86             : 
      87           0 :     if (input == NULL) {
      88           0 :         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
      89           0 :         return NULL;
      90             :     }
      91             : 
      92           0 :     bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
      93           0 :     rc = pcmk__bzlib2rc(rc);
      94           0 :     if (rc != pcmk_rc_ok) {
      95           0 :         crm_err("Could not prepare to read compressed %s: %s "
      96             :                 CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
      97           0 :         goto done;
      98             :     }
      99             : 
     100             :     // cppcheck seems not to understand the abort-logic in pcmk__realloc
     101             :     // cppcheck-suppress memleak
     102             :     do {
     103           0 :         int read_len = 0;
     104             : 
     105           0 :         buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
     106           0 :         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
     107             : 
     108           0 :         if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
     109           0 :             crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
     110           0 :             length += read_len;
     111             :         }
     112           0 :     } while (rc == BZ_OK);
     113             : 
     114           0 :     rc = pcmk__bzlib2rc(rc);
     115           0 :     if (rc != pcmk_rc_ok) {
     116           0 :         rc = pcmk__bzlib2rc(rc);
     117           0 :         crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d",
     118             :                 filename, pcmk_rc_str(rc), rc);
     119           0 :         free(buffer);
     120           0 :         buffer = NULL;
     121             :     } else {
     122           0 :         buffer[length] = '\0';
     123             :     }
     124             : 
     125           0 : done:
     126           0 :     BZ2_bzReadClose(&rc, bz_file);
     127           0 :     fclose(input);
     128           0 :     return buffer;
     129             : }
     130             : 
     131             : // @COMPAT Remove macro at 3.0.0 when we drop XML_PARSE_RECOVER
     132             : /*!
     133             :  * \internal
     134             :  * \brief Try to parse XML first without and then with recovery enabled
     135             :  *
     136             :  * \param[out] result  Where to store the resulting XML doc (<tt>xmlDoc **</tt>)
     137             :  * \param[in]  fn      XML parser function
     138             :  * \param[in]  ...     All arguments for \p fn except the final one (an
     139             :  *                     \c xmlParserOption group)
     140             :  */
     141             : #define parse_xml_recover(result, fn, ...) do {                             \
     142             :         *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);    \
     143             :         if (*result == NULL) {                                              \
     144             :             *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITH_RECOVER);   \
     145             :                                                                             \
     146             :             if (*result != NULL) {                                          \
     147             :                 crm_warn("Successfully recovered from XML errors "          \
     148             :                          "(note: a future release will treat this as a "    \
     149             :                          "fatal failure)");                                 \
     150             :             }                                                               \
     151             :         }                                                                   \
     152             :     } while (0);
     153             : 
     154             : /*!
     155             :  * \internal
     156             :  * \brief Parse XML from a file
     157             :  *
     158             :  * \param[in] filename  Name of file containing XML (\c NULL or \c "-" for
     159             :  *                      \c stdin); if \p filename ends in \c ".bz2", the file
     160             :  *                      will be decompressed using \c bzip2
     161             :  *
     162             :  * \return XML tree parsed from the given file; may be \c NULL or only partial
     163             :  *         on error
     164             :  */
     165             : xmlNode *
     166           0 : pcmk__xml_read(const char *filename)
     167             : {
     168           0 :     bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
     169           0 :     xmlNode *xml = NULL;
     170           0 :     xmlDoc *output = NULL;
     171           0 :     xmlParserCtxt *ctxt = NULL;
     172           0 :     const xmlError *last_error = NULL;
     173             : 
     174             :     // Create a parser context
     175           0 :     ctxt = xmlNewParserCtxt();
     176           0 :     CRM_CHECK(ctxt != NULL, return NULL);
     177             : 
     178           0 :     xmlCtxtResetLastError(ctxt);
     179           0 :     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
     180             : 
     181           0 :     if (use_stdin) {
     182             :         /* @COMPAT After dropping XML_PARSE_RECOVER, we can avoid capturing
     183             :          * stdin into a buffer and instead call
     184             :          * xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL, XML_PARSE_NOBLANKS);
     185             :          *
     186             :          * For now we have to save the input so that we can use it twice.
     187             :          */
     188           0 :         char *input = read_stdin();
     189             : 
     190           0 :         if (input != NULL) {
     191           0 :             parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input,
     192             :                               NULL, NULL);
     193           0 :             free(input);
     194             :         }
     195             : 
     196           0 :     } else if (pcmk__ends_with_ext(filename, ".bz2")) {
     197           0 :         char *input = decompress_file(filename);
     198             : 
     199           0 :         if (input != NULL) {
     200           0 :             parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input,
     201             :                               NULL, NULL);
     202           0 :             free(input);
     203             :         }
     204             : 
     205             :     } else {
     206           0 :         parse_xml_recover(&output, xmlCtxtReadFile, ctxt, filename, NULL);
     207             :     }
     208             : 
     209           0 :     if (output != NULL) {
     210           0 :         xml = xmlDocGetRootElement(output);
     211           0 :         if (xml != NULL) {
     212             :             /* @TODO Should we really be stripping out text? This seems like an
     213             :              * overly broad way to get rid of whitespace, if that's the goal.
     214             :              * Text nodes may be invalid in most or all Pacemaker inputs, but
     215             :              * stripping them in a generic "parse XML from file" function may
     216             :              * not be the best way to ignore them.
     217             :              */
     218           0 :             pcmk__strip_xml_text(xml);
     219             :         }
     220             :     }
     221             : 
     222             :     // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL on error
     223           0 :     last_error = xmlCtxtGetLastError(ctxt);
     224           0 :     if (last_error != NULL) {
     225           0 :         if (xml != NULL) {
     226           0 :             crm_log_xml_info(xml, "Partial");
     227             :         }
     228             :     }
     229             : 
     230           0 :     xmlFreeParserCtxt(ctxt);
     231           0 :     return xml;
     232             : }
     233             : 
     234             : /*!
     235             :  * \internal
     236             :  * \brief Parse XML from a string
     237             :  *
     238             :  * \param[in] input  String to parse
     239             :  *
     240             :  * \return XML tree parsed from the given string; may be \c NULL or only partial
     241             :  *         on error
     242             :  */
     243             : xmlNode *
     244           0 : pcmk__xml_parse(const char *input)
     245             : {
     246           0 :     xmlNode *xml = NULL;
     247           0 :     xmlDoc *output = NULL;
     248           0 :     xmlParserCtxt *ctxt = NULL;
     249           0 :     const xmlError *last_error = NULL;
     250             : 
     251           0 :     if (input == NULL) {
     252           0 :         return NULL;
     253             :     }
     254             : 
     255           0 :     ctxt = xmlNewParserCtxt();
     256           0 :     if (ctxt == NULL) {
     257           0 :         return NULL;
     258             :     }
     259             : 
     260           0 :     xmlCtxtResetLastError(ctxt);
     261           0 :     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
     262             : 
     263           0 :     parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input, NULL,
     264             :                       NULL);
     265             : 
     266           0 :     if (output != NULL) {
     267           0 :         xml = xmlDocGetRootElement(output);
     268             :     }
     269             : 
     270             :     // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL; update doxygen
     271           0 :     last_error = xmlCtxtGetLastError(ctxt);
     272           0 :     if (last_error != NULL) {
     273           0 :         if (xml != NULL) {
     274           0 :             crm_log_xml_info(xml, "Partial");
     275             :         }
     276             :     }
     277             : 
     278           0 :     xmlFreeParserCtxt(ctxt);
     279           0 :     return xml;
     280             : }
     281             : 
     282             : /*!
     283             :  * \internal
     284             :  * \brief Append a string representation of an XML element to a buffer
     285             :  *
     286             :  * \param[in]     data     XML whose representation to append
     287             :  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
     288             :  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
     289             :  * \param[in]     depth    Current indentation level
     290             :  */
     291             : static void
     292           0 : dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
     293             :                  int depth)
     294             : {
     295           0 :     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     296           0 :     bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
     297           0 :     int spaces = pretty? (2 * depth) : 0;
     298             : 
     299           0 :     for (int lpc = 0; lpc < spaces; lpc++) {
     300             :         g_string_append_c(buffer, ' ');
     301             :     }
     302             : 
     303           0 :     pcmk__g_strcat(buffer, "<", data->name, NULL);
     304             : 
     305           0 :     for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
     306           0 :          attr = attr->next) {
     307             : 
     308           0 :         if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
     309           0 :             pcmk__dump_xml_attr(attr, buffer);
     310             :         }
     311             :     }
     312             : 
     313           0 :     if (data->children == NULL) {
     314           0 :         g_string_append(buffer, "/>");
     315             : 
     316             :     } else {
     317             :         g_string_append_c(buffer, '>');
     318             :     }
     319             : 
     320           0 :     if (pretty) {
     321             :         g_string_append_c(buffer, '\n');
     322             :     }
     323             : 
     324           0 :     if (data->children) {
     325           0 :         for (const xmlNode *child = data->children; child != NULL;
     326           0 :              child = child->next) {
     327           0 :             pcmk__xml_string(child, options, buffer, depth + 1);
     328             :         }
     329             : 
     330           0 :         for (int lpc = 0; lpc < spaces; lpc++) {
     331             :             g_string_append_c(buffer, ' ');
     332             :         }
     333             : 
     334           0 :         pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
     335             : 
     336           0 :         if (pretty) {
     337             :             g_string_append_c(buffer, '\n');
     338             :         }
     339             :     }
     340           0 : }
     341             : 
     342             : /*!
     343             :  * \internal
     344             :  * \brief Append XML text content to a buffer
     345             :  *
     346             :  * \param[in]     data     XML whose content to append
     347             :  * \param[in]     options  Group of \p xml_log_options flags
     348             :  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
     349             :  * \param[in]     depth    Current indentation level
     350             :  */
     351             : static void
     352           0 : dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
     353             :               int depth)
     354             : {
     355           0 :     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     356           0 :     int spaces = pretty? (2 * depth) : 0;
     357           0 :     const char *content = (const char *) data->content;
     358           0 :     gchar *content_esc = NULL;
     359             : 
     360           0 :     if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) {
     361           0 :         content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
     362           0 :         content = content_esc;
     363             :     }
     364             : 
     365           0 :     for (int lpc = 0; lpc < spaces; lpc++) {
     366             :         g_string_append_c(buffer, ' ');
     367             :     }
     368             : 
     369             :     g_string_append(buffer, content);
     370             : 
     371           0 :     if (pretty) {
     372             :         g_string_append_c(buffer, '\n');
     373             :     }
     374           0 :     g_free(content_esc);
     375           0 : }
     376             : 
     377             : /*!
     378             :  * \internal
     379             :  * \brief Append XML CDATA content to a buffer
     380             :  *
     381             :  * \param[in]     data     XML whose content to append
     382             :  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
     383             :  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
     384             :  * \param[in]     depth    Current indentation level
     385             :  */
     386             : static void
     387           0 : dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
     388             :                int depth)
     389             : {
     390           0 :     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     391           0 :     int spaces = pretty? (2 * depth) : 0;
     392             : 
     393           0 :     for (int lpc = 0; lpc < spaces; lpc++) {
     394             :         g_string_append_c(buffer, ' ');
     395             :     }
     396             : 
     397           0 :     pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
     398             :                    NULL);
     399             : 
     400           0 :     if (pretty) {
     401             :         g_string_append_c(buffer, '\n');
     402             :     }
     403           0 : }
     404             : 
     405             : /*!
     406             :  * \internal
     407             :  * \brief Append an XML comment to a buffer
     408             :  *
     409             :  * \param[in]     data     XML whose content to append
     410             :  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
     411             :  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
     412             :  * \param[in]     depth    Current indentation level
     413             :  */
     414             : static void
     415           0 : dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
     416             :                  int depth)
     417             : {
     418           0 :     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     419           0 :     int spaces = pretty? (2 * depth) : 0;
     420             : 
     421           0 :     for (int lpc = 0; lpc < spaces; lpc++) {
     422             :         g_string_append_c(buffer, ' ');
     423             :     }
     424             : 
     425           0 :     pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
     426             : 
     427           0 :     if (pretty) {
     428             :         g_string_append_c(buffer, '\n');
     429             :     }
     430           0 : }
     431             : 
     432             : /*!
     433             :  * \internal
     434             :  * \brief Get a string representation of an XML element type
     435             :  *
     436             :  * \param[in] type  XML element type
     437             :  *
     438             :  * \return String representation of \p type
     439             :  */
     440             : static const char *
     441           0 : xml_element_type_text(xmlElementType type)
     442             : {
     443             :     static const char *const element_type_names[] = {
     444             :         [XML_ELEMENT_NODE]       = "element",
     445             :         [XML_ATTRIBUTE_NODE]     = "attribute",
     446             :         [XML_TEXT_NODE]          = "text",
     447             :         [XML_CDATA_SECTION_NODE] = "CDATA section",
     448             :         [XML_ENTITY_REF_NODE]    = "entity reference",
     449             :         [XML_ENTITY_NODE]        = "entity",
     450             :         [XML_PI_NODE]            = "PI",
     451             :         [XML_COMMENT_NODE]       = "comment",
     452             :         [XML_DOCUMENT_NODE]      = "document",
     453             :         [XML_DOCUMENT_TYPE_NODE] = "document type",
     454             :         [XML_DOCUMENT_FRAG_NODE] = "document fragment",
     455             :         [XML_NOTATION_NODE]      = "notation",
     456             :         [XML_HTML_DOCUMENT_NODE] = "HTML document",
     457             :         [XML_DTD_NODE]           = "DTD",
     458             :         [XML_ELEMENT_DECL]       = "element declaration",
     459             :         [XML_ATTRIBUTE_DECL]     = "attribute declaration",
     460             :         [XML_ENTITY_DECL]        = "entity declaration",
     461             :         [XML_NAMESPACE_DECL]     = "namespace declaration",
     462             :         [XML_XINCLUDE_START]     = "XInclude start",
     463             :         [XML_XINCLUDE_END]       = "XInclude end",
     464             :     };
     465             : 
     466           0 :     if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
     467           0 :         return "unrecognized type";
     468             :     }
     469           0 :     return element_type_names[type];
     470             : }
     471             : 
     472             : /*!
     473             :  * \internal
     474             :  * \brief Create a string representation of an XML object
     475             :  *
     476             :  * libxml2's \c xmlNodeDumpOutput() doesn't allow filtering, doesn't escape
     477             :  * special characters thoroughly, and doesn't allow a const argument.
     478             :  *
     479             :  * \param[in]     data     XML to convert
     480             :  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
     481             :  * \param[in,out] buffer   Where to store the text (must not be \p NULL)
     482             :  * \param[in]     depth    Current indentation level
     483             :  *
     484             :  * \todo Create a wrapper that doesn't require \p depth. Only used with
     485             :  *       recursive calls currently.
     486             :  */
     487             : void
     488           0 : pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
     489             :                  int depth)
     490             : {
     491           0 :     if (data == NULL) {
     492           0 :         crm_trace("Nothing to dump");
     493           0 :         return;
     494             :     }
     495             : 
     496           0 :     CRM_ASSERT(buffer != NULL);
     497           0 :     CRM_CHECK(depth >= 0, depth = 0);
     498             : 
     499           0 :     switch(data->type) {
     500           0 :         case XML_ELEMENT_NODE:
     501             :             /* Handle below */
     502           0 :             dump_xml_element(data, options, buffer, depth);
     503           0 :             break;
     504           0 :         case XML_TEXT_NODE:
     505           0 :             if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
     506           0 :                 dump_xml_text(data, options, buffer, depth);
     507             :             }
     508           0 :             break;
     509           0 :         case XML_COMMENT_NODE:
     510           0 :             dump_xml_comment(data, options, buffer, depth);
     511           0 :             break;
     512           0 :         case XML_CDATA_SECTION_NODE:
     513           0 :             dump_xml_cdata(data, options, buffer, depth);
     514           0 :             break;
     515           0 :         default:
     516           0 :             crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d",
     517             :                      xml_element_type_text(data->type), data->type);
     518           0 :             break;
     519             :     }
     520             : }
     521             : 
     522             : /*!
     523             :  * \internal
     524             :  * \brief Write a string to a file stream, compressed using \c bzip2
     525             :  *
     526             :  * \param[in]     text       String to write
     527             :  * \param[in]     filename   Name of file being written (for logging only)
     528             :  * \param[in,out] stream     Open file stream to write to
     529             :  * \param[out]    bytes_out  Number of bytes written (valid only on success)
     530             :  *
     531             :  * \return Standard Pacemaker return code
     532             :  */
     533             : static int
     534           0 : write_compressed_stream(char *text, const char *filename, FILE *stream,
     535             :                         unsigned int *bytes_out)
     536             : {
     537           0 :     unsigned int bytes_in = 0;
     538           0 :     int rc = pcmk_rc_ok;
     539             : 
     540             :     // (5, 0, 0): (intermediate block size, silent, default workFactor)
     541           0 :     BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
     542             : 
     543           0 :     rc = pcmk__bzlib2rc(rc);
     544           0 :     if (rc != pcmk_rc_ok) {
     545           0 :         crm_warn("Not compressing %s: could not prepare file stream: %s "
     546             :                  CRM_XS " rc=%d",
     547             :                  filename, pcmk_rc_str(rc), rc);
     548           0 :         goto done;
     549             :     }
     550             : 
     551           0 :     BZ2_bzWrite(&rc, bz_file, text, strlen(text));
     552           0 :     rc = pcmk__bzlib2rc(rc);
     553           0 :     if (rc != pcmk_rc_ok) {
     554           0 :         crm_warn("Not compressing %s: could not compress data: %s "
     555             :                  CRM_XS " rc=%d errno=%d",
     556             :                  filename, pcmk_rc_str(rc), rc, errno);
     557           0 :         goto done;
     558             :     }
     559             : 
     560           0 :     BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
     561           0 :     bz_file = NULL;
     562           0 :     rc = pcmk__bzlib2rc(rc);
     563           0 :     if (rc != pcmk_rc_ok) {
     564           0 :         crm_warn("Not compressing %s: could not write compressed data: %s "
     565             :                  CRM_XS " rc=%d errno=%d",
     566             :                  filename, pcmk_rc_str(rc), rc, errno);
     567           0 :         goto done;
     568             :     }
     569             : 
     570           0 :     crm_trace("Compressed XML for %s from %u bytes to %u",
     571             :               filename, bytes_in, *bytes_out);
     572             : 
     573           0 : done:
     574           0 :     if (bz_file != NULL) {
     575           0 :         BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
     576             :     }
     577           0 :     return rc;
     578             : }
     579             : 
     580             : /*!
     581             :  * \internal
     582             :  * \brief Write XML to a file stream
     583             :  *
     584             :  * \param[in]     xml       XML to write
     585             :  * \param[in]     filename  Name of file being written (for logging only)
     586             :  * \param[in,out] stream    Open file stream corresponding to filename (closed
     587             :  *                          when this function returns)
     588             :  * \param[in]     compress  Whether to compress XML before writing
     589             :  * \param[out]    nbytes    Number of bytes written
     590             :  *
     591             :  * \return Standard Pacemaker return code
     592             :  */
     593             : static int
     594           0 : write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
     595             :                  bool compress, unsigned int *nbytes)
     596             : {
     597             :     // @COMPAT Drop nbytes as arg when we drop write_xml_fd()/write_xml_file()
     598           0 :     GString *buffer = g_string_sized_new(1024);
     599           0 :     unsigned int bytes_out = 0;
     600           0 :     int rc = pcmk_rc_ok;
     601             : 
     602           0 :     pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
     603           0 :     CRM_CHECK(!pcmk__str_empty(buffer->str),
     604             :               crm_log_xml_info(xml, "dump-failed");
     605             :               rc = pcmk_rc_error;
     606             :               goto done);
     607             : 
     608           0 :     crm_log_xml_trace(xml, "writing");
     609             : 
     610           0 :     if (compress
     611           0 :         && (write_compressed_stream(buffer->str, filename, stream,
     612             :                                     &bytes_out) == pcmk_rc_ok)) {
     613           0 :         goto done;
     614             :     }
     615             : 
     616           0 :     rc = fprintf(stream, "%s", buffer->str);
     617           0 :     if (rc < 0) {
     618           0 :         rc = EIO;
     619           0 :         crm_perror(LOG_ERR, "writing %s", filename);
     620           0 :         goto done;
     621             :     }
     622           0 :     bytes_out = (unsigned int) rc;
     623           0 :     rc = pcmk_rc_ok;
     624             : 
     625           0 : done:
     626           0 :     if (fflush(stream) != 0) {
     627           0 :         rc = errno;
     628           0 :         crm_perror(LOG_ERR, "flushing %s", filename);
     629             :     }
     630             : 
     631             :     // Don't report error if the file does not support synchronization
     632           0 :     if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
     633           0 :         rc = errno;
     634           0 :         crm_perror(LOG_ERR, "synchronizing %s", filename);
     635             :     }
     636             : 
     637           0 :     fclose(stream);
     638           0 :     crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
     639             : 
     640           0 :     if (nbytes != NULL) {
     641           0 :         *nbytes = bytes_out;
     642             :     }
     643           0 :     g_string_free(buffer, TRUE);
     644           0 :     return rc;
     645             : }
     646             : 
     647             : /*!
     648             :  * \internal
     649             :  * \brief Write XML to a file descriptor
     650             :  *
     651             :  * \param[in]  xml       XML to write
     652             :  * \param[in]  filename  Name of file being written (for logging only)
     653             :  * \param[in]  fd        Open file descriptor corresponding to \p filename
     654             :  * \param[in]  compress  If \c true, compress XML before writing
     655             :  * \param[out] nbytes    Number of bytes written (can be \c NULL)
     656             :  *
     657             :  * \return Standard Pacemaker return code
     658             :  */
     659             : int
     660           0 : pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd,
     661             :                    bool compress, unsigned int *nbytes)
     662             : {
     663             :     // @COMPAT Drop compress and nbytes arguments when we drop write_xml_fd()
     664           0 :     FILE *stream = NULL;
     665             : 
     666           0 :     CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
     667           0 :     stream = fdopen(fd, "w");
     668           0 :     if (stream == NULL) {
     669           0 :         return errno;
     670             :     }
     671             : 
     672           0 :     return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
     673             :                             compress, nbytes);
     674             : }
     675             : 
     676             : /*!
     677             :  * \internal
     678             :  * \brief Write XML to a file
     679             :  *
     680             :  * \param[in]  xml       XML to write
     681             :  * \param[in]  filename  Name of file to write
     682             :  * \param[in]  compress  If \c true, compress XML before writing
     683             :  * \param[out] nbytes    Number of bytes written (can be \c NULL)
     684             :  *
     685             :  * \return Standard Pacemaker return code
     686             :  */
     687             : int
     688           0 : pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress,
     689             :                      unsigned int *nbytes)
     690             : {
     691             :     // @COMPAT Drop nbytes argument when we drop write_xml_fd()
     692           0 :     FILE *stream = NULL;
     693             : 
     694           0 :     CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
     695           0 :     stream = fopen(filename, "w");
     696           0 :     if (stream == NULL) {
     697           0 :         return errno;
     698             :     }
     699             : 
     700           0 :     return write_xml_stream(xml, filename, stream, compress, nbytes);
     701             : }
     702             : 
     703             : /*!
     704             :  * \internal
     705             :  * \brief Serialize XML (using libxml) into provided descriptor
     706             :  *
     707             :  * \param[in] fd  File descriptor to (piece-wise) write to
     708             :  * \param[in] cur XML subtree to proceed
     709             :  *
     710             :  * \return a standard Pacemaker return code
     711             :  */
     712             : int
     713           0 : pcmk__xml2fd(int fd, xmlNode *cur)
     714             : {
     715             :     bool success;
     716             : 
     717           0 :     xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
     718           0 :     pcmk__mem_assert(fd_out);
     719           0 :     xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
     720             : 
     721           0 :     success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
     722             : 
     723           0 :     success = xmlOutputBufferClose(fd_out) != -1 && success;
     724             : 
     725           0 :     if (!success) {
     726           0 :         return EIO;
     727             :     }
     728             : 
     729           0 :     fsync(fd);
     730           0 :     return pcmk_rc_ok;
     731             : }
     732             : 
     733             : void
     734           0 : save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
     735             : {
     736           0 :     char *f = NULL;
     737             : 
     738           0 :     if (filename == NULL) {
     739           0 :         char *uuid = crm_generate_uuid();
     740             : 
     741           0 :         f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
     742           0 :         filename = f;
     743           0 :         free(uuid);
     744             :     }
     745             : 
     746           0 :     crm_info("Saving %s to %s", desc, filename);
     747           0 :     pcmk__xml_write_file(xml, filename, false, NULL);
     748           0 :     free(f);
     749           0 : }
     750             : 
     751             : 
     752             : // Deprecated functions kept only for backward API compatibility
     753             : // LCOV_EXCL_START
     754             : 
     755             : #include <crm/common/xml_io_compat.h>
     756             : 
     757             : xmlNode *
     758             : filename2xml(const char *filename)
     759             : {
     760             :     return pcmk__xml_read(filename);
     761             : }
     762             : 
     763             : xmlNode *
     764             : stdin2xml(void)
     765             : {
     766             :     return pcmk__xml_read(NULL);
     767             : }
     768             : 
     769             : xmlNode *
     770             : string2xml(const char *input)
     771             : {
     772             :     return pcmk__xml_parse(input);
     773             : }
     774             : 
     775             : char *
     776             : dump_xml_formatted(const xmlNode *xml)
     777             : {
     778             :     char *str = NULL;
     779             :     GString *buffer = g_string_sized_new(1024);
     780             : 
     781             :     pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
     782             : 
     783             :     str = pcmk__str_copy(buffer->str);
     784             :     g_string_free(buffer, TRUE);
     785             :     return str;
     786             : }
     787             : 
     788             : char *
     789             : dump_xml_formatted_with_text(const xmlNode *xml)
     790             : {
     791             :     char *str = NULL;
     792             :     GString *buffer = g_string_sized_new(1024);
     793             : 
     794             :     pcmk__xml_string(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buffer, 0);
     795             : 
     796             :     str = pcmk__str_copy(buffer->str);
     797             :     g_string_free(buffer, TRUE);
     798             :     return str;
     799             : }
     800             : 
     801             : char *
     802             : dump_xml_unformatted(const xmlNode *xml)
     803             : {
     804             :     char *str = NULL;
     805             :     GString *buffer = g_string_sized_new(1024);
     806             : 
     807             :     pcmk__xml_string(xml, 0, buffer, 0);
     808             : 
     809             :     str = pcmk__str_copy(buffer->str);
     810             :     g_string_free(buffer, TRUE);
     811             :     return str;
     812             : }
     813             : 
     814             : int
     815             : write_xml_fd(const xmlNode *xml, const char *filename, int fd,
     816             :              gboolean compress)
     817             : {
     818             :     unsigned int nbytes = 0;
     819             :     int rc = pcmk__xml_write_fd(xml, filename, fd, compress, &nbytes);
     820             : 
     821             :     if (rc != pcmk_rc_ok) {
     822             :         return pcmk_rc2legacy(rc);
     823             :     }
     824             :     return (int) nbytes;
     825             : }
     826             : 
     827             : int
     828             : write_xml_file(const xmlNode *xml, const char *filename, gboolean compress)
     829             : {
     830             :     unsigned int nbytes = 0;
     831             :     int rc = pcmk__xml_write_file(xml, filename, compress, &nbytes);
     832             : 
     833             :     if (rc != pcmk_rc_ok) {
     834             :         return pcmk_rc2legacy(rc);
     835             :     }
     836             :     return (int) nbytes;
     837             : }
     838             : 
     839             : // LCOV_EXCL_STOP
     840             : // End deprecated API

Generated by: LCOV version 1.14