root/lib/common/xml_io.c

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

DEFINITIONS

This source file includes following definitions.
  1. read_stdin
  2. decompress_file
  3. pcmk__xml_read
  4. pcmk__xml_parse
  5. dump_xml_element
  6. dump_xml_text
  7. dump_xml_cdata
  8. dump_xml_comment
  9. xml_element_type_text
  10. pcmk__xml_string
  11. write_compressed_stream
  12. write_xml_stream
  13. pcmk__xml_write_fd
  14. pcmk__xml_write_file
  15. pcmk__xml2fd
  16. save_xml_to_file
  17. filename2xml
  18. stdin2xml
  19. string2xml
  20. dump_xml_formatted
  21. dump_xml_formatted_with_text
  22. dump_xml_unformatted
  23. write_xml_fd
  24. write_xml_file

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

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