Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/util.h b/include/crm/common/util.h
index 63b705cbae..9a5338827d 100644
--- a/include/crm/common/util.h
+++ b/include/crm/common/util.h
@@ -1,183 +1,186 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program 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 of the License, or (at your option) any later version.
*
* This software 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
* 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
*/
#ifndef CRM_COMMON_UTIL__H
# define CRM_COMMON_UTIL__H
/**
* \file
* \brief Utility functions
* \ingroup core
*/
# include <sys/types.h>
# include <stdlib.h>
# include <stdbool.h>
# include <limits.h>
# include <signal.h>
# include <sysexits.h>
# include <glib.h>
# include <crm/lrmd.h>
# if SUPPORT_HEARTBEAT
# include <heartbeat.h>
# else
# define NORMALNODE "normal"
# define ACTIVESTATUS "active"/* fully functional, and all links are up */
# define DEADSTATUS "dead"
/* Status of non-working link or machine */
# define PINGSTATUS "ping"
/* Status of a working ping node */
# define JOINSTATUS "join"
/* Status when an api client joins */
# define LEAVESTATUS "leave"
/* Status when an api client leaves */
# define ONLINESTATUS "online"/* Status of an online client */
# define OFFLINESTATUS "offline"
/* Status of an offline client */
# endif
/* public Pacemaker Remote functions (from remote.c) */
int crm_default_remote_port(void);
/* public string functions (from strings.c) */
char *crm_itoa_stack(int an_int, char *buf, size_t len);
char *crm_itoa(int an_int);
gboolean crm_is_true(const char *s);
int crm_str_to_boolean(const char *s, int *ret);
int crm_parse_int(const char *text, const char *default_text);
char * crm_strip_trailing_newline(char *str);
gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
gboolean safe_str_neq(const char *a, const char *b);
guint crm_strcase_hash(gconstpointer v);
guint g_str_hash_traditional(gconstpointer v);
# define safe_str_eq(a, b) crm_str_eq(a, b, FALSE)
# define crm_str_hash g_str_hash_traditional
/* used with hash tables where case does not matter */
static inline gboolean
crm_strcase_equal(gconstpointer a, gconstpointer b)
{
return crm_str_eq((const char *) a, (const char *) b, FALSE);
}
/*!
* \brief Create hash table with dynamically allocated string keys/values
*
* \return Newly hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_str_table_new()
{
return g_hash_table_new_full(crm_str_hash, g_str_equal, free, free);
}
/*!
* \brief Create hash table with case-insensitive dynamically allocated string keys/values
*
* \return Newly hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_strcase_table_new()
{
return g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, free, free);
}
GHashTable *crm_str_table_dup(GHashTable *old_table);
# define crm_atoi(text, default_text) crm_parse_int(text, default_text)
/* public I/O functions (from io.c) */
void crm_build_path(const char *path_c, mode_t mode);
long long crm_get_msec(const char *input);
unsigned long long crm_get_interval(const char *input);
int char2score(const char *score);
char *score2char(int score);
char *score2char_stack(int score, char *buf, size_t len);
/* public operation functions (from operations.c) */
gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
int *interval);
gboolean decode_transition_key(const char *key, char **uuid, int *action,
int *transition_id, int *target_rc);
gboolean decode_transition_magic(const char *magic, char **uuid,
int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc);
int rsc_op_expected_rc(lrmd_event_data_t *event);
gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
bool crm_op_needs_metadata(const char *rsc_class, const char *op);
+xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
+ const char *task, const char *interval,
+ const char *timeout);
int compare_version(const char *version1, const char *version2);
/* coverity[+kill] */
void crm_abort(const char *file, const char *function, int line,
const char *condition, gboolean do_core, gboolean do_fork);
static inline gboolean
is_not_set(long long word, long long bit)
{
return ((word & bit) == 0);
}
static inline gboolean
is_set(long long word, long long bit)
{
return ((word & bit) == bit);
}
static inline gboolean
is_set_any(long long word, long long bit)
{
return ((word & bit) != 0);
}
static inline guint
crm_hash_table_size(GHashTable * hashtable)
{
if (hashtable == NULL) {
return 0;
}
return g_hash_table_size(hashtable);
}
char *crm_meta_name(const char *field);
const char *crm_meta_value(GHashTable * hash, const char *field);
char *crm_md5sum(const char *buffer);
char *crm_generate_uuid(void);
bool crm_is_daemon_name(const char *name);
int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid);
#ifdef HAVE_GNUTLS_GNUTLS_H
void crm_gnutls_global_init(void);
#endif
int crm_exit(int rc);
bool pcmk_acl_required(const char *user);
char *crm_generate_ra_key(const char *class, const char *provider, const char *type);
#endif
diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index a625c73471..8297bb20ba 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,410 +1,407 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program 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 of the License, or (at your option) any later version.
*
* This software 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
* 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
*/
#ifndef CRM_COMMON_XML__H
# define CRM_COMMON_XML__H
/**
* \file
* \brief Wrappers for and extensions to libxml2
* \ingroup core
*/
# include <stdio.h>
# include <sys/types.h>
# include <unistd.h>
# include <stdlib.h>
# include <errno.h>
# include <fcntl.h>
# include <crm/crm.h>
# include <libxml/tree.h>
# include <libxml/xpath.h>
/* Define compression parameters for IPC messages
*
* Compression costs a LOT, so we don't want to do it unless we're hitting
* message limits. Currently, we use 128KB as the threshold, because higher
* values don't play well with the heartbeat stack. With an earlier limit of
* 10KB, compressing 184 of 1071 messages accounted for 23% of the total CPU
* used by the cib.
*/
# define CRM_BZ2_BLOCKS 4
# define CRM_BZ2_WORK 20
# define CRM_BZ2_THRESHOLD 128 * 1024
# define XML_PARANOIA_CHECKS 0
gboolean add_message_xml(xmlNode * msg, const char *field, xmlNode * xml);
xmlNode *get_message_xml(xmlNode * msg, const char *field);
GHashTable *xml2list(xmlNode * parent);
xmlNode *crm_create_nvpair_xml(xmlNode *parent, const char *id,
const char *name, const char *value);
-xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
- const char *task, const char *interval,
- const char *timeout);
void hash2nvpair(gpointer key, gpointer value, gpointer user_data);
void hash2field(gpointer key, gpointer value, gpointer user_data);
void hash2metafield(gpointer key, gpointer value, gpointer user_data);
void hash2smartfield(gpointer key, gpointer value, gpointer user_data);
xmlDoc *getDocPtr(xmlNode * node);
/*
* Replacement function for xmlCopyPropList which at the very least,
* doesn't work the way *I* would expect it to.
*
* Copy all the attributes/properties from src into target.
*
* Not recursive, does not return anything.
*
*/
void copy_in_properties(xmlNode * target, xmlNode * src);
void expand_plus_plus(xmlNode * target, const char *name, const char *value);
void fix_plus_plus_recursive(xmlNode * target);
/*
* Create a node named "name" as a child of "parent"
* If parent is NULL, creates an unconnected node.
*
* Returns the created node
*
*/
xmlNode *create_xml_node(xmlNode * parent, const char *name);
/*
* Make a copy of name and value and use the copied memory to create
* an attribute for node.
*
* If node, name or value are NULL, nothing is done.
*
* If name or value are an empty string, nothing is done.
*
* Returns FALSE on failure and TRUE on success.
*
*/
const char *crm_xml_add(xmlNode * node, const char *name, const char *value);
const char *crm_xml_replace(xmlNode * node, const char *name, const char *value);
const char *crm_xml_add_int(xmlNode * node, const char *name, int value);
/*!
* \brief Add a boolean attribute to an XML object
*
* Add an attribute with the value XML_BOOLEAN_TRUE or XML_BOOLEAN_FALSE
* as appropriate to an XML object.
*
* \param[in,out] node XML object to add attribute to
* \param[in] name Name of attribute to add
* \param[in] value Boolean whose value will be tested
*
* \return Pointer to newly created XML attribute's content, or NULL on error
*/
static inline const char *
crm_xml_add_boolean(xmlNode *node, const char *name, gboolean value)
{
return crm_xml_add(node, name, (value? "true" : "false"));
}
/*
* Unlink the node and set its doc pointer to NULL so free_xml()
* will act appropriately
*/
void unlink_xml_node(xmlNode * node);
/*
*
*/
void purge_diff_markers(xmlNode * a_node);
/*
* Returns a deep copy of src_node
*
*/
xmlNode *copy_xml(xmlNode * src_node);
/*
* Add a copy of xml_node to new_parent
*/
xmlNode *add_node_copy(xmlNode * new_parent, xmlNode * xml_node);
int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child);
/*
* XML I/O Functions
*
* Whitespace between tags is discarded.
*/
xmlNode *filename2xml(const char *filename);
xmlNode *stdin2xml(void);
xmlNode *string2xml(const char *input);
int write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress);
int write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress);
char *dump_xml_formatted(xmlNode * msg);
/* Also dump the text node with xml_log_option_text enabled */
char *dump_xml_formatted_with_text(xmlNode * msg);
char *dump_xml_unformatted(xmlNode * msg);
/*
* Diff related Functions
*/
xmlNode *diff_xml_object(xmlNode * left, xmlNode * right, gboolean suppress);
xmlNode *subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean full, gboolean * changed, const char *marker);
gboolean can_prune_leaf(xmlNode * xml_node);
void print_xml_diff(FILE * where, xmlNode * diff);
gboolean apply_xml_diff(xmlNode * old, xmlNode * diff, xmlNode ** new);
/*
* Searching & Modifying
*/
xmlNode *find_xml_node(xmlNode * cib, const char *node_path, gboolean must_find);
xmlNode *find_entity(xmlNode * parent, const char *node_name, const char *id);
void xml_remove_prop(xmlNode * obj, const char *name);
gboolean replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update,
gboolean delete_only);
gboolean update_xml_child(xmlNode * child, xmlNode * to_update);
int find_xml_children(xmlNode ** children, xmlNode * root,
const char *tag, const char *field, const char *value,
gboolean search_matches);
int crm_element_value_int(xmlNode * data, const char *name, int *dest);
char *crm_element_value_copy(xmlNode * data, const char *name);
int crm_element_value_const_int(const xmlNode * data, const char *name, int *dest);
const char *crm_element_value_const(const xmlNode * data, const char *name);
xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
xmlNode *get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level);
static inline const char *
crm_element_name(xmlNode *xml)
{
return xml? (const char *)(xml->name) : NULL;
}
const char *crm_element_value(xmlNode * data, const char *name);
/*!
* \brief Copy an element from one XML object to another
*
* \param[in] obj1 Source XML
* \param[in,out] obj2 Destination XML
* \param[in] element Name of element to copy
*
* \return Pointer to copied value (from source)
*/
static inline const char *
crm_copy_xml_element(xmlNode *obj1, xmlNode *obj2, const char *element)
{
const char *value = crm_element_value(obj1, element);
crm_xml_add(obj2, element, value);
return value;
}
void xml_validate(const xmlNode * root);
gboolean xml_has_children(const xmlNode * root);
char *calculate_on_disk_digest(xmlNode * local_cib);
char *calculate_operation_digest(xmlNode * local_cib, const char *version);
char *calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
const char *version);
/* schema-related functions (from schemas.c) */
gboolean validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs);
gboolean validate_xml_verbose(xmlNode * xml_blob);
/*!
* \brief Update CIB XML to most recent schema version
*
* "Update" means either actively employ XSLT-based transformation(s)
* (if intermediate product to transform valid per its declared schema version,
* transformation available, proceeded successfully with a result valid per
* expectated newer schema version), or just try to bump the marked validating
* schema until all gradually rising schema versions attested or the first
* such attempt subsequently fails to validate. Which of the two styles will
* be used depends on \p transform parameter (positive/negative, respectively).
*
* \param[in,out] xml_blob XML tree representing CIB, may be swapped with
* an "updated" one
* \param[out] best The highest configuration version (per its index
* in the global schemas table) it was possible to
* reach during the update steps while ensuring
* the validity of the result; if no validation
* success was observed against possibly multiple
* schemas, the value is less or equal the result
* of <tt>get_schema_version</tt> applied on the
* input \p xml_blob value (unless that function
* maps it to -1, then 0 would be used instead)
* \param[in] max When \p transform is positive, this allows to
* set upper boundary schema (per its index in the
* global schemas table) beyond which its forbidden
* to update by the means of XSLT transformation
* \param[in] transform Whether to employ XSLT-based transformation so
* as allow overcoming possible incompatibilities
* between major schema versions (see above)
* \param[in] to_logs If true, output notable progress info to
* internal log streams; if false, to stderr
*
* \return <tt>pcmk_ok</tt> if no non-recoverable error encountered (up to
* caller to evaluate if the update satisfies the requirements
* per returned \p best value), negative value carrying the reason
* otherwise
*/
int update_validation(xmlNode **xml_blob, int *best, int max,
gboolean transform, gboolean to_logs);
int get_schema_version(const char *name);
const char *get_schema_name(int version);
const char *xml_latest_schema(void);
gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
void crm_xml_init(void);
void crm_xml_cleanup(void);
static inline xmlNode *
__xml_first_child(xmlNode * parent)
{
xmlNode *child = NULL;
if (parent) {
child = parent->children;
while (child && child->type == XML_TEXT_NODE) {
child = child->next;
}
}
return child;
}
static inline xmlNode *
__xml_next(xmlNode * child)
{
if (child) {
child = child->next;
while (child && child->type == XML_TEXT_NODE) {
child = child->next;
}
}
return child;
}
static inline xmlNode *
__xml_first_child_element(xmlNode * parent)
{
xmlNode *child = NULL;
if (parent) {
child = parent->children;
}
while (child) {
if(child->type == XML_ELEMENT_NODE) {
return child;
}
child = child->next;
}
return NULL;
}
static inline xmlNode *
__xml_next_element(xmlNode * child)
{
while (child) {
child = child->next;
if(child && child->type == XML_ELEMENT_NODE) {
return child;
}
}
return NULL;
}
void free_xml(xmlNode * child);
xmlNode *first_named_child(xmlNode * parent, const char *name);
xmlNode *crm_next_same_xml(xmlNode *sibling);
xmlNode *sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive);
xmlXPathObjectPtr xpath_search(xmlNode * xml_top, const char *path);
void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
void (*helper)(xmlNode*, void*), void *user_data);
xmlNode *expand_idref(xmlNode * input, xmlNode * top);
void freeXpathObject(xmlXPathObjectPtr xpathObj);
xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
void dedupXpathResults(xmlXPathObjectPtr xpathObj);
static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
{
if(xpathObj == NULL || xpathObj->nodesetval == NULL) {
return 0;
}
return xpathObj->nodesetval->nodeNr;
}
bool xml_acl_enabled(xmlNode *xml);
void xml_acl_disable(xmlNode *xml);
bool xml_acl_denied(xmlNode *xml); /* Part or all of a change was rejected */
bool xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml, xmlNode ** result);
bool xml_tracking_changes(xmlNode * xml);
bool xml_document_dirty(xmlNode *xml);
void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
void xml_calculate_changes(xmlNode * old, xmlNode * new); /* For comparing two documents after the fact */
void xml_accept_changes(xmlNode * xml);
void xml_log_changes(uint8_t level, const char *function, xmlNode *xml);
void xml_log_patchset(uint8_t level, const char *function, xmlNode *xml);
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3]);
xmlNode *xml_create_patchset(
int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
void save_xml_to_file(xmlNode * xml, const char *desc, const char *filename);
char *xml_get_path(xmlNode *xml);
char * crm_xml_escape(const char *text);
void crm_xml_sanitize_id(char *id);
void crm_xml_set_id(xmlNode *xml, const char *format, ...)
__attribute__ ((__format__ (__printf__, 2, 3)));
/*!
* \brief xmlNode destructor which can be used in glib collections
*/
void crm_destroy_xml(gpointer data);
#endif
diff --git a/lib/common/operations.c b/lib/common/operations.c
index 0f6bee6d15..4fc3b5c930 100644
--- a/lib/common/operations.c
+++ b/lib/common/operations.c
@@ -1,637 +1,666 @@
/*
* Copyright (C) 2004-2017 Andrew Beekhof <andrew@beekhof.net>
*
* 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>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
/*!
* \brief Generate an operation key
*
* \param[in] rsc_id ID of resource being operated on
* \param[in] op_type Operation name
* \param[in] interval Operation interval
*
* \return Newly allocated memory containing operation key as string
*
* \note It is the caller's responsibility to free() the result.
*/
char *
generate_op_key(const char *rsc_id, const char *op_type, int interval)
{
CRM_ASSERT(rsc_id != NULL);
CRM_ASSERT(op_type != NULL);
CRM_ASSERT(interval >= 0);
return crm_strdup_printf("%s_%s_%d", rsc_id, op_type, interval);
}
gboolean
parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval)
{
char *notify = NULL;
char *mutable_key = NULL;
char *mutable_key_ptr = NULL;
int len = 0, offset = 0, ch = 0;
CRM_CHECK(key != NULL, return FALSE);
*interval = 0;
len = strlen(key);
offset = len - 1;
crm_trace("Source: %s", key);
while (offset > 0 && isdigit(key[offset])) {
int digits = len - offset;
ch = key[offset] - '0';
CRM_CHECK(ch < 10, return FALSE);
CRM_CHECK(ch >= 0, return FALSE);
while (digits > 1) {
digits--;
ch = ch * 10;
}
*interval += ch;
offset--;
}
crm_trace(" Interval: %d", *interval);
CRM_CHECK(key[offset] == '_', return FALSE);
mutable_key = strdup(key);
mutable_key[offset] = 0;
offset--;
while (offset > 0 && key[offset] != '_') {
offset--;
}
CRM_CHECK(key[offset] == '_', free(mutable_key);
return FALSE);
mutable_key_ptr = mutable_key + offset + 1;
crm_trace(" Action: %s", mutable_key_ptr);
*op_type = strdup(mutable_key_ptr);
mutable_key[offset] = 0;
offset--;
CRM_CHECK(mutable_key != mutable_key_ptr, free(mutable_key);
return FALSE);
notify = strstr(mutable_key, "_post_notify");
if (notify && safe_str_eq(notify, "_post_notify")) {
notify[0] = 0;
}
notify = strstr(mutable_key, "_pre_notify");
if (notify && safe_str_eq(notify, "_pre_notify")) {
notify[0] = 0;
}
crm_trace(" Resource: %s", mutable_key);
*rsc_id = mutable_key;
return TRUE;
}
char *
generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type)
{
int len = 12;
char *op_id = NULL;
CRM_CHECK(rsc_id != NULL, return NULL);
CRM_CHECK(op_type != NULL, return NULL);
CRM_CHECK(notify_type != NULL, return NULL);
len += strlen(op_type);
len += strlen(rsc_id);
len += strlen(notify_type);
if(len > 0) {
op_id = malloc(len);
}
if (op_id != NULL) {
sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type);
}
return op_id;
}
char *
generate_transition_magic_v202(const char *transition_key, int op_status)
{
int len = 80;
char *fail_state = NULL;
CRM_CHECK(transition_key != NULL, return NULL);
len += strlen(transition_key);
fail_state = malloc(len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%s", op_status, transition_key);
}
return fail_state;
}
char *
generate_transition_magic(const char *transition_key, int op_status, int op_rc)
{
int len = 80;
char *fail_state = NULL;
CRM_CHECK(transition_key != NULL, return NULL);
len += strlen(transition_key);
fail_state = malloc(len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key);
}
return fail_state;
}
gboolean
decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc)
{
int res = 0;
char *key = NULL;
gboolean result = TRUE;
CRM_CHECK(magic != NULL, return FALSE);
CRM_CHECK(op_rc != NULL, return FALSE);
CRM_CHECK(op_status != NULL, return FALSE);
key = calloc(1, strlen(magic) + 1);
res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key);
if (res != 3) {
crm_warn("Only found %d items in: '%s'", res, magic);
free(key);
return FALSE;
}
CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE);
free(key);
return result;
}
char *
generate_transition_key(int transition_id, int action_id, int target_rc, const char *node)
{
int len = 40;
char *fail_state = NULL;
CRM_CHECK(node != NULL, return NULL);
len += strlen(node);
fail_state = malloc(len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%d:%d:%-*s", action_id, transition_id, target_rc, 36, node);
}
return fail_state;
}
gboolean
decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
int *target_rc)
{
int res = 0;
gboolean done = FALSE;
CRM_CHECK(uuid != NULL, return FALSE);
CRM_CHECK(target_rc != NULL, return FALSE);
CRM_CHECK(action_id != NULL, return FALSE);
CRM_CHECK(transition_id != NULL, return FALSE);
*uuid = calloc(1, 37);
res = sscanf(key, "%d:%d:%d:%36s", action_id, transition_id, target_rc, *uuid);
switch (res) {
case 4:
/* Post Pacemaker 0.6 */
done = TRUE;
break;
case 3:
case 2:
/* this can be tricky - the UUID might start with an integer */
/* Until Pacemaker 0.6 */
done = TRUE;
*target_rc = -1;
res = sscanf(key, "%d:%d:%36s", action_id, transition_id, *uuid);
if (res == 2) {
*action_id = -1;
res = sscanf(key, "%d:%36s", transition_id, *uuid);
CRM_CHECK(res == 2, done = FALSE);
} else if (res != 3) {
CRM_CHECK(res == 3, done = FALSE);
}
break;
case 1:
/* Prior to Heartbeat 2.0.8 */
done = TRUE;
*action_id = -1;
*target_rc = -1;
res = sscanf(key, "%d:%36s", transition_id, *uuid);
CRM_CHECK(res == 2, done = FALSE);
break;
default:
crm_crit("Unhandled sscanf result (%d) for %s", res, key);
}
if (strlen(*uuid) != 36) {
crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key);
}
if (done == FALSE) {
crm_err("Cannot decode '%s' rc=%d", key, res);
free(*uuid);
*uuid = NULL;
*target_rc = -1;
*action_id = -1;
*transition_id = -1;
}
return done;
}
void
filter_action_parameters(xmlNode * param_set, const char *version)
{
char *key = NULL;
char *timeout = NULL;
char *interval = NULL;
const char *attr_filter[] = {
XML_ATTR_ID,
XML_ATTR_CRM_VERSION,
XML_LRM_ATTR_OP_DIGEST,
XML_LRM_ATTR_TARGET,
XML_LRM_ATTR_TARGET_UUID,
"pcmk_external_ip"
};
gboolean do_delete = FALSE;
int lpc = 0;
static int meta_len = 0;
if (meta_len == 0) {
meta_len = strlen(CRM_META);
}
if (param_set == NULL) {
return;
}
for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) {
xml_remove_prop(param_set, attr_filter[lpc]);
}
key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
interval = crm_element_value_copy(param_set, key);
free(key);
key = crm_meta_name(XML_ATTR_TIMEOUT);
timeout = crm_element_value_copy(param_set, key);
if (param_set) {
xmlAttrPtr xIter = param_set->properties;
while (xIter) {
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
do_delete = FALSE;
if (strncasecmp(prop_name, CRM_META, meta_len) == 0) {
do_delete = TRUE;
}
if (do_delete) {
xml_remove_prop(param_set, prop_name);
}
}
}
if (crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) {
/* Re-instate the operation's timeout value */
if (timeout != NULL) {
crm_xml_add(param_set, key, timeout);
}
}
free(interval);
free(timeout);
free(key);
}
#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
static void
append_digest(lrmd_event_data_t * op, xmlNode * update, const char *version, const char *magic,
int level)
{
/* this will enable us to later determine that the
* resource's parameters have changed and we should force
* a restart
*/
char *digest = NULL;
xmlNode *args_xml = NULL;
if (op->params == NULL) {
return;
}
args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
g_hash_table_foreach(op->params, hash2field, args_xml);
filter_action_parameters(args_xml, version);
digest = calculate_operation_digest(args_xml, version);
#if 0
if (level < get_crm_log_level()
&& op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) {
char *digest_source = dump_xml_unformatted(args_xml);
do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n",
digest, ID(update), magic, digest_source);
free(digest_source);
}
#endif
crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
free_xml(args_xml);
free(digest);
}
int
rsc_op_expected_rc(lrmd_event_data_t * op)
{
int rc = 0;
if (op && op->user_data) {
int dummy = 0;
char *uuid = NULL;
decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc);
free(uuid);
}
return rc;
}
gboolean
did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
{
switch (op->op_status) {
case PCMK_LRM_OP_CANCELLED:
case PCMK_LRM_OP_PENDING:
return FALSE;
break;
case PCMK_LRM_OP_NOTSUPPORTED:
case PCMK_LRM_OP_TIMEOUT:
case PCMK_LRM_OP_ERROR:
return TRUE;
break;
default:
if (target_rc != op->rc) {
return TRUE;
}
}
return FALSE;
}
+/*!
+ * \brief Create a CIB XML element for an operation
+ *
+ * \param[in] parent If not NULL, make new XML node a child of this one
+ * \param[in] prefix Generate an ID using this prefix
+ * \param[in] task Operation task to set
+ * \param[in] interval Operation interval to set
+ * \param[in] timeout If not NULL, operation timeout to set
+ *
+ * \return New XML object on success, NULL otherwise
+ */
+xmlNode *
+crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
+ const char *interval, const char *timeout)
+{
+ xmlNode *xml_op;
+
+ CRM_CHECK(prefix && task && interval, return NULL);
+
+ xml_op = create_xml_node(parent, XML_ATTR_OP);
+ crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval);
+ crm_xml_add(xml_op, XML_LRM_ATTR_INTERVAL, interval);
+ crm_xml_add(xml_op, "name", task);
+ if (timeout) {
+ crm_xml_add(xml_op, XML_ATTR_TIMEOUT, timeout);
+ }
+ return xml_op;
+}
+
xmlNode *
create_operation_update(xmlNode * parent, lrmd_event_data_t * op, const char * caller_version,
int target_rc, const char * node, const char * origin, int level)
{
char *key = NULL;
char *magic = NULL;
char *op_id = NULL;
char *op_id_additional = NULL;
char *local_user_data = NULL;
const char *exit_reason = NULL;
xmlNode *xml_op = NULL;
const char *task = NULL;
gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0);
gboolean dc_needs_unique_ops = (compare_version(caller_version, "3.0.6") < 0);
CRM_CHECK(op != NULL, return NULL);
do_crm_log(level, "%s: Updating resource %s after %s op %s (interval=%d)",
origin, op->rsc_id, op->op_type, services_lrm_status_str(op->op_status),
op->interval);
crm_trace("DC version: %s", caller_version);
task = op->op_type;
/* remap the task name under various scenarios
* this makes life easier for the PE when trying determine the current state
*/
if (crm_str_eq(task, "reload", TRUE)) {
if (op->op_status == PCMK_LRM_OP_DONE) {
task = CRMD_ACTION_START;
} else {
task = CRMD_ACTION_STATUS;
}
} else if (dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) {
/* if the migrate_from fails it will have enough info to do the right thing */
if (op->op_status == PCMK_LRM_OP_DONE) {
task = CRMD_ACTION_STOP;
} else {
task = CRMD_ACTION_STATUS;
}
} else if (dc_munges_migrate_ops
&& op->op_status == PCMK_LRM_OP_DONE
&& crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) {
task = CRMD_ACTION_START;
}
key = generate_op_key(op->rsc_id, task, op->interval);
if (dc_needs_unique_ops && op->interval > 0) {
op_id = strdup(key);
} else if (crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) {
const char *n_type = crm_meta_value(op->params, "notify_type");
const char *n_task = crm_meta_value(op->params, "notify_operation");
CRM_LOG_ASSERT(n_type != NULL);
CRM_LOG_ASSERT(n_task != NULL);
op_id = generate_notify_key(op->rsc_id, n_type, n_task);
/* these are not yet allowed to fail */
op->op_status = PCMK_LRM_OP_DONE;
op->rc = 0;
} else if (did_rsc_op_fail(op, target_rc)) {
op_id = generate_op_key(op->rsc_id, "last_failure", 0);
if (op->interval == 0) {
/* Ensure 'last' gets updated too in case recording-pending="true" */
op_id_additional = generate_op_key(op->rsc_id, "last", 0);
}
exit_reason = op->exit_reason;
} else if (op->interval > 0) {
op_id = strdup(key);
} else {
op_id = generate_op_key(op->rsc_id, "last", 0);
}
again:
xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id);
if (xml_op == NULL) {
xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
}
if (op->user_data == NULL) {
crm_debug("Generating fake transition key for:"
" %s_%s_%d %d from %s",
op->rsc_id, op->op_type, op->interval, op->call_id, origin);
local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID);
op->user_data = local_user_data;
}
if(magic == NULL) {
magic = generate_transition_magic(op->user_data, op->op_status, op->rc);
}
crm_xml_add(xml_op, XML_ATTR_ID, op_id);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
crm_xml_add(xml_op, XML_LRM_ATTR_EXIT_REASON, exit_reason);
crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, node); /* For context during triage */
crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval);
if (compare_version("2.1", caller_version) <= 0) {
if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
crm_trace("Timing data (%s_%s_%d): last=%u change=%u exec=%u queue=%u",
op->rsc_id, op->op_type, op->interval,
op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
if (op->interval == 0) {
/* The values are the same for non-recurring ops */
crm_xml_add_int(xml_op, XML_RSC_OP_LAST_RUN, op->t_run);
crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
} else if(op->t_rcchange) {
/* last-run is not accurate for recurring ops */
crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_rcchange);
} else {
/* ...but is better than nothing otherwise */
crm_xml_add_int(xml_op, XML_RSC_OP_LAST_CHANGE, op->t_run);
}
crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
}
}
if (crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE)
|| crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) {
/*
* Record migrate_source and migrate_target always for migrate ops.
*/
const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
name = XML_LRM_ATTR_MIGRATE_TARGET;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
}
append_digest(op, xml_op, caller_version, magic, LOG_DEBUG);
if (op_id_additional) {
free(op_id);
op_id = op_id_additional;
op_id_additional = NULL;
goto again;
}
if (local_user_data) {
free(local_user_data);
op->user_data = NULL;
}
free(magic);
free(op_id);
free(key);
return xml_op;
}
/*!
* \brief Check whether an operation requires resource agent meta-data
*
* \param[in] rsc_class Resource agent class (or NULL to skip class check)
* \param[in] op Operation action (or NULL to skip op check)
*
* \return TRUE if operation needs meta-data, FALSE otherwise
* \note At least one of rsc_class and op must be specified.
*/
bool
crm_op_needs_metadata(const char *rsc_class, const char *op)
{
/* Agent meta-data is used to determine whether a reload is possible, and to
* evaluate versioned parameters -- so if this op is not relevant to those
* features, we don't need the meta-data.
*/
CRM_CHECK(rsc_class || op, return FALSE);
if (rsc_class
&& strcmp(rsc_class, PCMK_RESOURCE_CLASS_OCF)
&& strcmp(rsc_class, PCMK_RESOURCE_CLASS_STONITH)) {
/* Meta-data is only needed for resource classes that use parameters */
return FALSE;
}
/* Meta-data is only needed for these actions */
if (op
&& strcmp(op, CRMD_ACTION_START)
&& strcmp(op, CRMD_ACTION_STATUS)
&& strcmp(op, CRMD_ACTION_PROMOTE)
&& strcmp(op, CRMD_ACTION_DEMOTE)
&& strcmp(op, CRMD_ACTION_RELOAD)
&& strcmp(op, CRMD_ACTION_MIGRATE)
&& strcmp(op, CRMD_ACTION_MIGRATED)
&& strcmp(op, CRMD_ACTION_NOTIFY)) {
return FALSE;
}
return TRUE;
}
diff --git a/lib/common/xml.c b/lib/common/xml.c
index ea0adb7c1d..947ddfe6f5 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,5219 +1,5190 @@
/*
* 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 <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> /* CRM_XML_LOG_BASE */
#if HAVE_BZLIB_H
# include <bzlib.h>
#endif
#if HAVE_LIBXML2
# include <libxml/parser.h>
# include <libxml/tree.h>
#endif
#define XML_BUFFER_SIZE 4096
#define XML_PARSER_DEBUG 0
static inline int
__get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset);
typedef struct {
int found;
const char *string;
} filter_t;
enum xml_private_flags {
xpf_none = 0x0000,
xpf_dirty = 0x0001,
xpf_deleted = 0x0002,
xpf_created = 0x0004,
xpf_modified = 0x0008,
xpf_tracking = 0x0010,
xpf_processed = 0x0020,
xpf_skip = 0x0040,
xpf_moved = 0x0080,
xpf_acl_enabled = 0x0100,
xpf_acl_read = 0x0200,
xpf_acl_write = 0x0400,
xpf_acl_deny = 0x0800,
xpf_acl_create = 0x1000,
xpf_acl_denied = 0x2000,
};
typedef struct xml_private_s
{
long check;
uint32_t flags;
char *user;
GListPtr acls;
GListPtr deleted_objs;
} xml_private_t;
typedef struct xml_acl_s {
enum xml_private_flags mode;
char *xpath;
} xml_acl_t;
typedef struct xml_deleted_obj_s {
char *path;
int position;
} xml_deleted_obj_t;
/* *INDENT-OFF* */
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 xmlNode *subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right, gboolean * changed);
static xmlNode *find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact);
static int add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update);
static bool __xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode);
const char *__xml_acl_to_text(enum xml_private_flags flags);
#define CHUNK_SIZE 1024
static inline bool TRACKING_CHANGES(xmlNode *xml)
{
if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
return FALSE;
} else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
return TRUE;
}
return FALSE;
}
#define buffer_print(buffer, max, offset, fmt, args...) do { \
int rc = (max); \
if(buffer) { \
rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
} \
if(buffer && rc < 0) { \
crm_perror(LOG_ERR, "snprintf failed at offset %d", offset); \
(buffer)[(offset)] = 0; \
break; \
} else if(rc >= ((max) - (offset))) { \
char *tmp = NULL; \
(max) = QB_MAX(CHUNK_SIZE, (max) * 2); \
tmp = realloc_safe((buffer), (max)); \
CRM_ASSERT(tmp); \
(buffer) = tmp; \
} 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 ((*buffer) == NULL || spaces >= ((*max) - (*offset))) {
(*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
(*buffer) = realloc_safe((*buffer), (*max));
}
memset((*buffer) + (*offset), ' ', spaces);
(*offset) += spaces;
}
}
static void
set_parent_flag(xmlNode *xml, long flag)
{
for(; xml; xml = xml->parent) {
xml_private_t *p = xml->_private;
if(p == NULL) {
/* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
} else {
p->flags |= flag;
/* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
}
}
}
static void
set_doc_flag(xmlNode *xml, long flag)
{
if(xml && xml->doc && xml->doc->_private){
/* During calls to xmlDocCopyNode(), xml->doc may be unset */
xml_private_t *p = xml->doc->_private;
p->flags |= flag;
/* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
}
}
static void
__xml_node_dirty(xmlNode *xml)
{
set_doc_flag(xml, xpf_dirty);
set_parent_flag(xml, xpf_dirty);
}
static void
__xml_node_clean(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_private_t *p = xml->_private;
if(p) {
p->flags = 0;
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_node_clean(cIter);
}
}
static void
crm_node_created(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_private_t *p = xml->_private;
if(p && TRACKING_CHANGES(xml)) {
if(is_not_set(p->flags, xpf_created)) {
p->flags |= xpf_created;
__xml_node_dirty(xml);
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
crm_node_created(cIter);
}
}
}
static void
crm_attr_dirty(xmlAttr *a)
{
xmlNode *parent = a->parent;
xml_private_t *p = NULL;
p = a->_private;
p->flags |= (xpf_dirty|xpf_modified);
p->flags = (p->flags & ~xpf_deleted);
/* crm_trace("Setting flag %x due to %s[@id=%s, @%s=%s]", */
/* xpf_dirty, parent?parent->name:NULL, ID(parent), a->name, a->children->content); */
__xml_node_dirty(parent);
}
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 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;
}
#define XML_PRIVATE_MAGIC (long) 0x81726354
static void
__xml_acl_free(void *data)
{
if(data) {
xml_acl_t *acl = data;
free(acl->xpath);
free(acl);
}
}
static void
__xml_deleted_obj_free(void *data)
{
if(data) {
xml_deleted_obj_t *deleted_obj = data;
free(deleted_obj->path);
free(deleted_obj);
}
}
static void
__xml_private_clean(xml_private_t *p)
{
if(p) {
CRM_ASSERT(p->check == XML_PRIVATE_MAGIC);
free(p->user);
p->user = NULL;
if(p->acls) {
g_list_free_full(p->acls, __xml_acl_free);
p->acls = NULL;
}
if(p->deleted_objs) {
g_list_free_full(p->deleted_objs, __xml_deleted_obj_free);
p->deleted_objs = NULL;
}
}
}
static void
__xml_private_free(xml_private_t *p)
{
__xml_private_clean(p);
free(p);
}
static void
pcmkDeregisterNode(xmlNodePtr node)
{
__xml_private_free(node->_private);
}
static void
pcmkRegisterNode(xmlNodePtr node)
{
xml_private_t *p = NULL;
switch(node->type) {
case XML_ELEMENT_NODE:
case XML_DOCUMENT_NODE:
case XML_ATTRIBUTE_NODE:
case XML_COMMENT_NODE:
p = calloc(1, sizeof(xml_private_t));
p->check = XML_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
p->flags |= (xpf_dirty|xpf_created);
node->_private = p;
break;
case XML_TEXT_NODE:
case XML_DTD_NODE:
case XML_CDATA_SECTION_NODE:
break;
default:
/* Ignore */
crm_trace("Ignoring %p %d", node, node->type);
CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
break;
}
if(p && TRACKING_CHANGES(node)) {
/* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
* not hooked up at the point we are called
*/
set_doc_flag(node, xpf_dirty);
__xml_node_dirty(node);
}
}
static xml_acl_t *
__xml_acl_create(xmlNode * xml, xmlNode *target, enum xml_private_flags mode)
{
xml_acl_t *acl = NULL;
xml_private_t *p = NULL;
const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
if(tag == NULL) {
/* Compatibility handling for pacemaker < 1.1.12 */
tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
}
if(ref == NULL) {
/* Compatibility handling for pacemaker < 1.1.12 */
ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
}
if(target == NULL || target->doc == NULL || target->doc->_private == NULL){
CRM_ASSERT(target);
CRM_ASSERT(target->doc);
CRM_ASSERT(target->doc->_private);
return NULL;
} else if (tag == NULL && ref == NULL && xpath == NULL) {
crm_trace("No criteria %p", xml);
return NULL;
}
p = target->doc->_private;
acl = calloc(1, sizeof(xml_acl_t));
if (acl) {
const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
acl->mode = mode;
if(xpath) {
acl->xpath = strdup(xpath);
crm_trace("Using xpath: %s", acl->xpath);
} else {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(tag) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//%s", tag);
} else {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//*");
}
if(ref || attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[");
}
if(ref) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@id='%s'", ref);
}
if(ref && attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, " and ");
}
if(attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@%s", attr);
}
if(ref || attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "]");
}
CRM_LOG_ASSERT(offset > 0);
acl->xpath = strdup(buffer);
crm_trace("Built xpath: %s", acl->xpath);
}
p->acls = g_list_append(p->acls, acl);
}
return acl;
}
static gboolean
__xml_acl_parse_entry(xmlNode * acl_top, xmlNode * acl_entry, xmlNode *target)
{
xmlNode *child = NULL;
for (child = __xml_first_child(acl_entry); child; child = __xml_next(child)) {
const char *tag = crm_element_name(child);
const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
tag = kind;
}
crm_trace("Processing %s %p", tag, child);
if(tag == NULL) {
CRM_ASSERT(tag != NULL);
} else if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
|| strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
const char *ref_role = crm_element_value(child, XML_ATTR_ID);
if (ref_role) {
xmlNode *role = NULL;
for (role = __xml_first_child(acl_top); role; role = __xml_next(role)) {
if (strcmp(XML_ACL_TAG_ROLE, (const char *)role->name) == 0) {
const char *role_id = crm_element_value(role, XML_ATTR_ID);
if (role_id && strcmp(ref_role, role_id) == 0) {
crm_debug("Unpacking referenced role: %s", role_id);
__xml_acl_parse_entry(acl_top, role, target);
break;
}
}
}
}
} else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_read);
} else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_write);
} else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_deny);
} else {
crm_warn("Unknown ACL entry: %s/%s", tag, kind);
}
}
return TRUE;
}
/*
<acls>
<acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
<acl_role id="auto-l33t-haxor">
<acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
</acl_role>
<acl_target id="niceguy">
<role id="observer"/>
</acl_target>
<acl_role id="observer">
<acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
<acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
<acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
</acl_role>
<acl_target id="badidea"><role id="auto-badidea"/></acl_target>
<acl_role id="auto-badidea">
<acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
<acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
</acl_role>
</acls>
*/
const char *
__xml_acl_to_text(enum xml_private_flags flags)
{
if(is_set(flags, xpf_acl_deny)) {
return "deny";
}
if(is_set(flags, xpf_acl_write)) {
return "read/write";
}
if(is_set(flags, xpf_acl_read)) {
return "read";
}
return "none";
}
static void
__xml_acl_apply(xmlNode *xml)
{
GListPtr aIter = NULL;
xml_private_t *p = NULL;
xmlXPathObjectPtr xpathObj = NULL;
if(xml_acl_enabled(xml) == FALSE) {
p = xml->doc->_private;
crm_trace("Not applying ACLs for %s", p->user);
return;
}
p = xml->doc->_private;
for(aIter = p->acls; aIter != NULL; aIter = aIter->next) {
int max = 0, lpc = 0;
xml_acl_t *acl = aIter->data;
xpathObj = xpath_search(xml, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
char *path = xml_get_path(match);
p = match->_private;
crm_trace("Applying %x to %s for %s", acl->mode, path, acl->xpath);
#ifdef SUSE_ACL_COMPAT
if(is_not_set(p->flags, acl->mode)) {
if(is_set(p->flags, xpf_acl_read)
|| is_set(p->flags, xpf_acl_write)
|| is_set(p->flags, xpf_acl_deny)) {
crm_config_warn("Configuration element %s is matched by multiple ACL rules, only the first applies ('%s' wins over '%s')",
path, __xml_acl_to_text(p->flags), __xml_acl_to_text(acl->mode));
free(path);
continue;
}
}
#endif
p->flags |= acl->mode;
free(path);
}
crm_trace("Now enforcing ACL: %s (%d matches)", acl->xpath, max);
freeXpathObject(xpathObj);
}
p = xml->_private;
if(is_not_set(p->flags, xpf_acl_read) && is_not_set(p->flags, xpf_acl_write)) {
p->flags |= xpf_acl_deny;
p = xml->doc->_private;
crm_info("Enforcing default ACL for %s to %s", p->user, crm_element_name(xml));
}
}
static void
__xml_acl_unpack(xmlNode *source, xmlNode *target, const char *user)
{
#if ENABLE_ACL
xml_private_t *p = NULL;
if(target == NULL || target->doc == NULL || target->doc->_private == NULL) {
return;
}
p = target->doc->_private;
if(pcmk_acl_required(user) == FALSE) {
crm_trace("no acls needed for '%s'", user);
} else if(p->acls == NULL) {
xmlNode *acls = get_xpath_object("//"XML_CIB_TAG_ACLS, source, LOG_TRACE);
free(p->user);
p->user = strdup(user);
if(acls) {
xmlNode *child = NULL;
for (child = __xml_first_child(acls); child; child = __xml_next(child)) {
const char *tag = crm_element_name(child);
if (strcmp(tag, XML_ACL_TAG_USER) == 0 || strcmp(tag, XML_ACL_TAG_USERv1) == 0) {
const char *id = crm_element_value(child, XML_ATTR_ID);
if(id && strcmp(id, user) == 0) {
crm_debug("Unpacking ACLs for %s", id);
__xml_acl_parse_entry(acls, child, target);
}
}
}
}
}
#endif
}
static inline bool
__xml_acl_mode_test(enum xml_private_flags allowed, enum xml_private_flags requested)
{
if(is_set(allowed, xpf_acl_deny)) {
return FALSE;
} else if(is_set(allowed, requested)) {
return TRUE;
} else if(is_set(requested, xpf_acl_read) && is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_created)) {
return TRUE;
}
return FALSE;
}
/* rc = TRUE if orig_cib has been filtered
* That means '*result' rather than 'xml' should be exploited afterwards
*/
static bool
__xml_purge_attributes(xmlNode *xml)
{
xmlNode *child = NULL;
xmlAttr *xIter = NULL;
bool readable_children = FALSE;
xml_private_t *p = xml->_private;
if(__xml_acl_mode_test(p->flags, xpf_acl_read)) {
crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
return TRUE;
}
xIter = crm_first_attr(xml);
while(xIter != NULL) {
xmlAttr *tmp = xIter;
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
xmlUnsetProp(xml, tmp->name);
}
child = __xml_first_child(xml);
while ( child != NULL ) {
xmlNode *tmp = child;
child = __xml_next(child);
readable_children |= __xml_purge_attributes(tmp);
}
if(readable_children == FALSE) {
free_xml(xml); /* Nothing readable under here, purge completely */
}
return readable_children;
}
bool
xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml, xmlNode ** result)
{
GListPtr aIter = NULL;
xmlNode *target = NULL;
xml_private_t *p = NULL;
xml_private_t *doc = NULL;
*result = NULL;
if(xml == NULL || pcmk_acl_required(user) == FALSE) {
crm_trace("no acls needed for '%s'", user);
return FALSE;
}
crm_trace("filtering copy of %p for '%s'", xml, user);
target = copy_xml(xml);
if(target == NULL) {
return TRUE;
}
__xml_acl_unpack(acl_source, target, user);
set_doc_flag(target, xpf_acl_enabled);
__xml_acl_apply(target);
doc = target->doc->_private;
for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
int max = 0;
xml_acl_t *acl = aIter->data;
if(acl->mode != xpf_acl_deny) {
/* Nothing to do */
} else if(acl->xpath) {
int lpc = 0;
xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
crm_trace("Purging attributes from %s", acl->xpath);
if(__xml_purge_attributes(match) == FALSE && match == target) {
crm_trace("No access to the entire document for %s", user);
freeXpathObject(xpathObj);
return TRUE;
}
}
crm_trace("Enforced ACL %s (%d matches)", acl->xpath, max);
freeXpathObject(xpathObj);
}
}
p = target->_private;
if(is_set(p->flags, xpf_acl_deny) && __xml_purge_attributes(target) == FALSE) {
crm_trace("No access to the entire document for %s", user);
return TRUE;
}
if(doc->acls) {
g_list_free_full(doc->acls, __xml_acl_free);
doc->acls = NULL;
} else {
crm_trace("Ordinary user '%s' cannot access the CIB without any defined ACLs", doc->user);
free_xml(target);
target = NULL;
}
if(target) {
*result = target;
}
return TRUE;
}
static void
__xml_acl_post_process(xmlNode * xml)
{
xmlNode *cIter = __xml_first_child(xml);
xml_private_t *p = xml->_private;
if(is_set(p->flags, xpf_created)) {
xmlAttr *xIter = NULL;
char *path = xml_get_path(xml);
/* Always allow new scaffolding (e.g. node with no attributes or only an
* 'id'), except in the ACLs section
*/
for (xIter = crm_first_attr(xml); xIter != NULL; xIter = xIter->next) {
const char *prop_name = (const char *)xIter->name;
if (strcmp(prop_name, XML_ATTR_ID) == 0 && strstr(path, "/"XML_CIB_TAG_ACLS"/") == NULL) {
/* Delay the acl check */
continue;
} else if(__xml_acl_check(xml, NULL, xpf_acl_write)) {
crm_trace("Creation of %s=%s is allowed", crm_element_name(xml), ID(xml));
break;
} else {
crm_trace("Cannot add new node %s at %s", crm_element_name(xml), path);
if(xml != xmlDocGetRootElement(xml->doc)) {
xmlUnlinkNode(xml);
xmlFreeNode(xml);
}
free(path);
return;
}
}
free(path);
}
while (cIter != NULL) {
xmlNode *child = cIter;
cIter = __xml_next(cIter); /* In case it is free'd */
__xml_acl_post_process(child);
}
}
bool
xml_acl_denied(xmlNode *xml)
{
if(xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_denied);
}
return FALSE;
}
void
xml_acl_disable(xmlNode *xml)
{
if(xml_acl_enabled(xml)) {
xml_private_t *p = xml->doc->_private;
/* Catch anything that was created but shouldn't have been */
__xml_acl_apply(xml);
__xml_acl_post_process(xml);
clear_bit(p->flags, xpf_acl_enabled);
}
}
bool
xml_acl_enabled(xmlNode *xml)
{
if(xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_enabled);
}
return FALSE;
}
void
xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
{
xml_accept_changes(xml);
crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
set_doc_flag(xml, xpf_tracking);
if(enforce_acls) {
if(acl_source == NULL) {
acl_source = xml;
}
set_doc_flag(xml, xpf_acl_enabled);
__xml_acl_unpack(acl_source, xml, user);
__xml_acl_apply(xml);
}
}
bool xml_tracking_changes(xmlNode * xml)
{
if(xml == NULL) {
return FALSE;
} else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
return TRUE;
}
return FALSE;
}
bool xml_document_dirty(xmlNode *xml)
{
if(xml != NULL && xml->doc && xml->doc->_private) {
xml_private_t *doc = xml->doc->_private;
return is_set(doc->flags, xpf_dirty);
}
return FALSE;
}
/*
<diff format="2.0">
<version>
<source admin_epoch="1" epoch="2" num_updates="3"/>
<target admin_epoch="1" epoch="3" num_updates="0"/>
</version>
<change operation="add" xpath="/cib/configuration/nodes">
<node id="node2" uname="node2" description="foo"/>
</change>
<change operation="add" xpath="/cib/configuration/nodes/node[node2]">
<instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
<nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
</instance_attributes>
</change>
<change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
<change-list>
<change-attr operation="set" name="type" value="member"/>
<change-attr operation="unset" name="description"/>
</change-list>
<change-result>
<node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
<change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
<change-list>
<change-attr operation="set" name="description" value="some grabage here"/>
</change-list>
<change-result>
<group id="g1" description="some grabage here"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
<change-list>
<change-attr operation="set" name="oper" value="member"/>
<change-attr operation="set" name="operation_key" value="Fence_start_0"/>
<change-attr operation="set" name="operation" value="start"/>
<change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="call-id" value="2"/>
<change-attr operation="set" name="rc-code" value="0"/>
</change-list>
<change-result>
<lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate" transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
</change-result>
</change>
</diff>
*/
static int __xml_offset(xmlNode *xml)
{
int position = 0;
xmlNode *cIter = NULL;
for(cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
if(is_not_set(p->flags, xpf_skip)) {
position++;
}
}
return position;
}
static int __xml_offset_no_deletions(xmlNode *xml)
{
int position = 0;
xmlNode *cIter = NULL;
for(cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
if(is_not_set(p->flags, xpf_deleted)) {
position++;
}
}
return position;
}
static void
__xml_build_changes(xmlNode * xml, xmlNode *patchset)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xmlNode *change = NULL;
xml_private_t *p = xml->_private;
if(patchset && is_set(p->flags, xpf_created)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml->parent, buffer, offset) > 0) {
int position = __xml_offset_no_deletions(xml);
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "create");
crm_xml_add(change, XML_DIFF_PATH, buffer);
crm_xml_add_int(change, XML_DIFF_POSITION, position);
add_node_copy(change, xml);
}
return;
}
for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
xmlNode *attr = NULL;
p = pIter->_private;
if(is_not_set(p->flags, xpf_deleted) && is_not_set(p->flags, xpf_dirty)) {
continue;
}
if(change == NULL) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "modify");
crm_xml_add(change, XML_DIFF_PATH, buffer);
change = create_xml_node(change, XML_DIFF_LIST);
}
}
attr = create_xml_node(change, XML_DIFF_ATTR);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
if(p->flags & xpf_deleted) {
crm_xml_add(attr, XML_DIFF_OP, "unset");
} else {
const char *value = crm_element_value(xml, (const char *)pIter->name);
crm_xml_add(attr, XML_DIFF_OP, "set");
crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
}
}
if(change) {
xmlNode *result = NULL;
change = create_xml_node(change->parent, XML_DIFF_RESULT);
result = create_xml_node(change, (const char *)xml->name);
for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
const char *value = crm_element_value(xml, (const char *)pIter->name);
p = pIter->_private;
if (is_not_set(p->flags, xpf_deleted)) {
crm_xml_add(result, (const char *)pIter->name, value);
}
}
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_build_changes(cIter, patchset);
}
p = xml->_private;
if(patchset && is_set(p->flags, xpf_moved)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
crm_trace("%s.%s moved to position %d", xml->name, ID(xml), __xml_offset(xml));
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "move");
crm_xml_add(change, XML_DIFF_PATH, buffer);
crm_xml_add_int(change, XML_DIFF_POSITION, __xml_offset_no_deletions(xml));
}
}
}
static void
__xml_accept_changes(xmlNode * xml)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xml_private_t *p = xml->_private;
p->flags = xpf_none;
pIter = crm_first_attr(xml);
while (pIter != NULL) {
const xmlChar *name = pIter->name;
p = pIter->_private;
pIter = pIter->next;
if(p->flags & xpf_deleted) {
xml_remove_prop(xml, (const char *)name);
} else {
p->flags = xpf_none;
}
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_accept_changes(cIter);
}
}
static bool
is_config_change(xmlNode *xml)
{
GListPtr gIter = NULL;
xml_private_t *p = NULL;
xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
if(config) {
p = config->_private;
}
if(p && is_set(p->flags, xpf_dirty)) {
return TRUE;
}
if(xml->doc && xml->doc->_private) {
p = xml->doc->_private;
for(gIter = p->deleted_objs; gIter; gIter = gIter->next) {
xml_deleted_obj_t *deleted_obj = gIter->data;
if(strstr(deleted_obj->path, "/"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION) != NULL) {
return TRUE;
}
}
}
return FALSE;
}
static void
xml_repair_v1_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed)
{
int lpc = 0;
xmlNode *cib = NULL;
xmlNode *diff_child = NULL;
const char *tag = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
if (local_diff == NULL) {
crm_trace("Nothing to do");
return;
}
tag = "diff-removed";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for(lpc = 0; last && lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(last, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
if(changed || lpc == 2) {
crm_xml_add(cib, vfields[lpc], value);
}
}
tag = "diff-added";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for(lpc = 0; next && lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(next, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
}
if (next) {
xmlAttrPtr xIter = NULL;
for (xIter = next->properties; xIter; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
const char *p_value = crm_element_value(next, p_name);
xmlSetProp(cib, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
}
crm_log_xml_explicit(local_diff, "Repaired-diff");
}
static xmlNode *
xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress)
{
xmlNode *patchset = diff_xml_object(source, target, suppress);
if(patchset) {
CRM_LOG_ASSERT(xml_document_dirty(target));
xml_repair_v1_diff(source, target, patchset, config);
crm_xml_add(patchset, "format", "1");
}
return patchset;
}
static xmlNode *
xml_create_patchset_v2(xmlNode *source, xmlNode *target)
{
int lpc = 0;
GListPtr gIter = NULL;
xml_private_t *doc = NULL;
xmlNode *v = NULL;
xmlNode *version = NULL;
xmlNode *patchset = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
CRM_ASSERT(target);
if(xml_document_dirty(target) == FALSE) {
return NULL;
}
CRM_ASSERT(target->doc);
doc = target->doc->_private;
patchset = create_xml_node(NULL, XML_TAG_DIFF);
crm_xml_add_int(patchset, "format", 2);
version = create_xml_node(patchset, XML_DIFF_VERSION);
v = create_xml_node(version, XML_DIFF_VSOURCE);
for(lpc = 0; lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(source, vfields[lpc]);
if(value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
v = create_xml_node(version, XML_DIFF_VTARGET);
for(lpc = 0; lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(target, vfields[lpc]);
if(value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
xml_deleted_obj_t *deleted_obj = gIter->data;
xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "delete");
crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
if (deleted_obj->position >= 0) {
crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
}
}
__xml_build_changes(target, patchset);
return patchset;
}
static gboolean patch_legacy_mode(void)
{
static gboolean init = TRUE;
static gboolean legacy = FALSE;
if(init) {
init = FALSE;
legacy = daemon_option_enabled("cib", "legacy");
if(legacy) {
crm_notice("Enabled legacy mode");
}
}
return legacy;
}
xmlNode *
xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
{
int counter = 0;
bool config = FALSE;
xmlNode *patch = NULL;
const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
xml_acl_disable(target);
if(xml_document_dirty(target) == FALSE) {
crm_trace("No change %d", format);
return NULL; /* No change */
}
config = is_config_change(target);
if(config_changed) {
*config_changed = config;
}
if(manage_version && config) {
crm_trace("Config changed %d", format);
crm_xml_add(target, XML_ATTR_NUMUPDATES, "0");
crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
} else if(manage_version) {
crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, XML_ATTR_NUMUPDATES));
crm_xml_add_int(target, XML_ATTR_NUMUPDATES, counter+1);
}
if(format == 0) {
if(patch_legacy_mode()) {
format = 1;
} else if(compare_version("3.0.8", version) < 0) {
format = 2;
} else {
format = 1;
}
crm_trace("Using patch format %d for version: %s", format, version);
}
switch(format) {
case 1:
patch = xml_create_patchset_v1(source, target, config, FALSE);
break;
case 2:
patch = xml_create_patchset_v2(source, target);
break;
default:
crm_err("Unknown patch format: %d", format);
return NULL;
}
return patch;
}
void
patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
{
int format = 1;
const char *version = NULL;
char *digest = NULL;
if (patch == NULL || source == NULL || target == NULL) {
return;
}
/* NOTE: We should always call xml_accept_changes() before calculating digest. */
/* Otherwise, with an on-tracking dirty target, we could get a wrong digest. */
CRM_LOG_ASSERT(xml_document_dirty(target) == FALSE);
crm_element_value_int(patch, "format", &format);
if (format > 1 && with_digest == FALSE) {
return;
}
version = crm_element_value(source, XML_ATTR_CRM_VERSION);
digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
crm_xml_add(patch, XML_ATTR_DIGEST, digest);
free(digest);
return;
}
static void
__xml_log_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options);
void
xml_log_patchset(uint8_t log_level, const char *function, xmlNode * patchset)
{
int format = 1;
xmlNode *child = NULL;
xmlNode *added = NULL;
xmlNode *removed = NULL;
gboolean is_first = TRUE;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
int options = xml_log_option_formatted;
static struct qb_log_callsite *patchset_cs = NULL;
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset", log_level, __LINE__, 0);
}
if (patchset == NULL) {
crm_trace("Empty patch");
return;
} else if (log_level == 0) {
/* Log to stdout */
} else if (crm_is_callsite_active(patchset_cs, log_level, 0) == FALSE) {
return;
}
xml_patch_versions(patchset, add, del);
fmt = crm_element_value(patchset, "format");
digest = crm_element_value(patchset, XML_ATTR_DIGEST);
if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
} else if (patchset != NULL && (add[0] || add[1] || add[2])) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"%s: Local-only Change: %d.%d.%d", function ? function : "",
add[0], add[1], add[2]);
}
crm_element_value_int(patchset, "format", &format);
if(format == 2) {
xmlNode *change = NULL;
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if(op == NULL) {
} else if(strcmp(op, "create") == 0) {
int lpc = 0, max = 0;
char *prefix = crm_strdup_printf("++ %s: ", xpath);
max = strlen(prefix);
__xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
0, xml_log_option_formatted|xml_log_option_open);
for(lpc = 2; lpc < max; lpc++) {
prefix[lpc] = ' ';
}
__xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
0, xml_log_option_formatted|xml_log_option_close|xml_log_option_children);
free(prefix);
} else if(strcmp(op, "move") == 0) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+~ %s moved to offset %s", xpath, crm_element_value(change, XML_DIFF_POSITION));
} else if(strcmp(op, "modify") == 0) {
xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
char buffer_set[XML_BUFFER_SIZE];
char buffer_unset[XML_BUFFER_SIZE];
int o_set = 0;
int o_unset = 0;
buffer_set[0] = 0;
buffer_unset[0] = 0;
for (child = __xml_first_child(clist); child != NULL; child = __xml_next(child)) {
const char *name = crm_element_value(child, "name");
op = crm_element_value(child, XML_DIFF_OP);
if(op == NULL) {
} else if(strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, "value");
if(o_set > 0) {
o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, ", ");
}
o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, "@%s=%s", name, value);
} else if(strcmp(op, "unset") == 0) {
if(o_unset > 0) {
o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, ", ");
}
o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, "@%s", name);
}
}
if(o_set) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+ %s: %s", xpath, buffer_set);
}
if(o_unset) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s: %s", xpath, buffer_unset);
}
} else if(strcmp(op, "delete") == 0) {
int position = -1;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if (position >= 0) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)", xpath, position);
} else {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", xpath);
}
}
}
return;
}
if (log_level < LOG_DEBUG || function == NULL) {
options |= xml_log_option_diff_short;
}
removed = find_xml_node(patchset, "diff-removed", FALSE);
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_alias(log_level, __FILE__, function, __LINE__, " --- ");
}
}
is_first = TRUE;
added = find_xml_node(patchset, "diff-added", FALSE);
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_alias(log_level, __FILE__, function, __LINE__, " +++ ");
}
}
}
void
xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
{
GListPtr gIter = NULL;
xml_private_t *doc = NULL;
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
doc = xml->doc->_private;
if(is_not_set(doc->flags, xpf_dirty)) {
return;
}
for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
xml_deleted_obj_t *deleted_obj = gIter->data;
if (deleted_obj->position >= 0) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)",
deleted_obj->path, deleted_obj->position);
} else {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s",
deleted_obj->path);
}
}
log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
xml_log_option_formatted|xml_log_option_dirty_add);
}
void
xml_accept_changes(xmlNode * xml)
{
xmlNode *top = NULL;
xml_private_t *doc = NULL;
if(xml == NULL) {
return;
}
crm_trace("Accepting changes to %p", xml);
doc = xml->doc->_private;
top = xmlDocGetRootElement(xml->doc);
__xml_private_clean(xml->doc->_private);
if(is_not_set(doc->flags, xpf_dirty)) {
doc->flags = xpf_none;
return;
}
doc->flags = xpf_none;
__xml_accept_changes(top);
}
static xmlNode *
find_element(xmlNode *haystack, xmlNode *needle, gboolean exact)
{
CRM_CHECK(needle != NULL, return NULL);
return (needle->type == XML_COMMENT_NODE)?
find_xml_comment(haystack, needle, exact)
: find_entity(haystack, crm_element_name(needle), ID(needle));
}
/* Simplified version for applying v1-style XML patches */
static void
__subtract_xml_object(xmlNode * target, xmlNode * patch)
{
xmlNode *patch_child = NULL;
xmlNode *cIter = NULL;
xmlAttrPtr xIter = NULL;
char *id = NULL;
const char *name = NULL;
const char *value = NULL;
if (target == NULL || patch == NULL) {
return;
}
if (target->type == XML_COMMENT_NODE) {
gboolean dummy;
subtract_xml_comment(target->parent, target, patch, &dummy);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
/* check for XML_DIFF_MARKER in a child */
id = crm_element_value_copy(target, XML_ATTR_ID);
value = crm_element_value(patch, 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);
free_xml(target);
free(id);
return;
}
for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
/* Removing and then restoring the id field would change the ordering of properties */
if (safe_str_neq(p_name, XML_ATTR_ID)) {
xml_remove_prop(target, p_name);
}
}
/* changes to child objects */
cIter = __xml_first_child(target);
while (cIter) {
xmlNode *target_child = cIter;
cIter = __xml_next(cIter);
patch_child = find_element(patch, target_child, FALSE);
__subtract_xml_object(target_child, patch_child);
}
free(id);
}
static void
__add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * patch)
{
xmlNode *patch_child = NULL;
xmlNode *target_child = NULL;
xmlAttrPtr xIter = NULL;
const char *id = NULL;
const char *name = NULL;
const char *value = NULL;
if (patch == NULL) {
return;
} else if (parent == NULL && target == NULL) {
return;
}
/* check for XML_DIFF_MARKER in a child */
value = crm_element_value(patch, XML_DIFF_MARKER);
if (target == NULL
&& value != NULL
&& strcmp(value, "added:top") == 0) {
id = ID(patch);
name = crm_element_name(patch);
crm_trace("We are the root of the addition: %s.id=%s", name, id);
add_node_copy(parent, patch);
return;
} else if(target == NULL) {
id = ID(patch);
name = crm_element_name(patch);
crm_err("Could not locate: %s.id=%s", name, id);
return;
}
if (target->type == XML_COMMENT_NODE) {
add_xml_comment(parent, target, patch);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
const char *p_value = crm_element_value(patch, p_name);
xml_remove_prop(target, p_name); /* Preserve the patch order */
crm_xml_add(target, p_name, p_value);
}
/* changes to child objects */
for (patch_child = __xml_first_child(patch); patch_child != NULL;
patch_child = __xml_next(patch_child)) {
target_child = find_element(target, patch_child, FALSE);
__add_xml_object(target, target_child, patch_child);
}
}
/*!
* \internal
* \brief Find additions or removals in a patch set
*
* \param[in] patchset XML of patch
* \param[in] format Patch version
* \param[in] added TRUE if looking for additions, FALSE if removals
* \param[in,out] patch_node Will be set to node if found
*
* \return TRUE if format is valid, FALSE if invalid
*/
static bool
find_patch_xml_node(xmlNode *patchset, int format, bool added,
xmlNode **patch_node)
{
xmlNode *cib_node;
const char *label;
switch(format) {
case 1:
label = added? "diff-added" : "diff-removed";
*patch_node = find_xml_node(patchset, label, FALSE);
cib_node = find_xml_node(*patch_node, "cib", FALSE);
if (cib_node != NULL) {
*patch_node = cib_node;
}
break;
case 2:
label = added? "target" : "source";
*patch_node = find_xml_node(patchset, "version", FALSE);
*patch_node = find_xml_node(*patch_node, label, FALSE);
break;
default:
crm_warn("Unknown patch format: %d", format);
*patch_node = NULL;
return FALSE;
}
return TRUE;
}
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
{
int lpc = 0;
int format = 1;
xmlNode *tmp = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
crm_element_value_int(patchset, "format", &format);
/* Process removals */
if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
return -EINVAL;
}
if (tmp) {
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
}
}
/* Process additions */
if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
return -EINVAL;
}
if (tmp) {
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
}
}
return pcmk_ok;
}
static int
xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
{
int lpc = 0;
bool changed = FALSE;
int this[] = { 0, 0, 0 };
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
if (this[lpc] < 0) {
this[lpc] = 0;
}
}
/* Set some defaults in case nothing is present */
add[0] = this[0];
add[1] = this[1];
add[2] = this[2] + 1;
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
del[lpc] = this[lpc];
}
xml_patch_versions(patchset, add, del);
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
if(this[lpc] < del[lpc]) {
crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc],
this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]);
return -pcmk_err_diff_resync;
} else if(this[lpc] > del[lpc]) {
crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc],
this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset);
crm_log_xml_info(patchset, "OldPatch");
return -pcmk_err_old_data;
}
}
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
if(add[lpc] > del[lpc]) {
changed = TRUE;
}
}
if(changed == FALSE) {
crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]);
return -pcmk_err_old_data;
}
crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
add[0], add[1], add[2], this[0], this[1], this[2]);
return pcmk_ok;
}
static int
xml_apply_patchset_v1(xmlNode *xml, xmlNode *patchset)
{
int rc = pcmk_ok;
int root_nodes_seen = 0;
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
xmlNode *old = copy_xml(xml);
crm_trace("Subtraction Phase");
for (child_diff = __xml_first_child(removed); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
if (root_nodes_seen == 0) {
__subtract_xml_object(xml, child_diff);
}
root_nodes_seen++;
}
if (root_nodes_seen > 1) {
crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
rc = -ENOTUNIQ;
}
root_nodes_seen = 0;
crm_trace("Addition Phase");
if (rc == pcmk_ok) {
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, rc = FALSE);
if (root_nodes_seen == 0) {
__add_xml_object(NULL, xml, child_diff);
}
root_nodes_seen++;
}
}
if (root_nodes_seen > 1) {
crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
rc = -ENOTUNIQ;
}
purge_diff_markers(xml); /* Purge prior to checking the digest */
free_xml(old);
return rc;
}
static xmlNode *
__first_xml_child_match(xmlNode *parent, const char *name, const char *id, int position)
{
xmlNode *cIter = NULL;
for (cIter = __xml_first_child(parent); cIter != NULL; cIter = __xml_next(cIter)) {
if(strcmp((const char *)cIter->name, name) != 0) {
continue;
} else if(id) {
const char *cid = ID(cIter);
if(cid == NULL || strcmp(cid, id) != 0) {
continue;
}
}
/* The "position" makes sense only for XML comments for now */
if (cIter->type == XML_COMMENT_NODE
&& position >= 0
&& __xml_offset(cIter) != position) {
continue;
}
return cIter;
}
return NULL;
}
static xmlNode *
__xml_find_path(xmlNode *top, const char *key, int target_position)
{
xmlNode *target = (xmlNode*)top->doc;
char *id = malloc(XML_BUFFER_SIZE);
char *tag = malloc(XML_BUFFER_SIZE);
char *section = malloc(XML_BUFFER_SIZE);
char *current = strdup(key);
char *remainder = malloc(XML_BUFFER_SIZE);
int rc = 0;
while(current) {
rc = sscanf (current, "/%[^/]%s", section, remainder);
if(rc <= 0) {
crm_trace("Done");
break;
} else if(rc > 2) {
crm_trace("Aborting on %s", current);
target = NULL;
break;
} else if(tag && section) {
int f = sscanf (section, "%[^[][@id='%[^']", tag, id);
int current_position = -1;
/* The "target_position" is for the target tag */
if (rc == 1 && target_position >= 0) {
current_position = target_position;
}
switch(f) {
case 1:
target = __first_xml_child_match(target, tag, NULL, current_position);
break;
case 2:
target = __first_xml_child_match(target, tag, id, current_position);
break;
default:
crm_trace("Aborting on %s", section);
target = NULL;
break;
}
if(rc == 1 || target == NULL) {
crm_trace("Done");
break;
} else {
char *tmp = current;
current = remainder;
remainder = tmp;
}
}
}
if(target) {
char *path = (char *)xmlGetNodePath(target);
crm_trace("Found %s for %s", path, key);
free(path);
} else {
crm_debug("No match for %s", key);
}
free(remainder);
free(current);
free(section);
free(tag);
free(id);
return target;
}
static int
xml_apply_patchset_v2(xmlNode *xml, xmlNode *patchset)
{
int rc = pcmk_ok;
xmlNode *change = NULL;
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
xmlNode *match = NULL;
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
int position = -1;
crm_trace("Processing %s %s", change->name, op);
if(op == NULL) {
continue;
}
if(strcmp(op, "delete") == 0) {
crm_element_value_int(change, XML_DIFF_POSITION, &position);
}
#if 0
match = get_xpath_object(xpath, xml, LOG_TRACE);
#else
match = __xml_find_path(xml, xpath, position);
#endif
crm_trace("Performing %s on %s with %p", op, xpath, match);
if(match == NULL && strcmp(op, "delete") == 0) {
crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
continue;
} else if(match == NULL) {
crm_err("No %s match for %s in %p", op, xpath, xml->doc);
rc = -pcmk_err_diff_failed;
continue;
} else if(strcmp(op, "create") == 0) {
int position = 0;
xmlNode *child = NULL;
xmlNode *match_child = NULL;
match_child = match->children;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
while(match_child && position != __xml_offset(match_child)) {
match_child = match_child->next;
}
child = xmlDocCopyNode(change->children, match->doc, 1);
if(match_child) {
crm_trace("Adding %s at position %d", child->name, position);
xmlAddPrevSibling(match_child, child);
} else if(match->last) { /* Add to the end */
crm_trace("Adding %s at position %d (end)", child->name, position);
xmlAddNextSibling(match->last, child);
} else {
crm_trace("Adding %s at position %d (first)", child->name, position);
CRM_LOG_ASSERT(position == 0);
xmlAddChild(match, child);
}
crm_node_created(child);
} else if(strcmp(op, "move") == 0) {
int position = 0;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if(position != __xml_offset(match)) {
xmlNode *match_child = NULL;
int p = position;
if(p > __xml_offset(match)) {
p++; /* Skip ourselves */
}
CRM_ASSERT(match->parent != NULL);
match_child = match->parent->children;
while(match_child && p != __xml_offset(match_child)) {
match_child = match_child->next;
}
crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
match->name, position, __xml_offset(match), match->prev,
match_child?"next":"last", match_child?match_child:match->parent->last);
if(match_child) {
xmlAddPrevSibling(match_child, match);
} else {
CRM_ASSERT(match->parent->last != NULL);
xmlAddNextSibling(match->parent->last, match);
}
} else {
crm_trace("%s is already in position %d", match->name, position);
}
if(position != __xml_offset(match)) {
crm_err("Moved %s.%d to position %d instead of %d (%p)",
match->name, ID(match), __xml_offset(match), position, match->prev);
rc = -pcmk_err_diff_failed;
}
} else if(strcmp(op, "delete") == 0) {
free_xml(match);
} else if(strcmp(op, "modify") == 0) {
xmlAttr *pIter = crm_first_attr(match);
xmlNode *attrs = __xml_first_child(first_named_child(change, XML_DIFF_RESULT));
if(attrs == NULL) {
rc = -ENOMSG;
continue;
}
while(pIter != NULL) {
const char *name = (const char *)pIter->name;
pIter = pIter->next;
xml_remove_prop(match, name);
}
for (pIter = crm_first_attr(attrs); pIter != NULL; pIter = pIter->next) {
const char *name = (const char *)pIter->name;
const char *value = crm_element_value(attrs, name);
crm_xml_add(match, name, value);
}
} else {
crm_err("Unknown operation: %s", op);
}
}
return rc;
}
int
xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int format = 1;
int rc = pcmk_ok;
xmlNode *old = NULL;
const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
if(patchset == NULL) {
return rc;
}
xml_log_patchset(LOG_TRACE, __FUNCTION__, patchset);
crm_element_value_int(patchset, "format", &format);
if(check_version) {
rc = xml_patch_version_check(xml, patchset, format);
if(rc != pcmk_ok) {
return rc;
}
}
if(digest) {
/* Make it available for logging if the result doesn't have the expected digest */
old = copy_xml(xml);
}
if(rc == pcmk_ok) {
switch(format) {
case 1:
rc = xml_apply_patchset_v1(xml, patchset);
break;
case 2:
rc = xml_apply_patchset_v2(xml, patchset);
break;
default:
crm_err("Unknown patch format: %d", format);
rc = -EINVAL;
}
}
if(rc == pcmk_ok && digest) {
static struct qb_log_callsite *digest_cs = NULL;
char *new_digest = NULL;
char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
if (digest_cs == NULL) {
digest_cs =
qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
if (safe_str_neq(new_digest, digest)) {
crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest);
rc = -pcmk_err_diff_failed;
if (digest_cs && digest_cs->targets) {
save_xml_to_file(old, "PatchDigest:input", NULL);
save_xml_to_file(xml, "PatchDigest:result", NULL);
save_xml_to_file(patchset,"PatchDigest:diff", NULL);
} else {
crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
}
} else {
crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest);
}
free(new_digest);
free(version);
}
free_xml(old);
return rc;
}
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) {
const char *cid = ID(a_child);
if (id == NULL || (cid != NULL && strcmp(id, cid) == 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 = (int)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);
crm_node_created(child);
return child;
}
int
add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
{
add_node_copy(parent, child);
free_xml(child);
return 1;
}
static bool
__xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode)
{
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
CRM_ASSERT(xml->doc->_private);
#if ENABLE_ACL
{
if(TRACKING_CHANGES(xml) && xml_acl_enabled(xml)) {
int offset = 0;
xmlNode *parent = xml;
char buffer[XML_BUFFER_SIZE];
xml_private_t *docp = xml->doc->_private;
if(docp->acls == NULL) {
crm_trace("Ordinary user %s cannot access the CIB without any defined ACLs", docp->user);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
offset = __get_prefix(NULL, xml, buffer, offset);
if(name) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[@%s]", name);
}
CRM_LOG_ASSERT(offset > 0);
/* Walk the tree upwards looking for xml_acl_* flags
* - Creating an attribute requires write permissions for the node
* - Creating a child requires write permissions for the parent
*/
if(name) {
xmlAttr *attr = xmlHasProp(xml, (const xmlChar *)name);
if(attr && mode == xpf_acl_create) {
mode = xpf_acl_write;
}
}
while(parent && parent->_private) {
xml_private_t *p = parent->_private;
if(__xml_acl_mode_test(p->flags, mode)) {
return TRUE;
} else if(is_set(p->flags, xpf_acl_deny)) {
crm_trace("%x access denied to %s: parent", mode, buffer);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
parent = parent->parent;
}
crm_trace("%x access denied to %s: default", mode, buffer);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
}
#endif
return TRUE;
}
const char *
crm_xml_add(xmlNode * node, const char *name, const char *value)
{
bool dirty = FALSE;
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
if(TRACKING_CHANGES(node)) {
const char *old = crm_element_value(node, name);
if(old == NULL || value == NULL || strcmp(old, value) != 0) {
dirty = TRUE;
}
}
if(dirty && __xml_acl_check(node, name, xpf_acl_create) == FALSE) {
crm_trace("Cannot add %s=%s to %s", name, value, node->name);
return NULL;
}
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
if(dirty) {
crm_attr_dirty(attr);
}
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)
{
bool dirty = FALSE;
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(__xml_acl_check(node, name, xpf_acl_write) == FALSE) {
/* Create a fake object linked to doc->_private instead? */
crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
return NULL;
} else if (old_value != NULL && value == NULL) {
xml_remove_prop(node, name);
return NULL;
} else if (value == NULL) {
return NULL;
}
if(TRACKING_CHANGES(node)) {
if(old_value == NULL || value == NULL || strcmp(old_value, value) != 0) {
dirty = TRUE;
}
}
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
if(dirty) {
crm_attr_dirty(attr);
}
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) {
CRM_CHECK(name != NULL && name[0] == 0, return NULL);
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);
}
crm_node_created(node);
return node;
}
static inline int
__get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset)
{
const char *id = ID(xml);
if(offset == 0 && prefix == NULL && xml->parent) {
offset = __get_prefix(NULL, xml->parent, buffer, offset);
}
if(id) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s[@id='%s']", (const char *)xml->name, id);
} else if(xml->name) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s", (const char *)xml->name);
}
return offset;
}
char *
xml_get_path(xmlNode *xml)
{
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
return strdup(buffer);
}
return NULL;
}
static void
free_xml_with_position(xmlNode * child, int position)
{
if (child != NULL) {
xmlNode *top = NULL;
xmlDoc *doc = child->doc;
xml_private_t *p = child->_private;
if (doc != NULL) {
top = xmlDocGetRootElement(doc);
}
if (doc != NULL && top == child) {
/* Free everything */
xmlFreeDoc(doc);
} else if(__xml_acl_check(child, NULL, xpf_acl_write) == FALSE) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
__get_prefix(NULL, child, buffer, offset);
crm_trace("Cannot remove %s %x", buffer, p->flags);
return;
} else {
if(doc && TRACKING_CHANGES(child) && is_not_set(p->flags, xpf_created)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, child, buffer, offset) > 0) {
xml_deleted_obj_t *deleted_obj = calloc(1, sizeof(xml_deleted_obj_t));
crm_trace("Deleting %s %p from %p", buffer, child, doc);
deleted_obj->path = strdup(buffer);
deleted_obj->position = -1;
/* Record the "position" only for XML comments for now */
if (child->type == XML_COMMENT_NODE) {
if (position >= 0) {
deleted_obj->position = position;
} else {
deleted_obj->position = __xml_offset(child);
}
}
p = doc->_private;
p->deleted_objs = g_list_append(p->deleted_objs, deleted_obj);
set_doc_flag(child, xpf_dirty);
}
}
/* Free this particular subtree
* Make sure to unlink it from the parent first
*/
xmlUnlinkNode(child);
xmlFreeNode(child);
}
}
}
void
free_xml(xmlNode * child)
{
free_xml_with_position(child, -1);
}
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 *fmt, ...)
G_GNUC_PRINTF(2, 3);
static void
crm_xml_err(void *ctx, const char *fmt, ...)
{
va_list ap;
static struct qb_log_callsite *xml_error_cs = NULL;
if (xml_error_cs == NULL) {
xml_error_cs = qb_log_callsite_get(
__func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
}
va_start(ap, fmt);
if (xml_error_cs && xml_error_cs->targets) {
CRM_XML_LOG_BASE(LOG_ERR, TRUE,
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error",
TRUE, TRUE),
"XML Error: ", fmt, ap);
} else {
CRM_XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
}
va_end(ap);
}
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__,__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_LOG_ASSERT("Cannot parse an empty string");
} 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_LOG_ASSERT("String parsing error");
}
}
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 {
size_t next = XML_BUFFER_SIZE + data_length + 1;
if(next <= 0) {
crm_err("Buffer size exceeded at: %l + %d", data_length, XML_BUFFER_SIZE);
break;
}
xml_buffer = realloc_safe(xml_buffer, next);
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_safe(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';
if (rc != BZ_STREAM_END) {
crm_err("Couldn't 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;
gboolean uncompressed = TRUE;
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) {
uncompressed = !crm_ends_with_ext(filename, ".bz2");
}
if (filename == NULL) {
/* STDIN_FILENO == fileno(stdin) */
output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, xml_options);
} else if (uncompressed) {
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__,__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;
}
/*!
* \internal
* \brief Add a "last written" attribute to an XML node, set to current time
*
* \param[in] xml_node XML node to get attribute
*
* \return Value that was set, or NULL on error
*/
const char *
crm_xml_add_last_written(xmlNode *xml_node)
{
time_t now = time(NULL);
char *now_str = ctime(&now);
now_str[24] = EOS; /* replace the newline */
return crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str);
}
/*!
* \brief Sanitize a string so it is usable as an XML ID
*
* \param[in,out] id String to sanitize
*/
void
crm_xml_sanitize_id(char *id)
{
char *c;
for (c = id; *c; ++c) {
/* @TODO Sanitize more comprehensively */
switch (*c) {
case ':':
case '#':
*c = '.';
}
}
}
/*!
* \brief Set the ID of an XML element using a format
*
* \param[in,out] xml XML element
* \param[in] fmt printf-style format
* \param[in] ... any arguments required by format
*/
void
crm_xml_set_id(xmlNode *xml, const char *format, ...)
{
va_list ap;
int len = 0;
char *id = NULL;
/* equivalent to crm_strdup_printf() */
va_start(ap, format);
len = vasprintf(&id, format, ap);
va_end(ap);
CRM_ASSERT(len > 0);
crm_xml_sanitize_id(id);
crm_xml_add(xml, XML_ATTR_ID, id);
free(id);
}
static int
write_xml_stream(xmlNode * xml_node, const char *filename, FILE * stream, gboolean compress)
{
int res = 0;
char *buffer = NULL;
unsigned int out = 0;
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;
}
crm_log_xml_trace(xml_node, "Writing out");
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;
}
/* Don't report error if the file does not support synchronization */
if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) {
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 = NULL;
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_safe(text, *length);
for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
text[lpc] = text[lpc - offset];
}
memcpy(text + start, replace, offset + 1);
return text;
}
char *
crm_xml_escape(const char *text)
{
int index;
int changes = 0;
int length = 1 + strlen(text);
char *copy = strdup(text);
/*
* When xmlCtxtReadDoc() parses &lt; 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 escaping 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, "&lt;");
changes++;
break;
case '>':
copy = crm_xml_escape_shuffle(copy, index, &length, "&gt;");
changes++;
break;
case '"':
copy = crm_xml_escape_shuffle(copy, index, &length, "&quot;");
changes++;
break;
case '\'':
copy = crm_xml_escape_shuffle(copy, index, &length, "&apos;");
changes++;
break;
case '&':
copy = crm_xml_escape_shuffle(copy, index, &length, "&amp;");
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 = crm_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 = NULL;
xml_private_t *p = NULL;
CRM_ASSERT(buffer != NULL);
if (attr == NULL || attr->children == NULL) {
return;
}
p = attr->_private;
if (p && is_set(p->flags, xpf_deleted)) {
return;
}
p_name = (const char *)attr->name;
p_value = crm_xml_escape((const char *)attr->children->content);
buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
free(p_value);
}
static void
__xml_log_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
int max = 0;
int offset = 0;
const char *name = NULL;
const char *hidden = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
if(data == NULL) {
return;
}
name = crm_element_name(data);
if(is_set(options, xml_log_option_open)) {
char *buffer = NULL;
insert_prefix(options, &buffer, &offset, &max, depth);
if (data->type == XML_COMMENT_NODE) {
buffer_print(buffer, max, offset, "<!--%s-->", data->content);
} else {
buffer_print(buffer, max, offset, "<%s", name);
hidden = crm_element_value(data, "hidden");
for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
xml_private_t *p = pIter->_private;
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
char *p_copy = NULL;
if(is_set(p->flags, xpf_deleted)) {
continue;
} else 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) == FALSE) {
buffer_print(buffer, max, offset, "/>");
} else if(is_set(options, xml_log_option_children)) {
buffer_print(buffer, max, offset, ">");
} else {
buffer_print(buffer, max, offset, "/>");
}
}
do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
free(buffer);
}
if(data->type == XML_COMMENT_NODE) {
return;
} else if(xml_has_children(data) == FALSE) {
return;
} else if(is_set(options, xml_log_option_children)) {
offset = 0;
max = 0;
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_element(log_level, file, function, line, prefix, child, depth + 1, options|xml_log_option_open|xml_log_option_close);
}
}
if(is_set(options, xml_log_option_close)) {
char *buffer = NULL;
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(buffer);
}
}
static void
__xml_log_change_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
xml_private_t *p;
char *prefix_m = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
if(data == NULL) {
return;
}
p = data->_private;
prefix_m = strdup(prefix);
prefix_m[1] = '+';
if(is_set(p->flags, xpf_dirty) && is_set(p->flags, xpf_created)) {
/* Continue and log full subtree */
__xml_log_element(log_level, file, function, line,
prefix_m, data, depth, options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
} else if(is_set(p->flags, xpf_dirty)) {
char *spaces = calloc(80, 1);
int s_count = 0, s_max = 80;
char *prefix_del = NULL;
char *prefix_moved = NULL;
const char *flags = prefix;
insert_prefix(options, &spaces, &s_count, &s_max, depth);
prefix_del = strdup(prefix);
prefix_del[0] = '-';
prefix_del[1] = '-';
prefix_moved = strdup(prefix);
prefix_moved[1] = '~';
if(is_set(p->flags, xpf_moved)) {
flags = prefix_moved;
} else {
flags = prefix;
}
__xml_log_element(log_level, file, function, line,
flags, data, depth, options|xml_log_option_open);
for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
const char *aname = (const char*)pIter->name;
p = pIter->_private;
if(is_set(p->flags, xpf_deleted)) {
const char *value = crm_element_value(data, aname);
flags = prefix_del;
do_crm_log_alias(log_level, file, function, line,
"%s %s @%s=%s", flags, spaces, aname, value);
} else if(is_set(p->flags, xpf_dirty)) {
const char *value = crm_element_value(data, aname);
if(is_set(p->flags, xpf_created)) {
flags = prefix_m;
} else if(is_set(p->flags, xpf_modified)) {
flags = prefix;
} else if(is_set(p->flags, xpf_moved)) {
flags = prefix_moved;
} else {
flags = prefix;
}
do_crm_log_alias(log_level, file, function, line,
"%s %s @%s=%s", flags, spaces, aname, value);
}
}
free(prefix_moved);
free(prefix_del);
free(spaces);
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
}
__xml_log_element(log_level, file, function, line,
prefix, data, depth, options|xml_log_option_close);
} else {
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
}
}
free(prefix_m);
}
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;
char *prefix_m = 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;
}
if(is_set(options, xml_log_option_dirty_add) || is_set(options, xml_log_option_dirty_add)) {
__xml_log_change_element(log_level, file, function, line, prefix, data, depth, options);
return;
}
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;
}
}
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);
}
} else {
__xml_log_element(log_level, file, function, line, prefix, data, depth,
options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
}
free(prefix_m);
}
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_element(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 {
xmlAttrPtr xIter = NULL;
for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
dump_xml_attr(xIter, options, buffer, offset, max);
}
}
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 = data->children; xChild != NULL; xChild = xChild->next) {
crm_xml_dump(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
dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
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;
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "%s", data->content);
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
static void
dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
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;
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "<!--");
buffer_print(*buffer, *max, *offset, "%s", data->content);
buffer_print(*buffer, *max, *offset, "-->");
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
void
crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
if(data == NULL) {
*offset = 0;
*max = 0;
return;
}
#if 0
if (is_not_set(options, xml_log_option_filtered)) {
/* 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 it's 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
switch(data->type) {
case XML_ELEMENT_NODE:
/* Handle below */
dump_xml_element(data, options, buffer, offset, max, depth);
break;
case XML_TEXT_NODE:
/* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
if (options & xml_log_option_text) {
dump_xml_text(data, options, buffer, offset, max, depth);
}
return;
case XML_COMMENT_NODE:
dump_xml_comment(data, options, buffer, offset, max, depth);
break;
default:
crm_warn("Unhandled type: %d", data->type);
return;
/*
XML_ATTRIBUTE_NODE = 2
XML_CDATA_SECTION_NODE = 4
XML_ENTITY_REF_NODE = 5
XML_ENTITY_NODE = 6
XML_PI_NODE = 7
XML_DOCUMENT_NODE = 9
XML_DOCUMENT_TYPE_NODE = 10
XML_DOCUMENT_FRAG_NODE = 11
XML_NOTATION_NODE = 12
XML_HTML_DOCUMENT_NODE = 13
XML_DTD_NODE = 14
XML_ELEMENT_DECL = 15
XML_ATTRIBUTE_DECL = 16
XML_ENTITY_DECL = 17
XML_NAMESPACE_DECL = 18
XML_XINCLUDE_START = 19
XML_XINCLUDE_END = 20
XML_DOCB_DOCUMENT_NODE = 21
*/
}
}
void
crm_buffer_add_char(char **buffer, int *offset, int *max, char c)
{
buffer_print(*buffer, *max, *offset, "%c", c);
}
char *
dump_xml_formatted_with_text(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
crm_xml_dump(an_xml_node, xml_log_option_formatted|xml_log_option_text, &buffer, &offset, &max, 0);
return buffer;
}
char *
dump_xml_formatted(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
crm_xml_dump(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;
crm_xml_dump(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)
{
if(__xml_acl_check(obj, NULL, xpf_acl_write) == FALSE) {
crm_trace("Cannot remove %s from %s", name, obj->name);
} else if(TRACKING_CHANGES(obj)) {
/* Leave in place (marked for removal) until after the diff is calculated */
xml_private_t *p = NULL;
xmlAttr *attr = xmlHasProp(obj, (const xmlChar *)name);
p = attr->_private;
set_parent_flag(obj, xpf_dirty);
p->flags |= xpf_deleted;
/* crm_trace("Setting flag %x due to %s[@id=%s].%s", xpf_dirty, obj->name, ID(obj), name); */
} else {
xmlUnsetProp(obj, (const xmlChar *)name);
}
}
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);
}
}
void
save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
{
char *f = NULL;
if (filename == NULL) {
char *uuid = crm_generate_uuid();
f = crm_strdup_printf("/tmp/%s", uuid);
filename = f;
free(uuid);
}
crm_info("Saving %s to %s", desc, filename);
write_xml_file(xml, filename, FALSE);
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("Subtraction 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 %.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);
} else if (result) {
purge_diff_markers(*new); /* Purge now so the diff is ok */
}
return result;
}
static void
__xml_diff_object(xmlNode * old, xmlNode * new)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
CRM_CHECK(new != NULL, return);
if(old == NULL) {
crm_node_created(new);
__xml_acl_post_process(new); /* Check creation is allowed */
return;
} else {
xml_private_t *p = new->_private;
if(p->flags & xpf_processed) {
/* Avoid re-comparing nodes */
return;
}
p->flags |= xpf_processed;
}
for (pIter = crm_first_attr(new); pIter != NULL; pIter = pIter->next) {
xml_private_t *p = pIter->_private;
/* Assume everything was just created and take it from there */
p->flags |= xpf_created;
}
for (pIter = crm_first_attr(old); pIter != NULL; ) {
xmlAttr *prop = pIter;
xml_private_t *p = NULL;
const char *name = (const char *)pIter->name;
const char *old_value = crm_element_value(old, name);
xmlAttr *exists = xmlHasProp(new, pIter->name);
pIter = pIter->next;
if(exists == NULL) {
p = new->doc->_private;
/* Prevent the dirty flag being set recursively upwards */
clear_bit(p->flags, xpf_tracking);
exists = xmlSetProp(new, (const xmlChar *)name, (const xmlChar *)old_value);
set_bit(p->flags, xpf_tracking);
p = exists->_private;
p->flags = 0;
crm_trace("Lost %s@%s=%s", old->name, name, old_value);
xml_remove_prop(new, name);
} else {
int p_new = __xml_offset((xmlNode*)exists);
int p_old = __xml_offset((xmlNode*)prop);
const char *value = crm_element_value(new, name);
p = exists->_private;
p->flags = (p->flags & ~xpf_created);
if(strcmp(value, old_value) != 0) {
/* Restore the original value, so we can call crm_xml_add(),
* which checks ACLs
*/
char *vcopy = crm_element_value_copy(new, name);
crm_trace("Modified %s@%s %s->%s", old->name, name, old_value, vcopy);
xmlSetProp(new, prop->name, (const xmlChar *)old_value);
crm_xml_add(new, name, vcopy);
free(vcopy);
} else if(p_old != p_new) {
crm_info("Moved %s@%s (%d -> %d)", old->name, name, p_old, p_new);
__xml_node_dirty(new);
p->flags |= xpf_dirty|xpf_moved;
if(p_old > p_new) {
p = prop->_private;
p->flags |= xpf_skip;
} else {
p = exists->_private;
p->flags |= xpf_skip;
}
}
}
}
for (pIter = crm_first_attr(new); pIter != NULL; ) {
xmlAttr *prop = pIter;
xml_private_t *p = pIter->_private;
pIter = pIter->next;
if(is_set(p->flags, xpf_created)) {
char *name = strdup((const char *)prop->name);
char *value = crm_element_value_copy(new, name);
crm_trace("Created %s@%s=%s", new->name, name, value);
/* Remove plus create won't work as it will modify the relative attribute ordering */
if(__xml_acl_check(new, name, xpf_acl_write)) {
crm_attr_dirty(prop);
} else {
xmlUnsetProp(new, prop->name); /* Remove - change not allowed */
}
free(value);
free(name);
}
}
for (cIter = __xml_first_child(old); cIter != NULL; ) {
xmlNode *old_child = cIter;
xmlNode *new_child = find_element(new, cIter, TRUE);
cIter = __xml_next(cIter);
if(new_child) {
__xml_diff_object(old_child, new_child);
} else {
/* Create then free (which will check the acls if necessary) */
xmlNode *candidate = add_node_copy(new, old_child);
xmlNode *top = xmlDocGetRootElement(candidate->doc);
__xml_node_clean(candidate);
__xml_acl_apply(top); /* Make sure any ACLs are applied to 'candidate' */
/* Record the old position */
free_xml_with_position(candidate, __xml_offset(old_child));
if (find_element(new, old_child, TRUE) == NULL) {
xml_private_t *p = old_child->_private;
p->flags |= xpf_skip;
}
}
}
for (cIter = __xml_first_child(new); cIter != NULL; ) {
xmlNode *new_child = cIter;
xmlNode *old_child = find_element(old, cIter, TRUE);
cIter = __xml_next(cIter);
if(old_child == NULL) {
xml_private_t *p = new_child->_private;
p->flags |= xpf_skip;
__xml_diff_object(old_child, new_child);
} else {
/* Check for movement, we already checked for differences */
int p_new = __xml_offset(new_child);
int p_old = __xml_offset(old_child);
if(p_old != p_new) {
xml_private_t *p = new_child->_private;
crm_info("%s.%s moved from %d to %d",
new_child->name, ID(new_child), p_old, p_new);
__xml_node_dirty(new);
p->flags |= xpf_moved;
if(p_old > p_new) {
p = old_child->_private;
} else {
p = new_child->_private;
}
p->flags |= xpf_skip;
}
}
}
}
void
xml_calculate_changes(xmlNode * old, xmlNode * new)
{
CRM_CHECK(safe_str_eq(crm_element_name(old), crm_element_name(new)), return);
CRM_CHECK(safe_str_eq(ID(old), ID(new)), return);
if(xml_tracking_changes(new) == FALSE) {
xml_track_changes(new, NULL, NULL, FALSE);
}
__xml_diff_object(old, new);
}
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 *cIter = NULL;
xmlAttrPtr pIter = NULL;
gboolean can_prune = TRUE;
const char *name = crm_element_name(xml_node);
if (safe_str_eq(name, XML_TAG_RESOURCE_REF)
|| safe_str_eq(name, XML_CIB_TAG_OBJ_REF)
|| safe_str_eq(name, XML_ACL_TAG_ROLE_REF)
|| safe_str_eq(name, XML_ACL_TAG_ROLE_REFv1)) {
return FALSE;
}
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;
}
cIter = __xml_first_child(xml_node);
while (cIter) {
xmlNode *child = cIter;
cIter = __xml_next(cIter);
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;
}
static xmlNode *
find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact)
{
xmlNode *a_child = NULL;
int search_offset = __xml_offset(search_comment);
CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
if (exact) {
int offset = __xml_offset(a_child);
xml_private_t *p = a_child->_private;
if (offset < search_offset) {
continue;
} else if (offset > search_offset) {
return NULL;
}
if (is_set(p->flags, xpf_skip)) {
continue;
}
}
if (a_child->type == XML_COMMENT_NODE
&& safe_str_eq((const char *)a_child->content, (const char *)search_comment->content)) {
return a_child;
} else if (exact) {
return NULL;
}
}
return NULL;
}
static xmlNode *
subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean * changed)
{
CRM_CHECK(left != NULL, return NULL);
CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
if (right == NULL
|| safe_str_neq((const char *)left->content, (const char *)right->content)) {
xmlNode *deleted = NULL;
deleted = add_node_copy(parent, left);
*changed = TRUE;
return deleted;
}
return NULL;
}
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;
}
if (left->type == XML_COMMENT_NODE) {
return subtract_xml_comment(parent, left, right, changed);
}
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);
CRM_CHECK(safe_str_eq(crm_element_name(left), crm_element_name(right)), 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;
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_element(right, left_child, FALSE);
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;
xmlAttrPtr right_attr = NULL;
xml_private_t *p = NULL;
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_attr = xmlHasProp(right, (const xmlChar *)prop_name);
if (right_attr) {
p = right_attr->_private;
}
right_val = crm_element_value(right, prop_name);
if (right_val == NULL || (p && is_set(p->flags, xpf_deleted))) {
/* 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;
}
static int
add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update)
{
CRM_CHECK(update != NULL, return 0);
CRM_CHECK(update->type == XML_COMMENT_NODE, return 0);
if (target == NULL) {
target = find_xml_comment(parent, update, FALSE);
}
if (target == NULL) {
add_node_copy(parent, update);
/* We won't reach here currently */
} else if (safe_str_neq((const char *)target->content, (const char *)update->content)) {
xmlFree(target->content);
target->content = xmlStrdup(update->content);
}
return 0;
}
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);
if (update->type == XML_COMMENT_NODE) {
return add_xml_comment(parent, target, update);
}
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
}
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(update)), return 0);
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 = NULL;
xml_accept_changes(tmp);
old = xmlReplaceNode(child, tmp);
if(xml_tracking_changes(tmp)) {
/* Replaced sections may have included relevant ACLs */
__xml_acl_apply(tmp);
}
xml_calculate_changes(old, tmp);
xmlDocSetRootElement(doc, old);
free_xml(old);
}
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;
}
/*!
* \brief Create an XML name/value pair
*
* \param[in] parent If not NULL, make new XML node a child of this one
* \param[in] id If not NULL, use this as ID (otherwise auto-generate)
* \param[in] name Name to use
* \param[in] value Value to use
*
* \return New XML object on success, NULL otherwise
*/
xmlNode *
crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
const char *value)
{
xmlNode *nvp;
/* id can be NULL so we auto-generate one, and name can be NULL if this
* will be used to delete a name/value pair by ID, but both can't be NULL
*/
CRM_CHECK(id || name, return NULL);
nvp = create_xml_node(parent, XML_CIB_TAG_NVPAIR);
CRM_CHECK(nvp, return NULL);
if (id) {
crm_xml_add(nvp, XML_ATTR_ID, id);
} else {
const char *parent_id = ID(parent);
crm_xml_set_id(nvp, "%s-%s",
(parent_id? parent_id : XML_CIB_TAG_NVPAIR), name);
}
crm_xml_add(nvp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(nvp, XML_NVPAIR_ATTR_VALUE, value);
return nvp;
}
-/*!
- * \brief Create a CIB XML element for an operation
- *
- * \param[in] parent If not NULL, make new XML node a child of this one
- * \param[in] prefix Generate an ID using this prefix
- * \param[in] task Operation task to set
- * \param[in] interval Operation interval to set
- * \param[in] timeout If not NULL, operation timeout to set
- *
- * \return New XML object on success, NULL otherwise
- */
-xmlNode *
-crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
- const char *interval, const char *timeout)
-{
- xmlNode *xml_op;
-
- CRM_CHECK(prefix && task && interval, return NULL);
-
- xml_op = create_xml_node(parent, XML_ATTR_OP);
- crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval);
- crm_xml_add(xml_op, XML_LRM_ATTR_INTERVAL, interval);
- crm_xml_add(xml_op, "name", task);
- if (timeout) {
- crm_xml_add(xml_op, XML_ATTR_TIMEOUT, timeout);
- }
- return xml_op;
-}
-
void
hash2nvpair(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
crm_create_nvpair_xml(xml_node, name, name, 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;
}
/* Filter out cluster-generated attributes that contain a '#' or ':'
* (like fail-count and last-failure).
*/
for (crm_name = key; *crm_name; ++crm_name) {
if ((*crm_name == '#') || (*crm_name == ':')) {
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 = crm_str_table_new();
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;
}
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 function, 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;
}
/*!
* \brief Get next instance of same XML tag
*
* \param[in] sibling XML tag to start from
*
* \return Next sibling XML tag with same name
*/
xmlNode *
crm_next_same_xml(xmlNode *sibling)
{
xmlNode *match = __xml_next(sibling);
const char *name = crm_element_name(sibling);
while (match != NULL) {
if (!strcmp(crm_element_name(match), name)) {
return match;
}
match = __xml_next(match);
}
return NULL;
}
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_safe()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);
/* Populate and free the _private field when nodes are created and destroyed */
xmlDeregisterNodeDefault(pcmkDeregisterNode);
xmlRegisterNodeDefault(pcmkRegisterNode);
crm_schema_init();
}
}
void
crm_xml_cleanup(void)
{
crm_info("Cleaning up memory from libxml2");
crm_schema_cleanup();
xmlCleanupParser();
}
#define XPATH_MAX 512
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 offset = 0;
xpath_string = calloc(1, XPATH_MAX);
offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//%s[@id='%s']", tag, ref);
CRM_LOG_ASSERT(offset > 0);
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;
}
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;
}
void
crm_destroy_xml(gpointer data)
{
free_xml(data);
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 25, 12:10 PM (19 h, 50 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1318423
Default Alt Text
(194 KB)

Event Timeline