root/lib/common/xml_io.c

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

DEFINITIONS

This source file includes following definitions.
  1. decompress_file
  2. pcmk__xml_read
  3. pcmk__xml_parse
  4. dump_xml_element
  5. dump_xml_text
  6. dump_xml_cdata
  7. dump_xml_comment
  8. xml_element_type_text
  9. pcmk__xml_string
  10. write_compressed_stream
  11. write_xml_stream
  12. pcmk__xml_write_fd
  13. pcmk__xml_write_file
  14. pcmk__xml2fd
  15. save_xml_to_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 /*!
  28  * \internal
  29  * \brief Decompress a <tt>bzip2</tt>-compressed file into a string buffer
  30  *
  31  * \param[in] filename  Name of file to decompress
  32  *
  33  * \return Newly allocated string with the decompressed contents of \p filename,
  34  *         or \c NULL on error.
  35  *
  36  * \note The caller is responsible for freeing the return value using \c free().
  37  */
  38 static char *
  39 decompress_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
  40 {
  41     char *buffer = NULL;
  42     int rc = pcmk_rc_ok;
  43     size_t length = 0;
  44     BZFILE *bz_file = NULL;
  45     FILE *input = fopen(filename, "r");
  46 
  47     if (input == NULL) {
  48         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
  49         return NULL;
  50     }
  51 
  52     bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
  53     rc = pcmk__bzlib2rc(rc);
  54     if (rc != pcmk_rc_ok) {
  55         crm_err("Could not prepare to read compressed %s: %s "
  56                 QB_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
  57         goto done;
  58     }
  59 
  60     // cppcheck seems not to understand the abort-logic in pcmk__realloc
  61     // cppcheck-suppress memleak
  62     do {
  63         int read_len = 0;
  64 
  65         buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1);
  66         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
  67 
  68         if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) {
  69             crm_trace("Read %ld bytes from file: %d", (long) read_len, rc);
  70             length += read_len;
  71         }
  72     } while (rc == BZ_OK);
  73 
  74     rc = pcmk__bzlib2rc(rc);
  75     if (rc != pcmk_rc_ok) {
  76         rc = pcmk__bzlib2rc(rc);
  77         crm_err("Could not read compressed %s: %s " QB_XS " rc=%d",
  78                 filename, pcmk_rc_str(rc), rc);
  79         free(buffer);
  80         buffer = NULL;
  81     } else {
  82         buffer[length] = '\0';
  83     }
  84 
  85 done:
  86     BZ2_bzReadClose(&rc, bz_file);
  87     fclose(input);
  88     return buffer;
  89 }
  90 
  91 /*!
  92  * \internal
  93  * \brief Parse XML from a file
  94  *
  95  * \param[in] filename  Name of file containing XML (\c NULL or \c "-" for
  96  *                      \c stdin); if \p filename ends in \c ".bz2", the file
  97  *                      will be decompressed using \c bzip2
  98  *
  99  * \return XML tree parsed from the given file on success, otherwise \c NULL
 100  */
 101 xmlNode *
 102 pcmk__xml_read(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 103 {
 104     bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches);
 105     xmlNode *xml = NULL;
 106     xmlDoc *output = NULL;
 107     xmlParserCtxt *ctxt = NULL;
 108     const xmlError *last_error = NULL;
 109 
 110     // Create a parser context
 111     ctxt = xmlNewParserCtxt();
 112     CRM_CHECK(ctxt != NULL, return NULL);
 113 
 114     xmlCtxtResetLastError(ctxt);
 115     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
 116 
 117     if (use_stdin) {
 118         output = xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL,
 119                                XML_PARSE_NOBLANKS);
 120 
 121     } else if (pcmk__ends_with_ext(filename, ".bz2")) {
 122         char *input = decompress_file(filename);
 123 
 124         if (input != NULL) {
 125             output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
 126                                     XML_PARSE_NOBLANKS);
 127             free(input);
 128         }
 129 
 130     } else {
 131         output = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_NOBLANKS);
 132     }
 133 
 134     if (output != NULL) {
 135         pcmk__xml_new_private_data((xmlNode *) output);
 136         xml = xmlDocGetRootElement(output);
 137         if (xml != NULL) {
 138             /* @TODO Should we really be stripping out text? This seems like an
 139              * overly broad way to get rid of whitespace, if that's the goal.
 140              * Text nodes may be invalid in most or all Pacemaker inputs, but
 141              * stripping them in a generic "parse XML from file" function may
 142              * not be the best way to ignore them.
 143              */
 144             pcmk__strip_xml_text(xml);
 145         }
 146     }
 147 
 148     last_error = xmlCtxtGetLastError(ctxt);
 149     if ((last_error != NULL) && (xml != NULL)) {
 150         crm_log_xml_debug(xml, "partial");
 151         pcmk__xml_free(xml);
 152         xml = NULL;
 153     }
 154 
 155     xmlFreeParserCtxt(ctxt);
 156     return xml;
 157 }
 158 
 159 /*!
 160  * \internal
 161  * \brief Parse XML from a string
 162  *
 163  * \param[in] input  String to parse
 164  *
 165  * \return XML tree parsed from the given string on success, otherwise \c NULL
 166  */
 167 xmlNode *
 168 pcmk__xml_parse(const char *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 169 {
 170     xmlNode *xml = NULL;
 171     xmlDoc *output = NULL;
 172     xmlParserCtxt *ctxt = NULL;
 173     const xmlError *last_error = NULL;
 174 
 175     if (input == NULL) {
 176         return NULL;
 177     }
 178 
 179     ctxt = xmlNewParserCtxt();
 180     if (ctxt == NULL) {
 181         return NULL;
 182     }
 183 
 184     xmlCtxtResetLastError(ctxt);
 185     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
 186 
 187     output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
 188                             XML_PARSE_NOBLANKS);
 189     if (output != NULL) {
 190         pcmk__xml_new_private_data((xmlNode *) output);
 191         xml = xmlDocGetRootElement(output);
 192     }
 193 
 194     last_error = xmlCtxtGetLastError(ctxt);
 195     if ((last_error != NULL) && (xml != NULL)) {
 196         crm_log_xml_debug(xml, "partial");
 197         pcmk__xml_free(xml);
 198         xml = NULL;
 199     }
 200 
 201     xmlFreeParserCtxt(ctxt);
 202     return xml;
 203 }
 204 
 205 /*!
 206  * \internal
 207  * \brief Append a string representation of an XML element to a buffer
 208  *
 209  * \param[in]     data     XML whose representation to append
 210  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 211  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
 212  * \param[in]     depth    Current indentation level
 213  */
 214 static void
 215 dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
     /* [previous][next][first][last][top][bottom][index][help] */
 216                  int depth)
 217 {
 218     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 219     bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
 220     int spaces = pretty? (2 * depth) : 0;
 221 
 222     for (int lpc = 0; lpc < spaces; lpc++) {
 223         g_string_append_c(buffer, ' ');
 224     }
 225 
 226     pcmk__g_strcat(buffer, "<", data->name, NULL);
 227 
 228     for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
 229          attr = attr->next) {
 230 
 231         if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
 232             pcmk__dump_xml_attr(attr, buffer);
 233         }
 234     }
 235 
 236     if (data->children == NULL) {
 237         g_string_append(buffer, "/>");
 238 
 239     } else {
 240         g_string_append_c(buffer, '>');
 241     }
 242 
 243     if (pretty) {
 244         g_string_append_c(buffer, '\n');
 245     }
 246 
 247     if (data->children) {
 248         for (const xmlNode *child = data->children; child != NULL;
 249              child = child->next) {
 250             pcmk__xml_string(child, options, buffer, depth + 1);
 251         }
 252 
 253         for (int lpc = 0; lpc < spaces; lpc++) {
 254             g_string_append_c(buffer, ' ');
 255         }
 256 
 257         pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
 258 
 259         if (pretty) {
 260             g_string_append_c(buffer, '\n');
 261         }
 262     }
 263 }
 264 
 265 /*!
 266  * \internal
 267  * \brief Append XML text content to a buffer
 268  *
 269  * \param[in]     data     XML whose content to append
 270  * \param[in]     options  Group of <tt>enum pcmk__xml_fmt_options</tt>
 271  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
 272  * \param[in]     depth    Current indentation level
 273  */
 274 static void
 275 dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
     /* [previous][next][first][last][top][bottom][index][help] */
 276               int depth)
 277 {
 278     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 279     int spaces = pretty? (2 * depth) : 0;
 280     const char *content = (const char *) data->content;
 281     gchar *content_esc = NULL;
 282 
 283     if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) {
 284         content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text);
 285         content = content_esc;
 286     }
 287 
 288     for (int lpc = 0; lpc < spaces; lpc++) {
 289         g_string_append_c(buffer, ' ');
 290     }
 291 
 292     g_string_append(buffer, content);
 293 
 294     if (pretty) {
 295         g_string_append_c(buffer, '\n');
 296     }
 297     g_free(content_esc);
 298 }
 299 
 300 /*!
 301  * \internal
 302  * \brief Append XML CDATA content to a buffer
 303  *
 304  * \param[in]     data     XML whose content to append
 305  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 306  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
 307  * \param[in]     depth    Current indentation level
 308  */
 309 static void
 310 dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
     /* [previous][next][first][last][top][bottom][index][help] */
 311                int depth)
 312 {
 313     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 314     int spaces = pretty? (2 * depth) : 0;
 315 
 316     for (int lpc = 0; lpc < spaces; lpc++) {
 317         g_string_append_c(buffer, ' ');
 318     }
 319 
 320     pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
 321                    NULL);
 322 
 323     if (pretty) {
 324         g_string_append_c(buffer, '\n');
 325     }
 326 }
 327 
 328 /*!
 329  * \internal
 330  * \brief Append an XML comment to a buffer
 331  *
 332  * \param[in]     data     XML whose content to append
 333  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 334  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
 335  * \param[in]     depth    Current indentation level
 336  */
 337 static void
 338 dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
     /* [previous][next][first][last][top][bottom][index][help] */
 339                  int depth)
 340 {
 341     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
 342     int spaces = pretty? (2 * depth) : 0;
 343 
 344     for (int lpc = 0; lpc < spaces; lpc++) {
 345         g_string_append_c(buffer, ' ');
 346     }
 347 
 348     pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
 349 
 350     if (pretty) {
 351         g_string_append_c(buffer, '\n');
 352     }
 353 }
 354 
 355 /*!
 356  * \internal
 357  * \brief Get a string representation of an XML element type
 358  *
 359  * \param[in] type  XML element type
 360  *
 361  * \return String representation of \p type
 362  */
 363 static const char *
 364 xml_element_type_text(xmlElementType type)
     /* [previous][next][first][last][top][bottom][index][help] */
 365 {
 366     static const char *const element_type_names[] = {
 367         [XML_ELEMENT_NODE]       = "element",
 368         [XML_ATTRIBUTE_NODE]     = "attribute",
 369         [XML_TEXT_NODE]          = "text",
 370         [XML_CDATA_SECTION_NODE] = "CDATA section",
 371         [XML_ENTITY_REF_NODE]    = "entity reference",
 372         [XML_ENTITY_NODE]        = "entity",
 373         [XML_PI_NODE]            = "PI",
 374         [XML_COMMENT_NODE]       = "comment",
 375         [XML_DOCUMENT_NODE]      = "document",
 376         [XML_DOCUMENT_TYPE_NODE] = "document type",
 377         [XML_DOCUMENT_FRAG_NODE] = "document fragment",
 378         [XML_NOTATION_NODE]      = "notation",
 379         [XML_HTML_DOCUMENT_NODE] = "HTML document",
 380         [XML_DTD_NODE]           = "DTD",
 381         [XML_ELEMENT_DECL]       = "element declaration",
 382         [XML_ATTRIBUTE_DECL]     = "attribute declaration",
 383         [XML_ENTITY_DECL]        = "entity declaration",
 384         [XML_NAMESPACE_DECL]     = "namespace declaration",
 385         [XML_XINCLUDE_START]     = "XInclude start",
 386         [XML_XINCLUDE_END]       = "XInclude end",
 387     };
 388 
 389     if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
 390         return "unrecognized type";
 391     }
 392     return element_type_names[type];
 393 }
 394 
 395 /*!
 396  * \internal
 397  * \brief Create a string representation of an XML object
 398  *
 399  * libxml2's \c xmlNodeDumpOutput() doesn't allow filtering, doesn't escape
 400  * special characters thoroughly, and doesn't allow a const argument.
 401  *
 402  * \param[in]     data     XML to convert
 403  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
 404  * \param[in,out] buffer   Where to store the text (must not be \p NULL)
 405  * \param[in]     depth    Current indentation level
 406  *
 407  * \todo Create a wrapper that doesn't require \p depth. Only used with
 408  *       recursive calls currently.
 409  */
 410 void
 411 pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer,
     /* [previous][next][first][last][top][bottom][index][help] */
 412                  int depth)
 413 {
 414     if (data == NULL) {
 415         crm_trace("Nothing to dump");
 416         return;
 417     }
 418 
 419     pcmk__assert(buffer != NULL);
 420     CRM_CHECK(depth >= 0, depth = 0);
 421 
 422     switch(data->type) {
 423         case XML_ELEMENT_NODE:
 424             /* Handle below */
 425             dump_xml_element(data, options, buffer, depth);
 426             break;
 427         case XML_TEXT_NODE:
 428             if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
 429                 dump_xml_text(data, options, buffer, depth);
 430             }
 431             break;
 432         case XML_COMMENT_NODE:
 433             dump_xml_comment(data, options, buffer, depth);
 434             break;
 435         case XML_CDATA_SECTION_NODE:
 436             dump_xml_cdata(data, options, buffer, depth);
 437             break;
 438         default:
 439             crm_warn("Cannot convert XML %s node to text " QB_XS " type=%d",
 440                      xml_element_type_text(data->type), data->type);
 441             break;
 442     }
 443 }
 444 
 445 /*!
 446  * \internal
 447  * \brief Write a string to a file stream, compressed using \c bzip2
 448  *
 449  * \param[in]     text       String to write
 450  * \param[in]     filename   Name of file being written (for logging only)
 451  * \param[in,out] stream     Open file stream to write to
 452  * \param[out]    bytes_out  Number of bytes written (valid only on success)
 453  *
 454  * \return Standard Pacemaker return code
 455  */
 456 static int
 457 write_compressed_stream(char *text, const char *filename, FILE *stream,
     /* [previous][next][first][last][top][bottom][index][help] */
 458                         unsigned int *bytes_out)
 459 {
 460     unsigned int bytes_in = 0;
 461     int rc = pcmk_rc_ok;
 462 
 463     // (5, 0, 0): (intermediate block size, silent, default workFactor)
 464     BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0);
 465 
 466     rc = pcmk__bzlib2rc(rc);
 467     if (rc != pcmk_rc_ok) {
 468         crm_warn("Not compressing %s: could not prepare file stream: %s "
 469                  QB_XS " rc=%d",
 470                  filename, pcmk_rc_str(rc), rc);
 471         goto done;
 472     }
 473 
 474     BZ2_bzWrite(&rc, bz_file, text, strlen(text));
 475     rc = pcmk__bzlib2rc(rc);
 476     if (rc != pcmk_rc_ok) {
 477         crm_warn("Not compressing %s: could not compress data: %s "
 478                  QB_XS " rc=%d errno=%d",
 479                  filename, pcmk_rc_str(rc), rc, errno);
 480         goto done;
 481     }
 482 
 483     BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out);
 484     bz_file = NULL;
 485     rc = pcmk__bzlib2rc(rc);
 486     if (rc != pcmk_rc_ok) {
 487         crm_warn("Not compressing %s: could not write compressed data: %s "
 488                  QB_XS " rc=%d errno=%d",
 489                  filename, pcmk_rc_str(rc), rc, errno);
 490         goto done;
 491     }
 492 
 493     crm_trace("Compressed XML for %s from %u bytes to %u",
 494               filename, bytes_in, *bytes_out);
 495 
 496 done:
 497     if (bz_file != NULL) {
 498         BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL);
 499     }
 500     return rc;
 501 }
 502 
 503 /*!
 504  * \internal
 505  * \brief Write XML to a file stream
 506  *
 507  * \param[in]     xml       XML to write
 508  * \param[in]     filename  Name of file being written (for logging only)
 509  * \param[in,out] stream    Open file stream corresponding to filename (closed
 510  *                          when this function returns)
 511  * \param[in]     compress  Whether to compress XML before writing
 512  *
 513  * \return Standard Pacemaker return code
 514  */
 515 static int
 516 write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
     /* [previous][next][first][last][top][bottom][index][help] */
 517                  bool compress)
 518 {
 519     GString *buffer = g_string_sized_new(1024);
 520     unsigned int bytes_out = 0;
 521     int rc = pcmk_rc_ok;
 522 
 523     pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0);
 524     CRM_CHECK(!pcmk__str_empty(buffer->str),
 525               crm_log_xml_info(xml, "dump-failed");
 526               rc = pcmk_rc_error;
 527               goto done);
 528 
 529     crm_log_xml_trace(xml, "writing");
 530 
 531     if (compress
 532         && (write_compressed_stream(buffer->str, filename, stream,
 533                                     &bytes_out) == pcmk_rc_ok)) {
 534         goto done;
 535     }
 536 
 537     rc = fprintf(stream, "%s", buffer->str);
 538     if (rc < 0) {
 539         rc = EIO;
 540         crm_perror(LOG_ERR, "writing %s", filename);
 541         goto done;
 542     }
 543     bytes_out = (unsigned int) rc;
 544     rc = pcmk_rc_ok;
 545 
 546 done:
 547     if (fflush(stream) != 0) {
 548         rc = errno;
 549         crm_perror(LOG_ERR, "flushing %s", filename);
 550     }
 551 
 552     // Don't report error if the file does not support synchronization
 553     if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) {
 554         rc = errno;
 555         crm_perror(LOG_ERR, "synchronizing %s", filename);
 556     }
 557 
 558     fclose(stream);
 559     crm_trace("Saved %u bytes to %s as XML", bytes_out, filename);
 560 
 561     g_string_free(buffer, TRUE);
 562     return rc;
 563 }
 564 
 565 /*!
 566  * \internal
 567  * \brief Write XML to a file descriptor
 568  *
 569  * \param[in]  xml       XML to write
 570  * \param[in]  filename  Name of file being written (for logging only)
 571  * \param[in]  fd        Open file descriptor corresponding to \p filename
 572  *
 573  * \return Standard Pacemaker return code
 574  */
 575 int
 576 pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 577 {
 578     FILE *stream = NULL;
 579 
 580     CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL);
 581     stream = fdopen(fd, "w");
 582     if (stream == NULL) {
 583         return errno;
 584     }
 585 
 586     return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream,
 587                             false);
 588 }
 589 
 590 /*!
 591  * \internal
 592  * \brief Write XML to a file
 593  *
 594  * \param[in]  xml       XML to write
 595  * \param[in]  filename  Name of file to write
 596  * \param[in]  compress  If \c true, compress XML before writing
 597  *
 598  * \return Standard Pacemaker return code
 599  */
 600 int
 601 pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress)
     /* [previous][next][first][last][top][bottom][index][help] */
 602 {
 603     FILE *stream = NULL;
 604 
 605     CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL);
 606     stream = fopen(filename, "w");
 607     if (stream == NULL) {
 608         return errno;
 609     }
 610 
 611     return write_xml_stream(xml, filename, stream, compress);
 612 }
 613 
 614 /*!
 615  * \internal
 616  * \brief Serialize XML (using libxml) into provided descriptor
 617  *
 618  * \param[in] fd  File descriptor to (piece-wise) write to
 619  * \param[in] cur XML subtree to proceed
 620  *
 621  * \return a standard Pacemaker return code
 622  */
 623 int
 624 pcmk__xml2fd(int fd, xmlNode *cur)
     /* [previous][next][first][last][top][bottom][index][help] */
 625 {
 626     bool success;
 627 
 628     xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
 629     pcmk__mem_assert(fd_out);
 630     xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
 631 
 632     success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
 633 
 634     success = xmlOutputBufferClose(fd_out) != -1 && success;
 635 
 636     if (!success) {
 637         return EIO;
 638     }
 639 
 640     fsync(fd);
 641     return pcmk_rc_ok;
 642 }
 643 
 644 void
 645 save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 646 {
 647     char *f = NULL;
 648 
 649     if (filename == NULL) {
 650         char *uuid = crm_generate_uuid();
 651 
 652         f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
 653         filename = f;
 654         free(uuid);
 655     }
 656 
 657     crm_info("Saving %s to %s", desc, filename);
 658     pcmk__xml_write_file(xml, filename, false);
 659     free(f);
 660 }

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