Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F1702018
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
92 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/common/xml.c b/lib/common/xml.c
index c9258df97a..6cf457d12f 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,3207 +1,3226 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <libxml/xmlreader.h>
#if HAVE_BZLIB_H
# include <bzlib.h>
#endif
#if HAVE_LIBXML2
# include <libxml/parser.h>
# include <libxml/tree.h>
# include <libxml/relaxng.h>
#endif
#if HAVE_LIBXSLT
# include <libxslt/xslt.h>
# include <libxslt/transform.h>
#endif
#define XML_BUFFER_SIZE 4096
#define XML_PARSER_DEBUG 0
#define BEST_EFFORT_STATUS 0
void
xml_log(int priority, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
void
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
qb_log_from_external_source_va(__FUNCTION__, __FILE__, fmt, priority, __LINE__, 0, ap);
va_end(ap);
}
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
struct schema_s {
int type;
const char *name;
const char *location;
const char *transform;
int after_transform;
void *cache;
};
typedef struct {
int found;
const char *string;
} filter_t;
/* *INDENT-OFF* */
struct schema_s known_schemas[] = {
/* 0 */ { 0, NULL, NULL, NULL, 1 },
/* 1 */ { 1, "pacemaker-0.6", "crm.dtd", "upgrade06.xsl", 4, NULL },
/* 2 */ { 1, "transitional-0.6", "crm-transitional.dtd", "upgrade06.xsl", 4, NULL },
/* 3 */ { 2, "pacemaker-0.7", "pacemaker-1.0.rng", NULL, 0, NULL },
/* 4 */ { 2, "pacemaker-1.0", "pacemaker-1.0.rng", NULL, 6, NULL },
/* 5 */ { 2, "pacemaker-1.1", "pacemaker-1.1.rng", NULL, 6, NULL },
/* 6 */ { 2, "pacemaker-1.2", "pacemaker-1.2.rng", NULL, 0, NULL },
/* 7 */ { 0, "none", NULL, NULL, 0, NULL },
};
static filter_t filter[] = {
{ 0, XML_ATTR_ORIGIN },
{ 0, XML_CIB_ATTR_WRITTEN },
{ 0, XML_ATTR_UPDATE_ORIG },
{ 0, XML_ATTR_UPDATE_CLIENT },
{ 0, XML_ATTR_UPDATE_USER },
};
/* *INDENT-ON* */
static int all_schemas = DIMOF(known_schemas);
static int max_schemas = DIMOF(known_schemas) - 2; /* skip back past 'none' */
#define CHUNK_SIZE 1024
#define buffer_print(buffer, max, offset, fmt, args...) do { \
int rc; \
rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
if(rc < 0) { \
crm_perror(LOG_ERR, "snprintf failed"); \
(buffer)[(offset)] = 0; \
return; \
} else if(rc >= ((max) - (offset))) { \
(max) = QB_MAX(CHUNK_SIZE, (max) * 2); \
(buffer) = realloc((buffer), (max) + 1); \
} else { \
offset += rc; \
break; \
} \
} while(1);
static void
insert_prefix(int options, char **buffer, int *offset, int *max, int depth)
{
if (options & xml_log_option_formatted) {
size_t spaces = 2 * depth;
if (spaces >= ((*max) - (*offset))) {
(*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
(*buffer) = realloc((*buffer), (*max) + 1);
}
memset((*buffer) + (*offset), ' ', spaces);
(*offset) += spaces;
}
}
static char *
get_schema_path(const char *file)
{
static const char *base = NULL;
if (base == NULL) {
base = getenv("PCMK_schema_directory");
}
if (base == NULL || strlen(base) == 0) {
base = CRM_DTD_DIRECTORY;
}
return crm_concat(base, file, '/');
}
int get_tag_name(const char *input, size_t offset, size_t max);
int get_attr_name(const char *input, size_t offset, size_t max);
int get_attr_value(const char *input, size_t offset, size_t max);
gboolean can_prune_leaf(xmlNode * xml_node);
void diff_filter_context(int context, int upper_bound, int lower_bound,
xmlNode * xml_node, xmlNode * parent);
int in_upper_context(int depth, int context, xmlNode * xml_node);
int write_file(const char *string, const char *filename);
int add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff);
static inline const char *
crm_attr_value(xmlAttr * attr)
{
if (attr == NULL || attr->children == NULL) {
return NULL;
}
return (const char *)attr->children->content;
}
static inline xmlAttr *
crm_first_attr(xmlNode * xml)
{
if (xml == NULL) {
return NULL;
}
return xml->properties;
}
xmlNode *
find_xml_node(xmlNode * root, const char *search_path, gboolean must_find)
{
xmlNode *a_child = NULL;
const char *name = "NULL";
if (root != NULL) {
name = crm_element_name(root);
}
if (search_path == NULL) {
crm_warn("Will never find <NULL>");
return NULL;
}
for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
if (strcmp((const char *)a_child->name, search_path) == 0) {
/* crm_trace("returning node (%s).", crm_element_name(a_child)); */
return a_child;
}
}
if (must_find) {
crm_warn("Could not find %s in %s.", search_path, name);
} else if (root != NULL) {
crm_trace("Could not find %s in %s.", search_path, name);
} else {
crm_trace("Could not find %s in <NULL>.", search_path);
}
return NULL;
}
xmlNode *
find_entity(xmlNode * parent, const char *node_name, const char *id)
{
xmlNode *a_child = NULL;
for (a_child = __xml_first_child(parent); a_child != NULL; a_child = __xml_next(a_child)) {
/* Uncertain if node_name == NULL check is strictly necessary here */
if (node_name == NULL || strcmp((const char *)a_child->name, node_name) == 0) {
if (id == NULL || strcmp(id, ID(a_child)) == 0) {
return a_child;
}
}
}
crm_trace("node <%s id=%s> not found in %s.", node_name, id, crm_element_name(parent));
return NULL;
}
void
copy_in_properties(xmlNode * target, xmlNode * src)
{
if (src == NULL) {
crm_warn("No node to copy properties from");
} else if (target == NULL) {
crm_err("No node to copy properties into");
} else {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(src); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
expand_plus_plus(target, p_name, p_value);
}
}
return;
}
void
fix_plus_plus_recursive(xmlNode * target)
{
/* TODO: Remove recursion and use xpath searches for value++ */
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(target); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
expand_plus_plus(target, p_name, p_value);
}
for (child = __xml_first_child(target); child != NULL; child = __xml_next(child)) {
fix_plus_plus_recursive(child);
}
}
void
expand_plus_plus(xmlNode * target, const char *name, const char *value)
{
int offset = 1;
int name_len = 0;
int int_value = 0;
int value_len = 0;
const char *old_value = NULL;
if (value == NULL || name == NULL) {
return;
}
old_value = crm_element_value(target, name);
if (old_value == NULL) {
/* if no previous value, set unexpanded */
goto set_unexpanded;
} else if (strstr(value, name) != value) {
goto set_unexpanded;
}
name_len = strlen(name);
value_len = strlen(value);
if (value_len < (name_len + 2)
|| value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
goto set_unexpanded;
}
/* if we are expanding ourselves,
* then no previous value was set and leave int_value as 0
*/
if (old_value != value) {
int_value = char2score(old_value);
}
if (value[name_len + 1] != '+') {
const char *offset_s = value + (name_len + 2);
offset = char2score(offset_s);
}
int_value += offset;
if (int_value > INFINITY) {
int_value = INFINITY;
}
crm_xml_add_int(target, name, int_value);
return;
set_unexpanded:
if (old_value == value) {
/* the old value is already set, nothing to do */
return;
}
crm_xml_add(target, name, value);
return;
}
xmlDoc *
getDocPtr(xmlNode * node)
{
xmlDoc *doc = NULL;
CRM_CHECK(node != NULL, return NULL);
doc = node->doc;
if (doc == NULL) {
doc = xmlNewDoc((const xmlChar *)"1.0");
xmlDocSetRootElement(doc, node);
xmlSetTreeDoc(node, doc);
}
return doc;
}
xmlNode *
add_node_copy(xmlNode * parent, xmlNode * src_node)
{
xmlNode *child = NULL;
xmlDoc *doc = getDocPtr(parent);
CRM_CHECK(src_node != NULL, return NULL);
child = xmlDocCopyNode(src_node, doc, 1);
xmlAddChild(parent, child);
return child;
}
int
add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
{
add_node_copy(parent, child);
free_xml(child);
return 1;
}
const char *
crm_xml_add(xmlNode * node, const char *name, const char *value)
{
xmlAttr *attr = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL, return NULL);
if (value == NULL) {
return NULL;
}
#if XML_PARANOIA_CHECKS
{
const char *old_value = NULL;
old_value = crm_element_value(node, name);
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, crm_err("Cannot reset %s with crm_xml_add(%s)", name, value);
return value);
}
#endif
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *)attr->children->content;
}
const char *
crm_xml_replace(xmlNode * node, const char *name, const char *value)
{
xmlAttr *attr = NULL;
const char *old_value = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL && name[0] != 0, return NULL);
old_value = crm_element_value(node, name);
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, return value);
if (old_value != NULL && value == NULL) {
xml_remove_prop(node, name);
return NULL;
} else if (value == NULL) {
return NULL;
}
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *)attr->children->content;
}
const char *
crm_xml_add_int(xmlNode * node, const char *name, int value)
{
char *number = crm_itoa(value);
const char *added = crm_xml_add(node, name, number);
free(number);
return added;
}
xmlNode *
create_xml_node(xmlNode * parent, const char *name)
{
xmlDoc *doc = NULL;
xmlNode *node = NULL;
if (name == NULL || name[0] == 0) {
return NULL;
}
if (parent == NULL) {
doc = xmlNewDoc((const xmlChar *)"1.0");
node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
xmlDocSetRootElement(doc, node);
} else {
doc = getDocPtr(parent);
node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
xmlAddChild(parent, node);
}
return node;
}
void
free_xml(xmlNode * child)
{
if (child != NULL) {
xmlNode *top = NULL;
xmlDoc *doc = child->doc;
if (doc != NULL) {
top = xmlDocGetRootElement(doc);
}
if (doc != NULL && top == child) {
/* Free everything */
xmlFreeDoc(doc);
} else {
/* Free this particular subtree
* Make sure to unlink it from the parent first
*/
xmlUnlinkNode(child);
xmlFreeNode(child);
}
}
}
xmlNode *
copy_xml(xmlNode * src)
{
xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
xmlNode *copy = xmlDocCopyNode(src, doc, 1);
xmlDocSetRootElement(doc, copy);
xmlSetTreeDoc(copy, doc);
return copy;
}
static void
crm_xml_err(void *ctx, const char *msg, ...)
G_GNUC_PRINTF(2, 3);
int
write_file(const char *string, const char *filename)
{
int rc = 0;
FILE *file_output_strm = NULL;
CRM_CHECK(filename != NULL, return -1);
if (string == NULL) {
crm_err("Cannot write NULL to %s", filename);
return -1;
}
file_output_strm = fopen(filename, "w");
if (file_output_strm == NULL) {
crm_perror(LOG_ERR, "Cannot open %s for writing", filename);
return -1;
}
rc = fprintf(file_output_strm, "%s", string);
if (rc < 0) {
crm_perror(LOG_ERR, "Cannot write output to %s", filename);
}
if (fflush(file_output_strm) != 0) {
crm_perror(LOG_ERR, "fflush for %s failed:", filename);
rc = -1;
}
if (fsync(fileno(file_output_strm)) < 0) {
crm_perror(LOG_ERR, "fsync for %s failed:", filename);
rc = -1;
}
fclose(file_output_strm);
return rc;
}
static void
crm_xml_err(void *ctx, const char *msg, ...)
{
int len = 0;
va_list args;
char *buf = NULL;
static int buffer_len = 0;
static char *buffer = NULL;
va_start(args, msg);
len = vasprintf(&buf, msg, args);
if (strchr(buf, '\n')) {
buf[len - 1] = 0;
if (buffer) {
crm_err("XML Error: %s%s", buffer, buf);
free(buffer);
} else {
crm_err("XML Error: %s", buf);
}
buffer = NULL;
buffer_len = 0;
} else if (buffer == NULL) {
buffer_len = len;
buffer = buf;
buf = NULL;
} else {
buffer = realloc(buffer, 1 + buffer_len + len);
memcpy(buffer + buffer_len, buf, len);
buffer_len += len;
buffer[buffer_len] = 0;
}
va_end(args);
free(buf);
}
xmlNode *
string2xml(const char *input)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
xmlParserCtxtPtr ctxt = NULL;
xmlErrorPtr last_error = NULL;
if (input == NULL) {
crm_err("Can't parse NULL input");
return NULL;
}
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
/* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, crm_xml_err);
/* initGenericErrorDefaultFunc(crm_xml_err); */
output =
xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL,
XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
if (output) {
xml = xmlDocGetRootElement(output);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "Cannot parse an empty string", TRUE,
TRUE);
} else if (last_error->code != XML_ERR_DOCUMENT_END) {
crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
input);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
} else {
int len = strlen(input);
int lpc = 0;
while(lpc < len) {
crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
lpc += 80;
}
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "String parsing error", TRUE, TRUE);
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
xmlNode *
stdin2xml(void)
{
size_t data_length = 0;
size_t read_chars = 0;
char *xml_buffer = NULL;
xmlNode *xml_obj = NULL;
do {
xml_buffer = realloc(xml_buffer, XML_BUFFER_SIZE + data_length + 1);
read_chars = fread(xml_buffer + data_length, 1, XML_BUFFER_SIZE, stdin);
data_length += read_chars;
} while (read_chars > 0);
if (data_length == 0) {
crm_warn("No XML supplied on stdin");
free(xml_buffer);
return NULL;
}
xml_buffer[data_length] = '\0';
xml_obj = string2xml(xml_buffer);
free(xml_buffer);
crm_log_xml_trace(xml_obj, "Created fragment");
return xml_obj;
}
static char *
decompress_file(const char *filename)
{
char *buffer = NULL;
#if HAVE_BZLIB_H
int rc = 0;
size_t length = 0, read_len = 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);
if (rc != BZ_OK) {
BZ2_bzReadClose(&rc, bz_file);
return NULL;
}
rc = BZ_OK;
while (rc == BZ_OK) {
buffer = realloc(buffer, XML_BUFFER_SIZE + length + 1);
read_len = BZ2_bzRead(&rc, bz_file, buffer + length, XML_BUFFER_SIZE);
crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
if (rc == BZ_OK || rc == BZ_STREAM_END) {
length += read_len;
}
}
buffer[length] = '\0';
read_len = length;
if (rc != BZ_STREAM_END) {
crm_err("Couldnt read compressed xml from file");
free(buffer);
buffer = NULL;
}
BZ2_bzReadClose(&rc, bz_file);
fclose(input);
#else
crm_err("Cannot read compressed files:" " bzlib was not available at compile time");
#endif
return buffer;
}
void
strip_text_nodes(xmlNode * xml)
{
xmlNode *iter = xml->children;
while (iter) {
xmlNode *next = iter->next;
switch (iter->type) {
case XML_TEXT_NODE:
/* Remove it */
xmlUnlinkNode(iter);
xmlFreeNode(iter);
break;
case XML_ELEMENT_NODE:
/* Search it */
strip_text_nodes(iter);
break;
default:
/* Leave it */
break;
}
iter = next;
}
}
xmlNode *
filename2xml(const char *filename)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
const char *match = NULL;
xmlParserCtxtPtr ctxt = NULL;
xmlErrorPtr last_error = NULL;
static int xml_options = XML_PARSE_NOBLANKS | XML_PARSE_RECOVER;
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
/* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, crm_xml_err);
/* initGenericErrorDefaultFunc(crm_xml_err); */
if (filename) {
match = strstr(filename, ".bz2");
}
if (filename == NULL) {
/* STDIN_FILENO == fileno(stdin) */
output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, xml_options);
} else if (match == NULL || match[4] != 0) {
output = xmlCtxtReadFile(ctxt, filename, NULL, xml_options);
} else {
char *input = decompress_file(filename);
output = xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL, xml_options);
free(input);
}
if (output && (xml = xmlDocGetRootElement(output))) {
strip_text_nodes(xml);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error && last_error->code != XML_ERR_OK) {
crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
static int
write_xml_stream(xmlNode * xml_node, const char *filename, FILE * stream, gboolean compress)
{
int res = 0;
time_t now;
char *buffer = NULL;
char *now_str = NULL;
unsigned int out = 0;
static mode_t cib_mode = S_IRUSR | S_IWUSR;
CRM_CHECK(stream != NULL, return -1);
crm_trace("Writing XML out to %s", filename);
if (xml_node == NULL) {
crm_err("Cannot write NULL to %s", filename);
fclose(stream);
return -1;
}
/* establish the correct permissions */
fchmod(fileno(stream), cib_mode);
crm_log_xml_trace(xml_node, "Writing out");
now = time(NULL);
now_str = ctime(&now);
now_str[24] = EOS; /* replace the newline */
crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str);
buffer = dump_xml_formatted(xml_node);
CRM_CHECK(buffer != NULL && strlen(buffer) > 0, crm_log_xml_warn(xml_node, "dump:failed");
goto bail);
if (compress) {
#if HAVE_BZLIB_H
int rc = BZ_OK;
unsigned int in = 0;
BZFILE *bz_file = NULL;
bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
if (rc != BZ_OK) {
crm_err("bzWriteOpen failed: %d", rc);
} else {
BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
if (rc != BZ_OK) {
crm_err("bzWrite() failed: %d", rc);
}
}
if (rc == BZ_OK) {
BZ2_bzWriteClose(&rc, bz_file, 0, &in, &out);
if (rc != BZ_OK) {
crm_err("bzWriteClose() failed: %d", rc);
out = -1;
} else {
crm_trace("%s: In: %d, out: %d", filename, in, out);
}
}
#else
crm_err("Cannot write compressed files:" " bzlib was not available at compile time");
#endif
}
if (out <= 0) {
res = fprintf(stream, "%s", buffer);
if (res < 0) {
crm_perror(LOG_ERR, "Cannot write output to %s", filename);
goto bail;
}
}
bail:
if (fflush(stream) != 0) {
crm_perror(LOG_ERR, "fflush for %s failed:", filename);
res = -1;
}
if (fsync(fileno(stream)) < 0) {
crm_perror(LOG_ERR, "fsync for %s failed:", filename);
res = -1;
}
fclose(stream);
crm_trace("Saved %d bytes to the Cib as XML", res);
free(buffer);
return res;
}
int
write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
{
FILE *stream = NULL;
CRM_CHECK(fd > 0, return -1);
stream = fdopen(fd, "w");
return write_xml_stream(xml_node, filename, stream, compress);
}
int
write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
{
FILE *stream = fopen(filename, "w");
return write_xml_stream(xml_node, filename, stream, compress);
}
xmlNode *
get_message_xml(xmlNode * msg, const char *field)
{
xmlNode *tmp = first_named_child(msg, field);
return __xml_first_child(tmp);
}
gboolean
add_message_xml(xmlNode * msg, const char *field, xmlNode * xml)
{
xmlNode *holder = create_xml_node(msg, field);
add_node_copy(holder, xml);
return TRUE;
}
static char *
crm_xml_escape_shuffle(char *text, int start, int *length, const char *replace)
{
int lpc;
int offset = strlen(replace) - 1; /* We have space for 1 char already */
*length += offset;
text = realloc(text, *length);
for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
text[lpc] = text[lpc - offset];
}
memcpy(text + start, replace, offset + 1);
return text;
}
static char *
crm_xml_escape(const char *text)
{
int index;
int changes = 0;
int length = 1 + strlen(text);
char *copy = strdup(text);
/*
* When xmlCtxtReadDoc() parses < and friends in a
* value, it converts them to their human readable
* form.
*
* If one uses xmlNodeDump() to convert it back to a
* string, all is well, because special characters are
* converted back to their escape sequences.
*
* However xmlNodeDump() is randomly dog slow, even with the same
* input. So we need to replicate the escapeing in our custom
* version so that the result can be re-parsed by xmlCtxtReadDoc()
* when necessary.
*/
for (index = 0; index < length; index++) {
switch (copy[index]) {
case 0:
break;
case '<':
copy = crm_xml_escape_shuffle(copy, index, &length, "<");
changes++;
break;
case '>':
copy = crm_xml_escape_shuffle(copy, index, &length, ">");
changes++;
break;
case '"':
copy = crm_xml_escape_shuffle(copy, index, &length, """);
changes++;
break;
case '\'':
copy = crm_xml_escape_shuffle(copy, index, &length, "'");
changes++;
break;
case '&':
copy = crm_xml_escape_shuffle(copy, index, &length, "&");
changes++;
break;
+ case '\t':
+ /* Might as well just expand to a few spaces... */
+ copy = crm_xml_escape_shuffle(copy, index, &length, " ");
+ changes++;
+ break;
+ case '\n':
+ /* crm_trace("Convert: \\%.3o", copy[index]); */
+ copy = crm_xml_escape_shuffle(copy, index, &length, "\\n");
+ changes++;
+ break;
+ case '\r':
+ copy = crm_xml_escape_shuffle(copy, index, &length, "\\r");
+ changes++;
+ break;
+ /* For debugging...
+ case '\\':
+ crm_trace("Passthrough: \\%c", copy[index+1]);
+ break;
+ */
default:
/* Check for and replace non-printing characters with their octal equivalent */
if(copy[index] < ' ' || copy[index] > '~') {
char *replace = g_strdup_printf("\\%.3o", copy[index]);
/* crm_trace("Convert to octal: \\%.3o", copy[index]); */
copy = crm_xml_escape_shuffle(copy, index, &length, replace);
free(replace);
changes++;
}
}
}
if (changes) {
crm_trace("Dumped '%s'", copy);
}
return copy;
}
static inline void
dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
{
char *p_value = NULL;
const char *p_name = (const char *)attr->name;
if (attr == NULL || attr->children == NULL) {
return;
}
p_value = crm_xml_escape((const char *)attr->children->content);
buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
free(p_value);
}
void
log_data_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
xmlNode *a_child = NULL;
int max = 0;
int offset = 0;
char *buffer = NULL;
char *prefix_m = NULL;
xmlAttrPtr pIter = NULL;
const char *name = NULL;
const char *hidden = NULL;
if (prefix == NULL) {
prefix = "";
}
/* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
if (data == NULL) {
do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
"No data to dump as XML");
return;
} else if (is_set(options, xml_log_option_diff_short)
&& is_not_set(options, xml_log_option_diff_all)) {
/* Still searching for the actual change */
for (a_child = __xml_first_child(data); a_child != NULL; a_child = __xml_next(a_child)) {
log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
}
return;
}
name = crm_element_name(data);
if (is_set(options, xml_log_option_formatted)) {
if (is_set(options, xml_log_option_diff_plus)
&& (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
options |= xml_log_option_diff_all;
prefix_m = strdup(prefix);
prefix_m[1] = '+';
prefix = prefix_m;
} else if (is_set(options, xml_log_option_diff_minus)
&& (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
options |= xml_log_option_diff_all;
prefix_m = strdup(prefix);
prefix_m[1] = '-';
prefix = prefix_m;
}
}
insert_prefix(options, &buffer, &offset, &max, depth);
buffer_print(buffer, max, offset, "<%s", name);
hidden = crm_element_value(data, "hidden");
for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
char *p_copy = NULL;
if ((is_set(options, xml_log_option_diff_plus)
|| is_set(options, xml_log_option_diff_minus))
&& strcmp(XML_DIFF_MARKER, p_name) == 0) {
continue;
} else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
p_copy = strdup("*****");
} else {
p_copy = crm_xml_escape(p_value);
}
buffer_print(buffer, max, offset, " %s=\"%s\"", p_name, p_copy);
free(p_copy);
}
if (xml_has_children(data)) {
buffer_print(buffer, max, offset, ">");
} else {
buffer_print(buffer, max, offset, "/>");
}
do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
if (data->children) {
offset = 0;
max = 0;
free(buffer);
buffer = NULL;
for (a_child = __xml_first_child(data); a_child != NULL; a_child = __xml_next(a_child)) {
log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
}
insert_prefix(options, &buffer, &offset, &max, depth);
buffer_print(buffer, max, offset, "</%s>", name);
do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
}
free(prefix_m);
free(buffer);
}
static void
dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
{
int lpc;
xmlAttrPtr xIter = NULL;
static int filter_len = DIMOF(filter);
for (lpc = 0; options && lpc < filter_len; lpc++) {
filter[lpc].found = FALSE;
}
for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
bool skip = FALSE;
const char *p_name = (const char *)xIter->name;
for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
if (filter[lpc].found == FALSE && strcmp(p_name, filter[lpc].string) == 0) {
filter[lpc].found = TRUE;
skip = TRUE;
break;
}
}
if (skip == FALSE) {
dump_xml_attr(xIter, options, buffer, offset, max);
}
}
}
static void
dump_xml(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
const char *name = NULL;
CRM_ASSERT(max != NULL);
CRM_ASSERT(offset != NULL);
CRM_ASSERT(buffer != NULL);
if (data == NULL) {
crm_trace("Nothing to dump");
return;
}
if (*buffer == NULL) {
*offset = 0;
*max = 0;
}
name = crm_element_name(data);
CRM_ASSERT(name != NULL);
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "<%s", name);
if (options & xml_log_option_filtered) {
dump_filtered_xml(data, options, buffer, offset, max);
} else {
#if 1
xmlAttrPtr xIter = NULL;
for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
dump_xml_attr(xIter, options, buffer, offset, max);
}
#else
/* Turning this code on also changes the PE tests for some reason
* (not just newlines). Figure out why before considering to
* enable this permanently.
*
* It exists to help debug slowness in xmlNodeDump() and
* potentially if we ever want to go back to it.
*
* In theory its a good idea (reuse) but our custom version does
* better for the filtered case and avoids the final strdup() for
* everything
*/
time_t now, next;
xmlDoc *doc = NULL;
xmlBuffer *xml_buffer = NULL;
*buffer = NULL;
doc = getDocPtr(data);
/* doc will only be NULL if data is */
CRM_CHECK(doc != NULL, return);
now = time(NULL);
xml_buffer = xmlBufferCreate();
CRM_ASSERT(xml_buffer != NULL);
/* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
* realloc()s and it can take upwards of 18 seconds (yes, seconds)
* to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
* less than 1 second.
*
* We could also use xmlBufferCreateSize() to start with a
* sane-ish initial size and avoid the first few doubles.
*/
xmlBufferSetAllocationScheme(xml_buffer, XML_BUFFER_ALLOC_DOUBLEIT);
*max = xmlNodeDump(xml_buffer, doc, data, 0, (options & xml_log_option_formatted));
if (*max > 0) {
*buffer = strdup((char *)xml_buffer->content);
}
next = time(NULL);
if ((now + 1) < next) {
crm_log_xml_trace(data, "Long time");
crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
}
xmlBufferFree(xml_buffer);
return;
#endif
}
if (data->children == NULL) {
buffer_print(*buffer, *max, *offset, "/>");
} else {
buffer_print(*buffer, *max, *offset, ">");
}
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
if (data->children) {
xmlNode *xChild = NULL;
for (xChild = __xml_first_child(data); xChild != NULL; xChild = __xml_next(xChild)) {
dump_xml(xChild, options, buffer, offset, max, depth + 1);
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "</%s>", name);
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
}
static void
fix_digest_buffer(char **buffer, int *offset, int *max, char c)
{
buffer_print(*buffer, *max, *offset, "%c", c);
}
static char *
dump_xml_for_digest(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
/* for compatability with the old result which is used for v1 digests */
fix_digest_buffer(&buffer, &offset, &max, ' ');
dump_xml(an_xml_node, 0, &buffer, &offset, &max, 0);
fix_digest_buffer(&buffer, &offset, &max, '\n');
return buffer;
}
char *
dump_xml_formatted(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
dump_xml(an_xml_node, xml_log_option_formatted, &buffer, &offset, &max, 0);
return buffer;
}
char *
dump_xml_unformatted(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
dump_xml(an_xml_node, 0, &buffer, &offset, &max, 0);
return buffer;
}
gboolean
xml_has_children(const xmlNode * xml_root)
{
if (xml_root != NULL && xml_root->children != NULL) {
return TRUE;
}
return FALSE;
}
int
crm_element_value_int(xmlNode * data, const char *name, int *dest)
{
const char *value = crm_element_value(data, name);
CRM_CHECK(dest != NULL, return -1);
if (value) {
*dest = crm_int_helper(value, NULL);
return 0;
}
return -1;
}
int
crm_element_value_const_int(const xmlNode * data, const char *name, int *dest)
{
return crm_element_value_int((xmlNode *) data, name, dest);
}
const char *
crm_element_value_const(const xmlNode * data, const char *name)
{
return crm_element_value((xmlNode *) data, name);
}
char *
crm_element_value_copy(xmlNode * data, const char *name)
{
char *value_copy = NULL;
const char *value = crm_element_value(data, name);
if (value != NULL) {
value_copy = strdup(value);
}
return value_copy;
}
void
xml_remove_prop(xmlNode * obj, const char *name)
{
xmlUnsetProp(obj, (const xmlChar *)name);
}
void
log_xml_diff(unsigned int log_level, xmlNode * diff, const char *function)
{
xmlNode *child = NULL;
xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
gboolean is_first = TRUE;
int options = xml_log_option_formatted;
static struct qb_log_callsite *diff_cs = NULL;
if (diff_cs == NULL) {
diff_cs = qb_log_callsite_get(function, __FILE__, "xml-diff", log_level, __LINE__, 0);
}
if (crm_is_callsite_active(diff_cs, log_level, 0) == FALSE) {
return;
}
if (log_level < LOG_DEBUG || function == NULL) {
options |= xml_log_option_diff_short;
}
for (child = __xml_first_child(removed); child != NULL; child = __xml_next(child)) {
log_data_element(log_level, __FILE__, function, __LINE__, "- ", child, 0,
options | xml_log_option_diff_minus);
if (is_first) {
is_first = FALSE;
} else {
do_crm_log(log_level, " --- ");
}
}
is_first = TRUE;
for (child = __xml_first_child(added); child != NULL; child = __xml_next(child)) {
log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child, 0,
options | xml_log_option_diff_plus);
if (is_first) {
is_first = FALSE;
} else {
do_crm_log(log_level, " +++ ");
}
}
}
void
purge_diff_markers(xmlNode * a_node)
{
xmlNode *child = NULL;
CRM_CHECK(a_node != NULL, return);
xml_remove_prop(a_node, XML_DIFF_MARKER);
for (child = __xml_first_child(a_node); child != NULL; child = __xml_next(child)) {
purge_diff_markers(child);
}
}
static void
save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
{
char *f = NULL;
FILE *st = NULL;
xmlDoc *doc = getDocPtr(xml);
xmlBuffer *xml_buffer = xmlBufferCreate();
if (filename == NULL) {
char *uuid = crm_generate_uuid();
f = g_strdup_printf("/tmp/%s", uuid);
filename = f;
free(uuid);
}
crm_info("Saving %s to %s", desc, filename);
xmlNodeDump(xml_buffer, doc, xml, 0, FALSE);
st = fopen(filename, "w");
if (st) {
fprintf(st, "%s", xml_buffer->content);
/* fflush(st); */
/* fsync(fileno(st)); */
fclose(st);
}
xmlBufferFree(xml_buffer);
g_free(f);
}
gboolean
apply_xml_diff(xmlNode * old, xmlNode * diff, xmlNode ** new)
{
gboolean result = TRUE;
int root_nodes_seen = 0;
static struct qb_log_callsite *digest_cs = NULL;
const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
CRM_CHECK(new != NULL, return FALSE);
if (digest_cs == NULL) {
digest_cs =
qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
crm_trace("Substraction Phase");
for (child_diff = __xml_first_child(removed); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, result = FALSE);
if (root_nodes_seen == 0) {
*new = subtract_xml_object(NULL, old, child_diff, FALSE, NULL, NULL);
}
root_nodes_seen++;
}
if (root_nodes_seen == 0) {
*new = copy_xml(old);
} else if (root_nodes_seen > 1) {
crm_err("(-) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
result = FALSE;
}
root_nodes_seen = 0;
crm_trace("Addition Phase");
if (result) {
xmlNode *child_diff = NULL;
for (child_diff = __xml_first_child(added); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, result = FALSE);
if (root_nodes_seen == 0) {
add_xml_object(NULL, *new, child_diff, TRUE);
}
root_nodes_seen++;
}
}
if (root_nodes_seen > 1) {
crm_err("(+) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
result = FALSE;
} else if (result && digest) {
char *new_digest = NULL;
purge_diff_markers(*new); /* Purge now so the diff is ok */
new_digest = calculate_xml_versioned_digest(*new, FALSE, TRUE, version);
if (safe_str_neq(new_digest, digest)) {
crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest);
result = FALSE;
crm_trace("%p %0.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
if (digest_cs && digest_cs->targets) {
save_xml_to_file(old, "diff:original", NULL);
save_xml_to_file(diff, "diff:input", NULL);
save_xml_to_file(*new, "diff:new", NULL);
}
} else {
crm_trace("Digest matched: expected %s, calculated %s", digest, new_digest);
}
free(new_digest);
}
return result;
}
xmlNode *
diff_xml_object(xmlNode * old, xmlNode * new, gboolean suppress)
{
xmlNode *tmp1 = NULL;
xmlNode *diff = create_xml_node(NULL, "diff");
xmlNode *removed = create_xml_node(diff, "diff-removed");
xmlNode *added = create_xml_node(diff, "diff-added");
crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
if (added->children == NULL && removed->children == NULL) {
free_xml(diff);
diff = NULL;
}
return diff;
}
gboolean
can_prune_leaf(xmlNode * xml_node)
{
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
gboolean can_prune = TRUE;
for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
if (strcmp(p_name, XML_ATTR_ID) == 0) {
continue;
}
can_prune = FALSE;
}
for (child = __xml_first_child(xml_node); child != NULL; child = __xml_next(child)) {
if (can_prune_leaf(child)) {
free_xml(child);
} else {
can_prune = FALSE;
}
}
return can_prune;
}
void
diff_filter_context(int context, int upper_bound, int lower_bound,
xmlNode * xml_node, xmlNode * parent)
{
xmlNode *us = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
xmlNode *new_parent = parent;
const char *name = crm_element_name(xml_node);
CRM_CHECK(xml_node != NULL && name != NULL, return);
us = create_xml_node(parent, name);
for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
lower_bound = context;
crm_xml_add(us, p_name, p_value);
}
if (lower_bound >= 0 || upper_bound >= 0) {
crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
new_parent = us;
} else {
upper_bound = in_upper_context(0, context, xml_node);
if (upper_bound >= 0) {
crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
new_parent = us;
} else {
free_xml(us);
us = NULL;
}
}
for (child = __xml_first_child(us); child != NULL; child = __xml_next(child)) {
diff_filter_context(context, upper_bound - 1, lower_bound - 1, child, new_parent);
}
}
int
in_upper_context(int depth, int context, xmlNode * xml_node)
{
if (context == 0) {
return 0;
}
if (xml_node->properties) {
return depth;
} else if (depth < context) {
xmlNode *child = NULL;
for (child = __xml_first_child(xml_node); child != NULL; child = __xml_next(child)) {
if (in_upper_context(depth + 1, context, child)) {
return depth;
}
}
}
return 0;
}
xmlNode *
subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean full, gboolean * changed, const char *marker)
{
gboolean dummy = FALSE;
gboolean skip = FALSE;
xmlNode *diff = NULL;
xmlNode *right_child = NULL;
xmlNode *left_child = NULL;
xmlAttrPtr xIter = NULL;
const char *id = NULL;
const char *name = NULL;
const char *value = NULL;
const char *right_val = NULL;
int lpc = 0;
static int filter_len = DIMOF(filter);
if (changed == NULL) {
changed = &dummy;
}
if (left == NULL) {
return NULL;
}
id = ID(left);
if (right == NULL) {
xmlNode *deleted = NULL;
crm_trace("Processing <%s id=%s> (complete copy)", crm_element_name(left), id);
deleted = add_node_copy(parent, left);
crm_xml_add(deleted, XML_DIFF_MARKER, marker);
*changed = TRUE;
return deleted;
}
name = crm_element_name(left);
CRM_CHECK(name != NULL, return NULL);
/* check for XML_DIFF_MARKER in a child */
value = crm_element_value(right, XML_DIFF_MARKER);
if (value != NULL && strcmp(value, "removed:top") == 0) {
crm_trace("We are the root of the deletion: %s.id=%s", name, id);
*changed = TRUE;
free_xml(diff);
return NULL;
}
/* Avoiding creating the full heirarchy would save even more work here */
diff = create_xml_node(parent, name);
/* Reset filter */
for (lpc = 0; lpc < filter_len; lpc++) {
filter[lpc].found = FALSE;
}
/* changes to child objects */
for (left_child = __xml_first_child(left); left_child != NULL;
left_child = __xml_next(left_child)) {
gboolean child_changed = FALSE;
right_child = find_entity(right, crm_element_name(left_child), ID(left_child));
subtract_xml_object(diff, left_child, right_child, full, &child_changed, marker);
if (child_changed) {
*changed = TRUE;
}
}
if (*changed == FALSE) {
/* Nothing to do */
} else if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
/* We already have everything we need... */
goto done;
} else if (id) {
xmlSetProp(diff, (const xmlChar *)XML_ATTR_ID, (const xmlChar *)id);
}
/* changes to name/value pairs */
for (xIter = crm_first_attr(left); xIter != NULL; xIter = xIter->next) {
const char *prop_name = (const char *)xIter->name;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
skip = FALSE;
for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
if (filter[lpc].found == FALSE && strcmp(prop_name, filter[lpc].string) == 0) {
filter[lpc].found = TRUE;
skip = TRUE;
break;
}
}
if (skip) {
continue;
}
right_val = crm_element_value(right, prop_name);
if (right_val == NULL) {
/* new */
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
break;
} else {
const char *left_value = crm_element_value(left, prop_name);
xmlSetProp(diff, (const xmlChar *)prop_name, (const xmlChar *)value);
crm_xml_add(diff, prop_name, left_value);
}
} else {
/* Only now do we need the left value */
const char *left_value = crm_element_value(left, prop_name);
if (strcmp(left_value, right_val) == 0) {
/* unchanged */
} else {
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
crm_element_name(left), id);
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
break;
} else {
crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
prop_name, left_value, right_val, crm_element_name(left), id);
crm_xml_add(diff, prop_name, left_value);
}
}
}
}
if (*changed == FALSE) {
free_xml(diff);
return NULL;
} else if (full == FALSE && id) {
crm_xml_add(diff, XML_ATTR_ID, id);
}
done:
return diff;
}
int
add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff)
{
xmlNode *a_child = NULL;
const char *object_id = NULL;
const char *object_name = NULL;
#if XML_PARSE_DEBUG
crm_log_xml_trace("update:", update);
crm_log_xml_trace("target:", target);
#endif
CRM_CHECK(update != NULL, return 0);
object_name = crm_element_name(update);
object_id = ID(update);
CRM_CHECK(object_name != NULL, return 0);
if (target == NULL && object_id == NULL) {
/* placeholder object */
target = find_xml_node(parent, object_name, FALSE);
} else if (target == NULL) {
target = find_entity(parent, object_name, object_id);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
CRM_CHECK(target != NULL, return 0);
#if XML_PARSER_DEBUG
crm_trace("Added <%s%s%s/>", crm_str(object_name),
object_id ? " id=" : "", object_id ? object_id : "");
} else {
crm_trace("Found node <%s%s%s/> to update",
crm_str(object_name), object_id ? " id=" : "", object_id ? object_id : "");
#endif
}
if (as_diff == FALSE) {
/* So that expand_plus_plus() gets called */
copy_in_properties(target, update);
} else {
/* No need for expand_plus_plus(), just raw speed */
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
/* Remove it first so the ordering of the update is preserved */
xmlUnsetProp(target, (const xmlChar *)p_name);
xmlSetProp(target, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
}
for (a_child = __xml_first_child(update); a_child != NULL; a_child = __xml_next(a_child)) {
#if XML_PARSER_DEBUG
crm_trace("Updating child <%s id=%s>", crm_element_name(a_child), ID(a_child));
#endif
add_xml_object(target, NULL, a_child, as_diff);
}
#if XML_PARSER_DEBUG
crm_trace("Finished with <%s id=%s>", crm_str(object_name), crm_str(object_id));
#endif
return 0;
}
gboolean
update_xml_child(xmlNode * child, xmlNode * to_update)
{
gboolean can_update = TRUE;
xmlNode *child_of_child = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(to_update != NULL, return FALSE);
if (safe_str_neq(crm_element_name(to_update), crm_element_name(child))) {
can_update = FALSE;
} else if (safe_str_neq(ID(to_update), ID(child))) {
can_update = FALSE;
} else if (can_update) {
#if XML_PARSER_DEBUG
crm_log_xml_trace(child, "Update match found...");
#endif
add_xml_object(NULL, child, to_update, FALSE);
}
for (child_of_child = __xml_first_child(child); child_of_child != NULL;
child_of_child = __xml_next(child_of_child)) {
/* only update the first one */
if (can_update) {
break;
}
can_update = update_xml_child(child_of_child, to_update);
}
return can_update;
}
int
find_xml_children(xmlNode ** children, xmlNode * root,
const char *tag, const char *field, const char *value, gboolean search_matches)
{
int match_found = 0;
CRM_CHECK(root != NULL, return FALSE);
CRM_CHECK(children != NULL, return FALSE);
if (tag != NULL && safe_str_neq(tag, crm_element_name(root))) {
} else if (value != NULL && safe_str_neq(value, crm_element_value(root, field))) {
} else {
if (*children == NULL) {
*children = create_xml_node(NULL, __FUNCTION__);
}
add_node_copy(*children, root);
match_found = 1;
}
if (search_matches || match_found == 0) {
xmlNode *child = NULL;
for (child = __xml_first_child(root); child != NULL; child = __xml_next(child)) {
match_found += find_xml_children(children, child, tag, field, value, search_matches);
}
}
return match_found;
}
gboolean
replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
{
gboolean can_delete = FALSE;
xmlNode *child_of_child = NULL;
const char *up_id = NULL;
const char *child_id = NULL;
const char *right_val = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(update != NULL, return FALSE);
up_id = ID(update);
child_id = ID(child);
if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
can_delete = TRUE;
}
if (safe_str_neq(crm_element_name(update), crm_element_name(child))) {
can_delete = FALSE;
}
if (can_delete && delete_only) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
right_val = crm_element_value(child, p_name);
if (safe_str_neq(p_value, right_val)) {
can_delete = FALSE;
}
}
}
if (can_delete && parent != NULL) {
crm_log_xml_trace(child, "Delete match found...");
if (delete_only || update == NULL) {
free_xml(child);
} else {
xmlNode *tmp = copy_xml(update);
xmlDoc *doc = tmp->doc;
xmlNode *old = xmlReplaceNode(child, tmp);
free_xml(old);
xmlDocSetRootElement(doc, NULL);
xmlFreeDoc(doc);
}
child = NULL;
return TRUE;
} else if (can_delete) {
crm_log_xml_debug(child, "Cannot delete the search root");
can_delete = FALSE;
}
child_of_child = __xml_first_child(child);
while (child_of_child) {
xmlNode *next = __xml_next(child_of_child);
can_delete = replace_xml_child(child, child_of_child, update, delete_only);
/* only delete the first one */
if (can_delete) {
child_of_child = NULL;
} else {
child_of_child = next;
}
}
return can_delete;
}
void
hash2nvpair(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
xmlNode *xml_child = create_xml_node(xml_node, XML_CIB_TAG_NVPAIR);
crm_xml_add(xml_child, XML_ATTR_ID, name);
crm_xml_add(xml_child, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(xml_child, XML_NVPAIR_ATTR_VALUE, s_value);
crm_trace("dumped: name=%s value=%s", name, s_value);
}
void
hash2smartfield(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (isdigit(name[0])) {
xmlNode *tmp = create_xml_node(xml_node, XML_TAG_PARAM);
crm_xml_add(tmp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(tmp, XML_NVPAIR_ATTR_VALUE, s_value);
} else if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
crm_trace("dumped: %s=%s", name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
void
hash2field(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
void
hash2metafield(gpointer key, gpointer value, gpointer user_data)
{
char *crm_name = NULL;
if (key == NULL || value == NULL) {
return;
} else if (((char *)key)[0] == '#') {
return;
} else if (strstr(key, ":")) {
return;
}
crm_name = crm_meta_name(key);
hash2field(crm_name, value, user_data);
free(crm_name);
}
GHashTable *
xml2list(xmlNode * parent)
{
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
xmlNode *nvpair_list = NULL;
GHashTable *nvpair_hash = g_hash_table_new_full(crm_str_hash, g_str_equal,
g_hash_destroy_str, g_hash_destroy_str);
CRM_CHECK(parent != NULL, return nvpair_hash);
nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE);
if (nvpair_list == NULL) {
crm_trace("No attributes in %s", crm_element_name(parent));
crm_log_xml_trace(parent, "No attributes for resource op");
}
crm_log_xml_trace(nvpair_list, "Unpacking");
for (pIter = crm_first_attr(nvpair_list); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
crm_trace("Added %s=%s", p_name, p_value);
g_hash_table_insert(nvpair_hash, strdup(p_name), strdup(p_value));
}
for (child = __xml_first_child(nvpair_list); child != NULL; child = __xml_next(child)) {
if (strcmp((const char *)child->name, XML_TAG_PARAM) == 0) {
const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
crm_trace("Added %s=%s", key, value);
if (key != NULL && value != NULL) {
g_hash_table_insert(nvpair_hash, strdup(key), strdup(value));
}
}
}
return nvpair_hash;
}
typedef struct name_value_s {
const char *name;
const void *value;
} name_value_t;
static gint
sort_pairs(gconstpointer a, gconstpointer b)
{
int rc = 0;
const name_value_t *pair_a = a;
const name_value_t *pair_b = b;
CRM_ASSERT(a != NULL);
CRM_ASSERT(pair_a->name != NULL);
CRM_ASSERT(b != NULL);
CRM_ASSERT(pair_b->name != NULL);
rc = strcmp(pair_a->name, pair_b->name);
if (rc < 0) {
return -1;
} else if (rc > 0) {
return 1;
}
return 0;
}
static void
dump_pair(gpointer data, gpointer user_data)
{
name_value_t *pair = data;
xmlNode *parent = user_data;
crm_xml_add(parent, pair->name, pair->value);
}
xmlNode *
sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive)
{
xmlNode *child = NULL;
GListPtr sorted = NULL;
GListPtr unsorted = NULL;
name_value_t *pair = NULL;
xmlNode *result = NULL;
const char *name = NULL;
xmlAttrPtr pIter = NULL;
CRM_CHECK(input != NULL, return NULL);
name = crm_element_name(input);
CRM_CHECK(name != NULL, return NULL);
result = create_xml_node(parent, name);
for (pIter = crm_first_attr(input); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
pair = calloc(1, sizeof(name_value_t));
pair->name = p_name;
pair->value = p_value;
unsorted = g_list_prepend(unsorted, pair);
pair = NULL;
}
sorted = g_list_sort(unsorted, sort_pairs);
g_list_foreach(sorted, dump_pair, result);
g_list_free_full(sorted, free);
for (child = __xml_first_child(input); child != NULL; child = __xml_next(child)) {
if (recursive) {
sorted_xml(child, result, recursive);
} else {
add_node_copy(result, child);
}
}
return result;
}
/* "c048eae664dba840e1d2060f00299e9d" */
static char *
calculate_xml_digest_v1(xmlNode * input, gboolean sort, gboolean ignored)
{
char *digest = NULL;
char *buffer = NULL;
xmlNode *copy = NULL;
if (sort) {
crm_trace("Sorting xml...");
copy = sorted_xml(input, NULL, TRUE);
crm_trace("Done");
input = copy;
}
buffer = dump_xml_for_digest(input);
CRM_CHECK(buffer != NULL && strlen(buffer) > 0, free_xml(copy);
free(buffer);
return NULL);
digest = crm_md5sum(buffer);
crm_log_xml_trace(copy, "digest:source");
free(buffer);
free_xml(copy);
return digest;
}
static char *
calculate_xml_digest_v2(xmlNode * source, gboolean do_filter)
{
char *digest = NULL;
char *buffer = NULL;
int offset, max;
static struct qb_log_callsite *digest_cs = NULL;
crm_trace("Begin digest %s", do_filter?"filtered":"");
if (do_filter && BEST_EFFORT_STATUS) {
/* Exclude the status calculation from the digest
*
* This doesn't mean it wont be sync'd, we just wont be paranoid
* about it being an _exact_ copy
*
* We don't need it to be exact, since we throw it away and regenerate
* from our peers whenever a new DC is elected anyway
*
* Importantly, this reduces the amount of XML to copy+export as
* well as the amount of data for MD5 needs to operate on
*/
} else {
dump_xml(source, do_filter ? xml_log_option_filtered : 0, &buffer, &offset, &max, 0);
}
CRM_ASSERT(buffer != NULL);
digest = crm_md5sum(buffer);
if (digest_cs == NULL) {
digest_cs = qb_log_callsite_get(__func__, __FILE__, "cib-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
if (digest_cs && digest_cs->targets) {
char *trace_file = crm_concat("/tmp/cib-digest", digest, '-');
crm_trace("Saving %s.%s.%s to %s",
crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
crm_element_value(source, XML_ATTR_GENERATION),
crm_element_value(source, XML_ATTR_NUMUPDATES), trace_file);
save_xml_to_file(source, "digest input", trace_file);
free(trace_file);
}
free(buffer);
crm_trace("End digest");
return digest;
}
char *
calculate_on_disk_digest(xmlNode * input)
{
/* Always use the v1 format for on-disk digests
* a) its a compatability nightmare
* b) we only use this once at startup, all other
* invocations are in a separate child process
*/
return calculate_xml_digest_v1(input, FALSE, FALSE);
}
char *
calculate_operation_digest(xmlNode * input, const char *version)
{
/* We still need the sorting for parameter digests */
return calculate_xml_digest_v1(input, TRUE, FALSE);
}
char *
calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
const char *version)
{
/*
* The sorting associated with v1 digest creation accounted for 23% of
* the CIB's CPU usage on the server. v2 drops this.
*
* The filtering accounts for an additional 2.5% and we may want to
* remove it in future.
*
* v2 also uses the xmlBuffer contents directly to avoid additional copying
*/
if (version == NULL || compare_version("3.0.5", version) > 0) {
crm_trace("Using v1 digest algorithm for %s", crm_str(version));
return calculate_xml_digest_v1(input, sort, do_filter);
}
crm_trace("Using v2 digest algorithm for %s", crm_str(version));
return calculate_xml_digest_v2(input, do_filter);
}
static gboolean
validate_with_dtd(xmlDocPtr doc, gboolean to_logs, const char *dtd_file)
{
gboolean valid = TRUE;
xmlDtdPtr dtd = NULL;
xmlValidCtxtPtr cvp = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(dtd_file != NULL, return FALSE);
dtd = xmlParseDTD(NULL, (const xmlChar *)dtd_file);
CRM_CHECK(dtd != NULL, crm_err("Could not find/parse %s", dtd_file);
goto cleanup);
cvp = xmlNewValidCtxt();
CRM_CHECK(cvp != NULL, goto cleanup);
if (to_logs) {
cvp->userData = (void *)LOG_ERR;
cvp->error = (xmlValidityErrorFunc) xml_log;
cvp->warning = (xmlValidityWarningFunc) xml_log;
} else {
cvp->userData = (void *)stderr;
cvp->error = (xmlValidityErrorFunc) fprintf;
cvp->warning = (xmlValidityWarningFunc) fprintf;
}
if (!xmlValidateDtd(cvp, doc, dtd)) {
valid = FALSE;
}
cleanup:
if (cvp) {
xmlFreeValidCtxt(cvp);
}
if (dtd) {
xmlFreeDtd(dtd);
}
return valid;
}
xmlNode *
first_named_child(xmlNode * parent, const char *name)
{
xmlNode *match = NULL;
for (match = __xml_first_child(parent); match != NULL; match = __xml_next(match)) {
/*
* name == NULL gives first child regardless of name; this is
* semantically incorrect in this funciton, but may be necessary
* due to prior use of xml_child_iter_filter
*/
if (name == NULL || strcmp((const char *)match->name, name) == 0) {
return match;
}
}
return NULL;
}
#if 0
static void
relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
{
/*
Structure xmlError
struct _xmlError {
int domain : What part of the library raised this er
int code : The error code, e.g. an xmlParserError
char * message : human-readable informative error messag
xmlErrorLevel level : how consequent is the error
char * file : the filename
int line : the line number if available
char * str1 : extra string information
char * str2 : extra string information
char * str3 : extra string information
int int1 : extra number information
int int2 : column number of the error or 0 if N/A
void * ctxt : the parser context if available
void * node : the node in the tree
}
*/
crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
}
#endif
static gboolean
validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
relaxng_ctx_cache_t ** cached_ctx)
{
int rc = 0;
gboolean valid = TRUE;
relaxng_ctx_cache_t *ctx = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(relaxng_file != NULL, return FALSE);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_info("Creating RNG parser context");
ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
xmlLoadExtDtdDefaultValue = 1;
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf, stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL, crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf, stderr);
}
}
/* xmlRelaxNGSetValidStructuredErrors( */
/* valid, relaxng_invalid_stderr, valid); */
xmlLineNumbersDefault(1);
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
valid = FALSE;
} else if (rc < 0) {
crm_err("Internal libxml error during validation\n");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
void
crm_xml_init(void)
{
static bool init = TRUE;
if(init) {
init = FALSE;
/* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
* realloc()s and it can take upwards of 18 seconds (yes, seconds)
* to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
* less than 1 second.
*/
xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
}
}
void
crm_xml_cleanup(void)
{
int lpc = 0;
relaxng_ctx_cache_t *ctx = NULL;
crm_info("Cleaning up memory from libxml2");
for (; lpc < all_schemas; lpc++) {
switch (known_schemas[lpc].type) {
case 0:
/* None */
break;
case 1:
/* DTD - Not cached */
break;
case 2:
/* RNG - Cached */
ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
if (ctx == NULL) {
break;
}
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
known_schemas[lpc].cache = NULL;
break;
default:
break;
}
}
xsltCleanupGlobals();
xmlCleanupParser();
}
static gboolean
validate_with(xmlNode * xml, int method, gboolean to_logs)
{
xmlDocPtr doc = NULL;
gboolean valid = FALSE;
int type = known_schemas[method].type;
char *file = NULL;
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
file = get_schema_path(known_schemas[method].location);
crm_trace("Validating with: %s (type=%d)", crm_str(file), type);
switch (type) {
case 0:
valid = TRUE;
break;
case 1:
valid = validate_with_dtd(doc, to_logs, file);
break;
case 2:
valid =
validate_with_relaxng(doc, to_logs, file,
(relaxng_ctx_cache_t **) & (known_schemas[method].cache));
break;
default:
crm_err("Unknown validator type: %d", type);
break;
}
free(file);
return valid;
}
#include <stdio.h>
static void
dump_file(const char *filename)
{
FILE *fp = NULL;
int ch, line = 0;
CRM_CHECK(filename != NULL, return);
fp = fopen(filename, "r");
CRM_CHECK(fp != NULL, return);
fprintf(stderr, "%4d ", ++line);
do {
ch = getc(fp);
if (ch == EOF) {
putc('\n', stderr);
break;
} else if (ch == '\n') {
fprintf(stderr, "\n%4d ", ++line);
} else {
putc(ch, stderr);
}
} while (1);
fclose(fp);
}
gboolean
validate_xml_verbose(xmlNode * xml_blob)
{
int fd = 0;
xmlDoc *doc = NULL;
xmlNode *xml = NULL;
gboolean rc = FALSE;
char *filename = strdup(CRM_STATE_DIR "/cib-invalid.XXXXXX");
umask(S_IWGRP | S_IWOTH | S_IROTH);
fd = mkstemp(filename);
write_xml_fd(xml_blob, filename, fd, FALSE);
dump_file(filename);
doc = xmlParseFile(filename);
xml = xmlDocGetRootElement(doc);
rc = validate_xml(xml, NULL, FALSE);
free_xml(xml);
unlink(filename);
return rc;
}
gboolean
validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs)
{
int lpc = 0;
if (validation == NULL) {
validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
}
if (validation == NULL) {
validation = crm_element_value(xml_blob, "ignore-dtd");
if (crm_is_true(validation)) {
validation = "none";
} else {
validation = "pacemaker-1.0";
}
}
if (strcmp(validation, "none") == 0) {
return TRUE;
}
for (; lpc < all_schemas; lpc++) {
if (known_schemas[lpc].name && strcmp(validation, known_schemas[lpc].name) == 0) {
return validate_with(xml_blob, lpc, to_logs);
}
}
crm_err("Unknown validator: %s", validation);
return FALSE;
}
#if HAVE_LIBXSLT
static xmlNode *
apply_transformation(xmlNode * xml, const char *transform)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xmlDocPtr doc = NULL;
xsltStylesheet *xslt = NULL;
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
xform = get_schema_path(transform);
xmlLoadExtDtdDefaultValue = 1;
xmlSubstituteEntitiesDefault(1);
xslt = xsltParseStylesheetFile((const xmlChar *)xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
out = xmlDocGetRootElement(res);
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
#endif
const char *
get_schema_name(int version)
{
if (version < 0 || version >= all_schemas) {
return "unknown";
}
return known_schemas[version].name;
}
int
get_schema_version(const char *name)
{
int lpc = 0;
for (; lpc < all_schemas; lpc++) {
if (safe_str_eq(name, known_schemas[lpc].name)) {
return lpc;
}
}
return -1;
}
/* set which validation to use */
#include <crm/cib.h>
int
update_validation(xmlNode ** xml_blob, int *best, gboolean transform, gboolean to_logs)
{
xmlNode *xml = NULL;
char *value = NULL;
int lpc = 0, match = -1, rc = pcmk_ok;
CRM_CHECK(best != NULL, return -EINVAL);
CRM_CHECK(xml_blob != NULL, return -EINVAL);
CRM_CHECK(*xml_blob != NULL, return -EINVAL);
*best = 0;
xml = *xml_blob;
value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
if (value != NULL) {
match = get_schema_version(value);
lpc = match;
if (lpc >= 0 && transform == FALSE) {
lpc++;
} else if (lpc < 0) {
crm_debug("Unknown validation type");
lpc = 0;
}
}
if (match >= max_schemas) {
/* nothing to do */
free(value);
*best = match;
return pcmk_ok;
}
for (; lpc < max_schemas; lpc++) {
gboolean valid = TRUE;
crm_debug("Testing '%s' validation",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
valid = validate_with(xml, lpc, to_logs);
if (valid) {
*best = lpc;
}
if (valid && transform) {
xmlNode *upgrade = NULL;
int next = known_schemas[lpc].after_transform;
if (next <= 0) {
next = lpc + 1;
}
crm_notice("Upgrading %s-style configuration to %s with %s",
known_schemas[lpc].name, known_schemas[next].name,
known_schemas[lpc].transform ? known_schemas[lpc].transform : "no-op");
if (known_schemas[lpc].transform == NULL) {
if (validate_with(xml, next, to_logs)) {
crm_debug("Configuration valid for schema: %s", known_schemas[next].name);
lpc = next;
*best = next;
rc = pcmk_ok;
} else {
crm_info("Configuration not valid for schema: %s", known_schemas[next].name);
}
} else {
#if HAVE_LIBXSLT
upgrade = apply_transformation(xml, known_schemas[lpc].transform);
#endif
if (upgrade == NULL) {
crm_err("Transformation %s failed", known_schemas[lpc].transform);
rc = -pcmk_err_transform_failed;
} else if (validate_with(upgrade, next, to_logs)) {
crm_info("Transformation %s successful", known_schemas[lpc].transform);
lpc = next;
*best = next;
free_xml(xml);
xml = upgrade;
rc = pcmk_ok;
} else {
crm_err("Transformation %s did not produce a valid configuration",
known_schemas[lpc].transform);
crm_log_xml_info(upgrade, "transform:bad");
free_xml(upgrade);
rc = -pcmk_err_dtd_validation;
}
}
}
}
if (*best > match) {
crm_notice("Upgraded from %s to %s validation", value ? value : "<none>",
known_schemas[*best].name);
crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
}
*xml_blob = xml;
free(value);
return rc;
}
/*
* From xpath2.c
*
* All the elements returned by an XPath query are pointers to
* elements from the tree *except* namespace nodes where the XPath
* semantic is different from the implementation in libxml2 tree.
* As a result when a returned node set is freed when
* xmlXPathFreeObject() is called, that routine must check the
* element type. But node from the returned set may have been removed
* by xmlNodeSetContent() resulting in access to freed data.
*
* This can be exercised by running
* valgrind xpath2 test3.xml '//discarded' discarded
*
* There is 2 ways around it:
* - make a copy of the pointers to the nodes from the result set
* then call xmlXPathFreeObject() and then modify the nodes
* or
* - remove the references from the node set, if they are not
namespace nodes, before calling xmlXPathFreeObject().
*/
void
freeXpathObject(xmlXPathObjectPtr xpathObj)
{
int lpc, max = numXpathResults(xpathObj);
if(xpathObj == NULL) {
return;
}
for(lpc = 0; lpc < max; lpc++) {
if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
xpathObj->nodesetval->nodeTab[lpc] = NULL;
}
}
/* _Now_ its safe to free it */
xmlXPathFreeObject(xpathObj);
}
xmlNode *
getXpathResult(xmlXPathObjectPtr xpathObj, int index)
{
xmlNode *match = NULL;
int max = numXpathResults(xpathObj);
CRM_CHECK(index >= 0, return NULL);
CRM_CHECK(xpathObj != NULL, return NULL);
if (index >= max) {
crm_err("Requested index %d of only %d items", index, max);
return NULL;
} else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
/* Previously requested */
return NULL;
}
match = xpathObj->nodesetval->nodeTab[index];
CRM_CHECK(match != NULL, return NULL);
if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
/* See the comment for freeXpathObject() */
xpathObj->nodesetval->nodeTab[index] = NULL;
}
if (match->type == XML_DOCUMENT_NODE) {
/* Will happen if section = '/' */
match = match->children;
} else if (match->type != XML_ELEMENT_NODE
&& match->parent && match->parent->type == XML_ELEMENT_NODE) {
/* reurning the parent instead */
match = match->parent;
} else if (match->type != XML_ELEMENT_NODE) {
/* We only support searching nodes */
crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type);
match = NULL;
}
return match;
}
/* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */
xmlXPathObjectPtr
xpath_search(xmlNode * xml_top, const char *path)
{
xmlDocPtr doc = NULL;
xmlXPathObjectPtr xpathObj = NULL;
xmlXPathContextPtr xpathCtx = NULL;
const xmlChar *xpathExpr = (const xmlChar *)path;
CRM_CHECK(path != NULL, return NULL);
CRM_CHECK(xml_top != NULL, return NULL);
CRM_CHECK(strlen(path) > 0, return NULL);
doc = getDocPtr(xml_top);
xpathCtx = xmlXPathNewContext(doc);
CRM_ASSERT(xpathCtx != NULL);
xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
xmlXPathFreeContext(xpathCtx);
return xpathObj;
}
gboolean
cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs)
{
gboolean rc = TRUE;
static int min_version = -1;
static int max_version = -1;
const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
int version = get_schema_version(value);
if (min_version < 0) {
min_version = get_schema_version(MINIMUM_SCHEMA_VERSION);
}
if (max_version < 0) {
max_version = get_schema_version(LATEST_SCHEMA_VERSION);
}
if (version < min_version) {
xmlNode *converted = NULL;
converted = copy_xml(*xml);
update_validation(&converted, &version, TRUE, to_logs);
value = crm_element_value(converted, XML_ATTR_VALIDATION);
if (version < min_version) {
if (to_logs) {
crm_config_err("Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.\n", crm_str(value),
MINIMUM_SCHEMA_VERSION);
} else {
fprintf(stderr, "Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.\n", crm_str(value), MINIMUM_SCHEMA_VERSION);
}
free_xml(converted);
converted = NULL;
rc = FALSE;
} else {
free_xml(*xml);
*xml = converted;
if (version < max_version) {
crm_config_warn("Your configuration was internally updated to %s... "
"which is acceptable but not the most recent",
get_schema_name(version));
} else if (to_logs) {
crm_info("Your configuration was internally updated to the latest version (%s)",
get_schema_name(version));
}
}
} else if (version > max_version) {
if (to_logs) {
crm_config_warn("Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.");
} else {
fprintf(stderr, "Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.\n");
}
}
if (best_version) {
*best_version = version;
}
return rc;
}
xmlNode *
expand_idref(xmlNode * input, xmlNode * top)
{
const char *tag = NULL;
const char *ref = NULL;
xmlNode *result = input;
char *xpath_string = NULL;
if (result == NULL) {
return NULL;
} else if (top == NULL) {
top = input;
}
tag = crm_element_name(result);
ref = crm_element_value(result, XML_ATTR_IDREF);
if (ref != NULL) {
int xpath_max = 512, offset = 0;
xpath_string = calloc(1, xpath_max);
offset += snprintf(xpath_string + offset, xpath_max - offset, "//%s[@id='%s']", tag, ref);
result = get_xpath_object(xpath_string, top, LOG_ERR);
if (result == NULL) {
char *nodePath = (char *)xmlGetNodePath(top);
crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
crm_str(nodePath));
free(nodePath);
}
}
free(xpath_string);
return result;
}
xmlNode *
get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level)
{
int len = 0;
xmlNode *result = NULL;
char *xpath_full = NULL;
char *xpath_prefix = NULL;
if (xml_obj == NULL || xpath == NULL) {
return NULL;
}
xpath_prefix = (char *)xmlGetNodePath(xml_obj);
len += strlen(xpath_prefix);
len += strlen(xpath);
xpath_full = strdup(xpath_prefix);
xpath_full = realloc(xpath_full, len + 1);
strncat(xpath_full, xpath, len);
result = get_xpath_object(xpath_full, xml_obj, error_level);
free(xpath_prefix);
free(xpath_full);
return result;
}
xmlNode *
get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
{
int max;
xmlNode *result = NULL;
xmlXPathObjectPtr xpathObj = NULL;
char *nodePath = NULL;
char *matchNodePath = NULL;
if (xpath == NULL) {
return xml_obj; /* or return NULL? */
}
xpathObj = xpath_search(xml_obj, xpath);
nodePath = (char *)xmlGetNodePath(xml_obj);
max = numXpathResults(xpathObj);
if (max < 1) {
do_crm_log(error_level, "No match for %s in %s", xpath, crm_str(nodePath));
crm_log_xml_explicit(xml_obj, "Unexpected Input");
} else if (max > 1) {
int lpc = 0;
do_crm_log(error_level, "Too many matches for %s in %s", xpath, crm_str(nodePath));
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_CHECK(match != NULL, continue);
matchNodePath = (char *)xmlGetNodePath(match);
do_crm_log(error_level, "%s[%d] = %s", xpath, lpc, crm_str(matchNodePath));
free(matchNodePath);
}
crm_log_xml_explicit(xml_obj, "Bad Input");
} else {
result = getXpathResult(xpathObj, 0);
}
freeXpathObject(xpathObj);
free(nodePath);
return result;
}
const char *
crm_element_value(xmlNode * data, const char *name)
{
xmlAttr *attr = NULL;
if (data == NULL) {
crm_err("Couldn't find %s in NULL", name ? name : "<null>");
CRM_LOG_ASSERT(data != NULL);
return NULL;
} else if (name == NULL) {
crm_err("Couldn't find NULL in %s", crm_element_name(data));
return NULL;
}
attr = xmlHasProp(data, (const xmlChar *)name);
if (attr == NULL || attr->children == NULL) {
return NULL;
}
return (const char *)attr->children->content;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Oct 29, 8:34 PM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
942597
Default Alt Text
(92 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment