Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/xml_io_compat.h b/include/crm/common/xml_io_compat.h
index 52f97699b1..9e649acd71 100644
--- a/include/crm/common/xml_io_compat.h
+++ b/include/crm/common/xml_io_compat.h
@@ -1,38 +1,35 @@
/*
* 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.
*/
#ifndef PCMK__CRM_COMMON_XML_IO_COMPAT__H
#define PCMK__CRM_COMMON_XML_IO_COMPAT__H
#include <libxml/tree.h> // xmlNode
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Deprecated Pacemaker XML I/O API
* \ingroup core
* \deprecated Do not include this header directly. The XML APIs in this
* header, and the header itself, will be removed in a future
* release.
*/
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *filename2xml(const char *filename);
-//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
-xmlNode *stdin2xml(void);
-
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_XML_IO_COMPAT__H
diff --git a/lib/common/xml_io.c b/lib/common/xml_io.c
index d902efa4f3..49b74a1a5b 100644
--- a/lib/common/xml_io.c
+++ b/lib/common/xml_io.c
@@ -1,770 +1,764 @@
/*
* 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);
-}
-
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 2:55 AM (16 m, 35 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1010890
Default Alt Text
(24 KB)

Event Timeline