diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-03 13:39:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-03 13:39:28 +0000 |
commit | 924f5ea83e48277e014ebf0d19a27187cb93e2f7 (patch) | |
tree | 75920a275bba045f6d108204562c218a9a26ea15 /lib/common/xml_io.c | |
parent | Adding upstream version 2.1.7. (diff) | |
download | pacemaker-924f5ea83e48277e014ebf0d19a27187cb93e2f7.tar.xz pacemaker-924f5ea83e48277e014ebf0d19a27187cb93e2f7.zip |
Adding upstream version 2.1.8~rc1.upstream/2.1.8_rc1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/common/xml_io.c')
-rw-r--r-- | lib/common/xml_io.c | 840 |
1 files changed, 840 insertions, 0 deletions
diff --git a/lib/common/xml_io.c b/lib/common/xml_io.c new file mode 100644 index 0000000..f88e0b5 --- /dev/null +++ b/lib/common/xml_io.c @@ -0,0 +1,840 @@ +/* + * Copyright 2004-2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include <bzlib.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/xmlIO.h> // xmlOutputBuffer* + +#include <crm/crm.h> +#include <crm/common/xml.h> +#include <crm/common/xml_io.h> +#include "crmcommon_private.h" + +/* @COMPAT XML_PARSE_RECOVER allows some XML errors to be silently worked around + * by libxml2, which is potentially ambiguous and dangerous. We should drop it + * when we can break backward compatibility with configurations that might be + * relying on it (i.e. pacemaker 3.0.0). + */ +#define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER (XML_PARSE_NOBLANKS) +#define PCMK__XML_PARSE_OPTS_WITH_RECOVER (XML_PARSE_NOBLANKS \ + |XML_PARSE_RECOVER) + +/*! + * \internal + * \brief Read from \c stdin until EOF or error + * + * \return Newly allocated string containing the bytes read from \c stdin, or + * \c NULL on error + * + * \note The caller is responsible for freeing the return value using \c free(). + */ +static char * +read_stdin(void) +{ + char *buf = NULL; + size_t length = 0; + + do { + buf = pcmk__realloc(buf, length + PCMK__BUFFER_SIZE + 1); + length += fread(buf + length, 1, PCMK__BUFFER_SIZE, stdin); + } while ((feof(stdin) == 0) && (ferror(stdin) == 0)); + + if (ferror(stdin) != 0) { + crm_err("Error reading input from stdin"); + free(buf); + buf = NULL; + } else { + buf[length] = '\0'; + } + clearerr(stdin); + return buf; +} + +/*! + * \internal + * \brief Decompress a <tt>bzip2</tt>-compressed file into a string buffer + * + * \param[in] filename Name of file to decompress + * + * \return Newly allocated string with the decompressed contents of \p filename, + * or \c NULL on error. + * + * \note The caller is responsible for freeing the return value using \c free(). + */ +static char * +decompress_file(const char *filename) +{ + char *buffer = NULL; + int rc = pcmk_rc_ok; + size_t length = 0; + BZFILE *bz_file = NULL; + FILE *input = fopen(filename, "r"); + + if (input == NULL) { + crm_perror(LOG_ERR, "Could not open %s for reading", filename); + return NULL; + } + + bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0); + rc = pcmk__bzlib2rc(rc); + if (rc != pcmk_rc_ok) { + crm_err("Could not prepare to read compressed %s: %s " + CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc); + goto done; + } + + // cppcheck seems not to understand the abort-logic in pcmk__realloc + // cppcheck-suppress memleak + do { + int read_len = 0; + + buffer = pcmk__realloc(buffer, length + PCMK__BUFFER_SIZE + 1); + read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE); + + if ((rc == BZ_OK) || (rc == BZ_STREAM_END)) { + crm_trace("Read %ld bytes from file: %d", (long) read_len, rc); + length += read_len; + } + } while (rc == BZ_OK); + + rc = pcmk__bzlib2rc(rc); + if (rc != pcmk_rc_ok) { + rc = pcmk__bzlib2rc(rc); + crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d", + filename, pcmk_rc_str(rc), rc); + free(buffer); + buffer = NULL; + } else { + buffer[length] = '\0'; + } + +done: + BZ2_bzReadClose(&rc, bz_file); + fclose(input); + return buffer; +} + +// @COMPAT Remove macro at 3.0.0 when we drop XML_PARSE_RECOVER +/*! + * \internal + * \brief Try to parse XML first without and then with recovery enabled + * + * \param[out] result Where to store the resulting XML doc (<tt>xmlDoc **</tt>) + * \param[in] fn XML parser function + * \param[in] ... All arguments for \p fn except the final one (an + * \c xmlParserOption group) + */ +#define parse_xml_recover(result, fn, ...) do { \ + *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); \ + if (*result == NULL) { \ + *result = fn(__VA_ARGS__, PCMK__XML_PARSE_OPTS_WITH_RECOVER); \ + \ + if (*result != NULL) { \ + crm_warn("Successfully recovered from XML errors " \ + "(note: a future release will treat this as a " \ + "fatal failure)"); \ + } \ + } \ + } while (0); + +/*! + * \internal + * \brief Parse XML from a file + * + * \param[in] filename Name of file containing XML (\c NULL or \c "-" for + * \c stdin); if \p filename ends in \c ".bz2", the file + * will be decompressed using \c bzip2 + * + * \return XML tree parsed from the given file; may be \c NULL or only partial + * on error + */ +xmlNode * +pcmk__xml_read(const char *filename) +{ + bool use_stdin = pcmk__str_eq(filename, "-", pcmk__str_null_matches); + xmlNode *xml = NULL; + xmlDoc *output = NULL; + xmlParserCtxt *ctxt = NULL; + const xmlError *last_error = NULL; + + // Create a parser context + ctxt = xmlNewParserCtxt(); + CRM_CHECK(ctxt != NULL, return NULL); + + xmlCtxtResetLastError(ctxt); + xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err); + + if (use_stdin) { + /* @COMPAT After dropping XML_PARSE_RECOVER, we can avoid capturing + * stdin into a buffer and instead call + * xmlCtxtReadFd(ctxt, STDIN_FILENO, NULL, NULL, XML_PARSE_NOBLANKS); + * + * For now we have to save the input so that we can use it twice. + */ + char *input = read_stdin(); + + if (input != NULL) { + parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input, + NULL, NULL); + free(input); + } + + } else if (pcmk__ends_with_ext(filename, ".bz2")) { + char *input = decompress_file(filename); + + if (input != NULL) { + parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input, + NULL, NULL); + free(input); + } + + } else { + parse_xml_recover(&output, xmlCtxtReadFile, ctxt, filename, NULL); + } + + if (output != NULL) { + xml = xmlDocGetRootElement(output); + if (xml != NULL) { + /* @TODO Should we really be stripping out text? This seems like an + * overly broad way to get rid of whitespace, if that's the goal. + * Text nodes may be invalid in most or all Pacemaker inputs, but + * stripping them in a generic "parse XML from file" function may + * not be the best way to ignore them. + */ + pcmk__strip_xml_text(xml); + } + } + + // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL on error + last_error = xmlCtxtGetLastError(ctxt); + if (last_error != NULL) { + if (xml != NULL) { + crm_log_xml_info(xml, "Partial"); + } + } + + xmlFreeParserCtxt(ctxt); + return xml; +} + +/*! + * \internal + * \brief Parse XML from a string + * + * \param[in] input String to parse + * + * \return XML tree parsed from the given string; may be \c NULL or only partial + * on error + */ +xmlNode * +pcmk__xml_parse(const char *input) +{ + xmlNode *xml = NULL; + xmlDoc *output = NULL; + xmlParserCtxt *ctxt = NULL; + const xmlError *last_error = NULL; + + if (input == NULL) { + return NULL; + } + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) { + return NULL; + } + + xmlCtxtResetLastError(ctxt); + xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err); + + parse_xml_recover(&output, xmlCtxtReadDoc, ctxt, (pcmkXmlStr) input, NULL, + NULL); + + if (output != NULL) { + xml = xmlDocGetRootElement(output); + } + + // @COMPAT At 3.0.0, free xml and return NULL if xml != NULL; update doxygen + last_error = xmlCtxtGetLastError(ctxt); + if (last_error != NULL) { + if (xml != NULL) { + crm_log_xml_info(xml, "Partial"); + } + } + + xmlFreeParserCtxt(ctxt); + return xml; +} + +/*! + * \internal + * \brief Append a string representation of an XML element to a buffer + * + * \param[in] data XML whose representation to append + * \param[in] options Group of \p pcmk__xml_fmt_options flags + * \param[in,out] buffer Where to append the content (must not be \p NULL) + * \param[in] depth Current indentation level + */ +static void +dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer, + int depth) +{ + bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); + bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered); + int spaces = pretty? (2 * depth) : 0; + + for (int lpc = 0; lpc < spaces; lpc++) { + g_string_append_c(buffer, ' '); + } + + pcmk__g_strcat(buffer, "<", data->name, NULL); + + for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; + attr = attr->next) { + + if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) { + pcmk__dump_xml_attr(attr, buffer); + } + } + + if (data->children == NULL) { + g_string_append(buffer, "/>"); + + } else { + g_string_append_c(buffer, '>'); + } + + if (pretty) { + g_string_append_c(buffer, '\n'); + } + + if (data->children) { + for (const xmlNode *child = data->children; child != NULL; + child = child->next) { + pcmk__xml_string(child, options, buffer, depth + 1); + } + + for (int lpc = 0; lpc < spaces; lpc++) { + g_string_append_c(buffer, ' '); + } + + pcmk__g_strcat(buffer, "</", data->name, ">", NULL); + + if (pretty) { + g_string_append_c(buffer, '\n'); + } + } +} + +/*! + * \internal + * \brief Append XML text content to a buffer + * + * \param[in] data XML whose content to append + * \param[in] options Group of \p xml_log_options flags + * \param[in,out] buffer Where to append the content (must not be \p NULL) + * \param[in] depth Current indentation level + */ +static void +dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer, + int depth) +{ + bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); + int spaces = pretty? (2 * depth) : 0; + const char *content = (const char *) data->content; + gchar *content_esc = NULL; + + if (pcmk__xml_needs_escape(content, pcmk__xml_escape_text)) { + content_esc = pcmk__xml_escape(content, pcmk__xml_escape_text); + content = content_esc; + } + + for (int lpc = 0; lpc < spaces; lpc++) { + g_string_append_c(buffer, ' '); + } + + g_string_append(buffer, content); + + if (pretty) { + g_string_append_c(buffer, '\n'); + } + g_free(content_esc); +} + +/*! + * \internal + * \brief Append XML CDATA content to a buffer + * + * \param[in] data XML whose content to append + * \param[in] options Group of \p pcmk__xml_fmt_options flags + * \param[in,out] buffer Where to append the content (must not be \p NULL) + * \param[in] depth Current indentation level + */ +static void +dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer, + int depth) +{ + bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); + int spaces = pretty? (2 * depth) : 0; + + for (int lpc = 0; lpc < spaces; lpc++) { + g_string_append_c(buffer, ' '); + } + + pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>", + NULL); + + if (pretty) { + g_string_append_c(buffer, '\n'); + } +} + +/*! + * \internal + * \brief Append an XML comment to a buffer + * + * \param[in] data XML whose content to append + * \param[in] options Group of \p pcmk__xml_fmt_options flags + * \param[in,out] buffer Where to append the content (must not be \p NULL) + * \param[in] depth Current indentation level + */ +static void +dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer, + int depth) +{ + bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); + int spaces = pretty? (2 * depth) : 0; + + for (int lpc = 0; lpc < spaces; lpc++) { + g_string_append_c(buffer, ' '); + } + + pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL); + + if (pretty) { + g_string_append_c(buffer, '\n'); + } +} + +/*! + * \internal + * \brief Get a string representation of an XML element type + * + * \param[in] type XML element type + * + * \return String representation of \p type + */ +static const char * +xml_element_type_text(xmlElementType type) +{ + static const char *const element_type_names[] = { + [XML_ELEMENT_NODE] = "element", + [XML_ATTRIBUTE_NODE] = "attribute", + [XML_TEXT_NODE] = "text", + [XML_CDATA_SECTION_NODE] = "CDATA section", + [XML_ENTITY_REF_NODE] = "entity reference", + [XML_ENTITY_NODE] = "entity", + [XML_PI_NODE] = "PI", + [XML_COMMENT_NODE] = "comment", + [XML_DOCUMENT_NODE] = "document", + [XML_DOCUMENT_TYPE_NODE] = "document type", + [XML_DOCUMENT_FRAG_NODE] = "document fragment", + [XML_NOTATION_NODE] = "notation", + [XML_HTML_DOCUMENT_NODE] = "HTML document", + [XML_DTD_NODE] = "DTD", + [XML_ELEMENT_DECL] = "element declaration", + [XML_ATTRIBUTE_DECL] = "attribute declaration", + [XML_ENTITY_DECL] = "entity declaration", + [XML_NAMESPACE_DECL] = "namespace declaration", + [XML_XINCLUDE_START] = "XInclude start", + [XML_XINCLUDE_END] = "XInclude end", + }; + + if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) { + return "unrecognized type"; + } + return element_type_names[type]; +} + +/*! + * \internal + * \brief Create a string representation of an XML object + * + * libxml2's \c xmlNodeDumpOutput() doesn't allow filtering, doesn't escape + * special characters thoroughly, and doesn't allow a const argument. + * + * \param[in] data XML to convert + * \param[in] options Group of \p pcmk__xml_fmt_options flags + * \param[in,out] buffer Where to store the text (must not be \p NULL) + * \param[in] depth Current indentation level + * + * \todo Create a wrapper that doesn't require \p depth. Only used with + * recursive calls currently. + */ +void +pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, + int depth) +{ + if (data == NULL) { + crm_trace("Nothing to dump"); + return; + } + + CRM_ASSERT(buffer != NULL); + CRM_CHECK(depth >= 0, depth = 0); + + switch(data->type) { + case XML_ELEMENT_NODE: + /* Handle below */ + dump_xml_element(data, options, buffer, depth); + break; + case XML_TEXT_NODE: + if (pcmk_is_set(options, pcmk__xml_fmt_text)) { + dump_xml_text(data, options, buffer, depth); + } + break; + case XML_COMMENT_NODE: + dump_xml_comment(data, options, buffer, depth); + break; + case XML_CDATA_SECTION_NODE: + dump_xml_cdata(data, options, buffer, depth); + break; + default: + crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d", + xml_element_type_text(data->type), data->type); + break; + } +} + +/*! + * \internal + * \brief Write a string to a file stream, compressed using \c bzip2 + * + * \param[in] text String to write + * \param[in] filename Name of file being written (for logging only) + * \param[in,out] stream Open file stream to write to + * \param[out] bytes_out Number of bytes written (valid only on success) + * + * \return Standard Pacemaker return code + */ +static int +write_compressed_stream(char *text, const char *filename, FILE *stream, + unsigned int *bytes_out) +{ + unsigned int bytes_in = 0; + int rc = pcmk_rc_ok; + + // (5, 0, 0): (intermediate block size, silent, default workFactor) + BZFILE *bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 0); + + rc = pcmk__bzlib2rc(rc); + if (rc != pcmk_rc_ok) { + crm_warn("Not compressing %s: could not prepare file stream: %s " + CRM_XS " rc=%d", + filename, pcmk_rc_str(rc), rc); + goto done; + } + + BZ2_bzWrite(&rc, bz_file, text, strlen(text)); + rc = pcmk__bzlib2rc(rc); + if (rc != pcmk_rc_ok) { + crm_warn("Not compressing %s: could not compress data: %s " + CRM_XS " rc=%d errno=%d", + filename, pcmk_rc_str(rc), rc, errno); + goto done; + } + + BZ2_bzWriteClose(&rc, bz_file, 0, &bytes_in, bytes_out); + bz_file = NULL; + rc = pcmk__bzlib2rc(rc); + if (rc != pcmk_rc_ok) { + crm_warn("Not compressing %s: could not write compressed data: %s " + CRM_XS " rc=%d errno=%d", + filename, pcmk_rc_str(rc), rc, errno); + goto done; + } + + crm_trace("Compressed XML for %s from %u bytes to %u", + filename, bytes_in, *bytes_out); + +done: + if (bz_file != NULL) { + BZ2_bzWriteClose(&rc, bz_file, 0, NULL, NULL); + } + return rc; +} + +/*! + * \internal + * \brief Write XML to a file stream + * + * \param[in] xml XML to write + * \param[in] filename Name of file being written (for logging only) + * \param[in,out] stream Open file stream corresponding to filename (closed + * when this function returns) + * \param[in] compress Whether to compress XML before writing + * \param[out] nbytes Number of bytes written + * + * \return Standard Pacemaker return code + */ +static int +write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream, + bool compress, unsigned int *nbytes) +{ + // @COMPAT Drop nbytes as arg when we drop write_xml_fd()/write_xml_file() + GString *buffer = g_string_sized_new(1024); + unsigned int bytes_out = 0; + int rc = pcmk_rc_ok; + + pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0); + CRM_CHECK(!pcmk__str_empty(buffer->str), + crm_log_xml_info(xml, "dump-failed"); + rc = pcmk_rc_error; + goto done); + + crm_log_xml_trace(xml, "writing"); + + if (compress + && (write_compressed_stream(buffer->str, filename, stream, + &bytes_out) == pcmk_rc_ok)) { + goto done; + } + + rc = fprintf(stream, "%s", buffer->str); + if (rc < 0) { + rc = EIO; + crm_perror(LOG_ERR, "writing %s", filename); + goto done; + } + bytes_out = (unsigned int) rc; + rc = pcmk_rc_ok; + +done: + if (fflush(stream) != 0) { + rc = errno; + crm_perror(LOG_ERR, "flushing %s", filename); + } + + // Don't report error if the file does not support synchronization + if ((fsync(fileno(stream)) < 0) && (errno != EROFS) && (errno != EINVAL)) { + rc = errno; + crm_perror(LOG_ERR, "synchronizing %s", filename); + } + + fclose(stream); + crm_trace("Saved %u bytes to %s as XML", bytes_out, filename); + + if (nbytes != NULL) { + *nbytes = bytes_out; + } + g_string_free(buffer, TRUE); + return rc; +} + +/*! + * \internal + * \brief Write XML to a file descriptor + * + * \param[in] xml XML to write + * \param[in] filename Name of file being written (for logging only) + * \param[in] fd Open file descriptor corresponding to \p filename + * \param[in] compress If \c true, compress XML before writing + * \param[out] nbytes Number of bytes written (can be \c NULL) + * + * \return Standard Pacemaker return code + */ +int +pcmk__xml_write_fd(const xmlNode *xml, const char *filename, int fd, + bool compress, unsigned int *nbytes) +{ + // @COMPAT Drop compress and nbytes arguments when we drop write_xml_fd() + FILE *stream = NULL; + + CRM_CHECK((xml != NULL) && (fd > 0), return EINVAL); + stream = fdopen(fd, "w"); + if (stream == NULL) { + return errno; + } + + return write_xml_stream(xml, pcmk__s(filename, "unnamed file"), stream, + compress, nbytes); +} + +/*! + * \internal + * \brief Write XML to a file + * + * \param[in] xml XML to write + * \param[in] filename Name of file to write + * \param[in] compress If \c true, compress XML before writing + * \param[out] nbytes Number of bytes written (can be \c NULL) + * + * \return Standard Pacemaker return code + */ +int +pcmk__xml_write_file(const xmlNode *xml, const char *filename, bool compress, + unsigned int *nbytes) +{ + // @COMPAT Drop nbytes argument when we drop write_xml_fd() + FILE *stream = NULL; + + CRM_CHECK((xml != NULL) && (filename != NULL), return EINVAL); + stream = fopen(filename, "w"); + if (stream == NULL) { + return errno; + } + + return write_xml_stream(xml, filename, stream, compress, nbytes); +} + +/*! + * \internal + * \brief Serialize XML (using libxml) into provided descriptor + * + * \param[in] fd File descriptor to (piece-wise) write to + * \param[in] cur XML subtree to proceed + * + * \return a standard Pacemaker return code + */ +int +pcmk__xml2fd(int fd, xmlNode *cur) +{ + bool success; + + xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL); + pcmk__mem_assert(fd_out); + xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL); + + success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1; + + success = xmlOutputBufferClose(fd_out) != -1 && success; + + if (!success) { + return EIO; + } + + fsync(fd); + return pcmk_rc_ok; +} + +void +save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename) +{ + char *f = NULL; + + if (filename == NULL) { + char *uuid = crm_generate_uuid(); + + f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid); + filename = f; + free(uuid); + } + + crm_info("Saving %s to %s", desc, filename); + pcmk__xml_write_file(xml, filename, false, NULL); + free(f); +} + + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include <crm/common/xml_io_compat.h> + +xmlNode * +filename2xml(const char *filename) +{ + return pcmk__xml_read(filename); +} + +xmlNode * +stdin2xml(void) +{ + return pcmk__xml_read(NULL); +} + +xmlNode * +string2xml(const char *input) +{ + return pcmk__xml_parse(input); +} + +char * +dump_xml_formatted(const xmlNode *xml) +{ + char *str = NULL; + GString *buffer = g_string_sized_new(1024); + + pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buffer, 0); + + str = pcmk__str_copy(buffer->str); + g_string_free(buffer, TRUE); + return str; +} + +char * +dump_xml_formatted_with_text(const xmlNode *xml) +{ + char *str = NULL; + GString *buffer = g_string_sized_new(1024); + + pcmk__xml_string(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buffer, 0); + + str = pcmk__str_copy(buffer->str); + g_string_free(buffer, TRUE); + return str; +} + +char * +dump_xml_unformatted(const xmlNode *xml) +{ + char *str = NULL; + GString *buffer = g_string_sized_new(1024); + + pcmk__xml_string(xml, 0, buffer, 0); + + str = pcmk__str_copy(buffer->str); + g_string_free(buffer, TRUE); + return str; +} + +int +write_xml_fd(const xmlNode *xml, const char *filename, int fd, + gboolean compress) +{ + unsigned int nbytes = 0; + int rc = pcmk__xml_write_fd(xml, filename, fd, compress, &nbytes); + + if (rc != pcmk_rc_ok) { + return pcmk_rc2legacy(rc); + } + return (int) nbytes; +} + +int +write_xml_file(const xmlNode *xml, const char *filename, gboolean compress) +{ + unsigned int nbytes = 0; + int rc = pcmk__xml_write_file(xml, filename, compress, &nbytes); + + if (rc != pcmk_rc_ok) { + return pcmk_rc2legacy(rc); + } + return (int) nbytes; +} + +// LCOV_EXCL_STOP +// End deprecated API |