Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F2825319
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
194 KB
Referenced Files
None
Subscribers
None
View Options
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 < 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, "<");
changes++;
break;
case '>':
copy = crm_xml_escape_shuffle(copy, index, &length, ">");
changes++;
break;
case '"':
copy = crm_xml_escape_shuffle(copy, index, &length, """);
changes++;
break;
case '\'':
copy = crm_xml_escape_shuffle(copy, index, &length, "'");
changes++;
break;
case '&':
copy = crm_xml_escape_shuffle(copy, index, &length, "&");
changes++;
break;
case '\t':
/* Might as well just expand to a few spaces... */
copy = crm_xml_escape_shuffle(copy, index, &length, " ");
changes++;
break;
case '\n':
/* crm_trace("Convert: \\%.3o", copy[index]); */
copy = crm_xml_escape_shuffle(copy, index, &length, "\\n");
changes++;
break;
case '\r':
copy = crm_xml_escape_shuffle(copy, index, &length, "\\r");
changes++;
break;
/* For debugging...
case '\\':
crm_trace("Passthrough: \\%c", copy[index+1]);
break;
*/
default:
/* Check for and replace non-printing characters with their octal equivalent */
if(copy[index] < ' ' || copy[index] > '~') {
char *replace = 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
Details
Attached
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)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment