/* 
 * 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 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 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 General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <crm_internal.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

#include <sys/param.h>
#include <sys/types.h>

#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/msg.h>
#include <crm/common/xml.h>

enum cib_errors 
cib_process_query(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	xmlNode *obj_root = NULL;
	enum cib_errors result = cib_ok;
	
	crm_debug_2("Processing \"%s\" event for section=%s",
		  op, crm_str(section));

	if(options & cib_xpath) {
	    return cib_process_xpath(op, options, section, req, input,
				     existing_cib, result_cib, answer);
	}

	CRM_CHECK(*answer == NULL, free_xml(*answer));
	*answer = NULL;
	
	if (safe_str_eq(XML_CIB_TAG_SECTION_ALL, section)) {
		section = NULL;
	}

	obj_root = get_object_root(section, existing_cib);
	
	if(obj_root == NULL) {
		result = cib_NOTEXISTS;

	} else {
		*answer = obj_root;
	}

	if(result == cib_ok && *answer == NULL) {
		crm_err("Error creating query response");
		result = cib_output_data;
	}
	
	return result;
}

enum cib_errors 
cib_process_erase(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	enum cib_errors result = cib_ok;

	crm_debug_2("Processing \"%s\" event", op);
	*answer = NULL;
	free_xml(*result_cib);
	*result_cib = createEmptyCib();

	copy_in_properties(*result_cib, existing_cib);	
	cib_update_counter(*result_cib, XML_ATTR_GENERATION, FALSE);
	
	return result;
}

enum cib_errors 
cib_process_upgrade(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
    int rc = 0;
    int new_version = 0;
    int current_version = 0;

    const char *value = NULL;

    *answer = NULL;
    crm_debug_2("Processing \"%s\" event", op);
    
    value = crm_element_value_copy(existing_cib, XML_ATTR_VALIDATION);
    if(value != NULL) {
	current_version = get_schema_version(value);
    }

    rc = update_validation(result_cib, &new_version, TRUE, TRUE);
    if(new_version > current_version) {
	return cib_ok;
    }
    
    return rc;
}

enum cib_errors 
cib_process_bump(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	enum cib_errors result = cib_ok;

	crm_debug_2("Processing \"%s\" event for epoch=%s",
		  op, crm_str(crm_element_value(existing_cib, XML_ATTR_GENERATION)));
	
	*answer = NULL;
	cib_update_counter(*result_cib, XML_ATTR_GENERATION, FALSE);
	
	return result;
}


enum cib_errors 
cib_update_counter(xmlNode *xml_obj, const char *field, gboolean reset)
{
	char *new_value = NULL;
	char *old_value = NULL;
	int  int_value  = -1;
	
	if(reset == FALSE && crm_element_value(xml_obj, field) != NULL) {
		old_value = crm_element_value_copy(xml_obj, field);
	}
	if(old_value != NULL) {
		crm_malloc0(new_value, 128);
		int_value = atoi(old_value);
		sprintf(new_value, "%d", ++int_value);
	} else {
		new_value = crm_strdup("1");
	}

	crm_debug_4("%s %d(%s)->%s",
		  field, int_value, crm_str(old_value), crm_str(new_value));
	crm_xml_add(xml_obj, field, new_value);

	crm_free(new_value);
	crm_free(old_value);

	return cib_ok;
}

enum cib_errors 
cib_process_replace(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	const char *tag = NULL;
	gboolean verbose       = FALSE;
	enum cib_errors result = cib_ok;
	
	crm_debug_2("Processing \"%s\" event for section=%s",
		    op, crm_str(section));

	if(options & cib_xpath) {
	    return cib_process_xpath(op, options, section, req, input,
				     existing_cib, result_cib, answer);
	}

	*answer = NULL;

	if (input == NULL) {
		return cib_NOOBJECT;
	}

	tag = crm_element_name(input);

	if (options & cib_verbose) {
		verbose = TRUE;
	}
	if(safe_str_eq(XML_CIB_TAG_SECTION_ALL, section)) {
		section = NULL;

	} else if(safe_str_eq(tag, section)) {
		section = NULL;
	}
	
	if(safe_str_eq(tag, XML_TAG_CIB)) {
		int updates = 0;
		int epoch  = 0;
		int admin_epoch = 0;
		
		int replace_updates = 0;
		int replace_epoch  = 0;
		int replace_admin_epoch = 0;
		const char *reason = NULL;
		
		cib_version_details(
			existing_cib, &admin_epoch, &epoch, &updates);
		cib_version_details(input, &replace_admin_epoch,
				    &replace_epoch, &replace_updates);

		if(replace_admin_epoch < admin_epoch) {
			reason = XML_ATTR_GENERATION_ADMIN;

		} else if(replace_admin_epoch > admin_epoch) {
			/* no more checks */

		} else if(replace_epoch < epoch) {
			reason = XML_ATTR_GENERATION;

		} else if(replace_epoch > epoch) {
			/* no more checks */

		} else if(replace_updates < updates) {
			reason = XML_ATTR_NUMUPDATES;
		}

		if(reason != NULL) {
			crm_warn("Replacement %d.%d.%d not applied to %d.%d.%d:"
				 " current %s is greater than the replacement",
				 replace_admin_epoch, replace_epoch,
				 replace_updates, admin_epoch, epoch, updates,
				 reason);
			result = cib_old_data;
		}

		free_xml(*result_cib);
		*result_cib = copy_xml(input);
		
	} else {
		xmlNode *obj_root = NULL;
		gboolean ok = TRUE;
		obj_root = get_object_root(section, *result_cib);
		ok = replace_xml_child(NULL, obj_root, input, FALSE);
		if(ok == FALSE) {
			crm_debug_2("No matching object to replace");
			result = cib_NOTEXISTS;
		}
	}

	return result;
}

enum cib_errors 
cib_process_delete(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	xmlNode *obj_root = NULL;
	crm_debug_2("Processing \"%s\" event", op);

	if(options & cib_xpath) {
	    return cib_process_xpath(op, options, section, req, input,
				     existing_cib, result_cib, answer);
	}

	if(input == NULL) {
		crm_err("Cannot perform modification with no data");
		return cib_NOOBJECT;
	}
	
	obj_root = get_object_root(section, *result_cib);
	
	crm_validate_data(input);
	crm_validate_data(*result_cib);

	if(replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) {
		crm_debug_2("No matching object to delete");
	}
	
	return cib_ok;
}

enum cib_errors 
cib_process_modify(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	xmlNode *obj_root = NULL;
	crm_debug_2("Processing \"%s\" event", op);

	if(options & cib_xpath) {
	    return cib_process_xpath(op, options, section, req, input,
				     existing_cib, result_cib, answer);
	}

	if(input == NULL) {
		crm_err("Cannot perform modification with no data");
		return cib_NOOBJECT;
	}
	
	obj_root = get_object_root(section, *result_cib);
	
	crm_validate_data(input);
	crm_validate_data(*result_cib);

	if(obj_root == NULL) {
	    xmlNode *tmp_section = NULL;
	    const char *path = get_object_parent(section);
	    if(path == NULL) {
		return cib_bad_section;		
	    }

	    tmp_section = create_xml_node(NULL, section);
	    cib_process_xpath(
		CIB_OP_CREATE, 0, path, NULL, tmp_section, NULL, result_cib, answer);
	    free_xml(tmp_section);
	    
	    obj_root = get_object_root(section, *result_cib);
	}

	CRM_CHECK(obj_root != NULL, return cib_unknown);
	
	if(update_xml_child(obj_root, input) == FALSE) {
	    if(options & cib_can_create) {
		add_node_copy(obj_root, input);
	    } else {
		return cib_NOTEXISTS;		
	    }
	}
	
	return cib_ok;
}

static int
update_cib_object(xmlNode *parent, xmlNode *update)
{
	const char *replace = NULL;
	const char *object_name = NULL;
	const char *object_id = NULL;
	xmlNode *target = NULL;
	int result = cib_ok;

	CRM_CHECK(update != NULL, return cib_NOOBJECT);
	CRM_CHECK(parent != NULL, return cib_NOPARENT);

	object_name = crm_element_name(update);
	CRM_CHECK(object_name != NULL, return cib_NOOBJECT);

	object_id = ID(update);
	crm_debug_3("Processing: <%s id=%s>",
		    crm_str(object_name), crm_str(object_id));
	
	if(object_id == NULL) {
		/*  placeholder object */
		target = find_xml_node(parent, object_name, FALSE);

	} else {
		target = find_entity(parent, object_name, object_id);
	}

	if(target == NULL) {
		target = create_xml_node(parent, object_name);
	} 

	crm_debug_2("Found node <%s id=%s> to update",
		    crm_str(object_name), crm_str(object_id));
	
	replace = crm_element_value(update, XML_CIB_ATTR_REPLACE);
	if(replace != NULL) {
	    xmlNode *remove = NULL;
	    int last = 0, lpc = 0, len = 0;

	    len = strlen(replace);
	    while(lpc <= len) {
		if(replace[lpc] == ',' || replace[lpc] == 0) {
		    char *replace_item = NULL;
		    if ( last == lpc ) {
			/* nothing to do */
			last = lpc+1;
			goto incr;
		    }

		    crm_malloc0(replace_item, lpc - last + 1);
		    strncpy(replace_item, replace+last, lpc-last);
		    
		    remove = find_xml_node(target, replace_item, FALSE);
		    if(remove != NULL) {
			crm_debug_3("Replacing node <%s> in <%s>",
				    replace_item, crm_element_name(target));
			zap_xml_from_parent(target, remove);
		    }
		    crm_free(replace_item);
		    last = lpc+1;
		}
	      incr:
		lpc++;
	    }
	    xml_remove_prop(update, XML_CIB_ATTR_REPLACE);
	    xml_remove_prop(target, XML_CIB_ATTR_REPLACE);
	}
	
	copy_in_properties(target, update);

	crm_debug_3("Processing children of <%s id=%s>",
		    crm_str(object_name), crm_str(object_id));
	
	xml_child_iter(
		update, a_child,  
		int tmp_result = 0;
		crm_debug_3("Updating child <%s id=%s>",
			    crm_element_name(a_child), ID(a_child));
		
		tmp_result = update_cib_object(target, a_child);
		
		/*  only the first error is likely to be interesting */
		if(tmp_result != cib_ok) {
			crm_err("Error updating child <%s id=%s>",
				crm_element_name(a_child), ID(a_child));
			
			if(result == cib_ok) {
				result = tmp_result;
			}
		}
		);
	
	crm_debug_3("Finished with <%s id=%s>",
		  crm_str(object_name), crm_str(object_id));

	return result;
}

static int
add_cib_object(xmlNode *parent, xmlNode *new_obj)
{
	enum cib_errors result = cib_ok;
	const char *object_name = NULL;
	const char *object_id = NULL;
	xmlNode *equiv_node = NULL;
	
	if(new_obj != NULL) {
		object_name = crm_element_name(new_obj);
	}
	object_id = crm_element_value(new_obj, XML_ATTR_ID);

	crm_debug_3("Processing: <%s id=%s>",
		    crm_str(object_name), crm_str(object_id));
	
	if(new_obj == NULL || object_name == NULL) {
		result = cib_NOOBJECT;

	} else if(parent == NULL) {
		result = cib_NOPARENT;

	} else if(object_id == NULL) {
		/*  placeholder object */
		equiv_node = find_xml_node(parent, object_name, FALSE);
		
	} else {
		equiv_node = find_entity(parent, object_name, object_id);
	}

	if(result != cib_ok) {
		; /* do nothing */
		
	} else if(equiv_node != NULL) {
		result = cib_EXISTS;

	} else {
		result = update_cib_object(parent, new_obj);
	}

	return result;
}



enum cib_errors 
cib_process_create(
    const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
    xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
    xmlNode *failed = NULL;
    enum cib_errors result = cib_ok;
    xmlNode *update_section = NULL;
	
    crm_debug_2("Processing \"%s\" event for section=%s", op, crm_str(section));
    if(safe_str_eq(XML_CIB_TAG_SECTION_ALL, section)) {
	section = NULL;

    } else if(safe_str_eq(XML_TAG_CIB, section)) {
	section = NULL;

    } else if(safe_str_eq(crm_element_name(input), XML_TAG_CIB)) {
	section = NULL;
    }

    CRM_CHECK(strcasecmp(CIB_OP_CREATE, op) == 0, return cib_operation);
	
    if(input == NULL) {
	crm_err("Cannot perform modification with no data");
	return cib_NOOBJECT;
    }
	
    if(section == NULL) {
	return cib_process_modify(op, options, section, req, input, existing_cib, result_cib, answer);
    }

    failed = create_xml_node(NULL, XML_TAG_FAILED);

    update_section = get_object_root(section, *result_cib);
    if(safe_str_eq(crm_element_name(input), section)) {
	xml_child_iter(input, a_child, 
		       result = add_cib_object(update_section, a_child);
		       if(update_results(failed, a_child, op, result)) {
			   break;
		       }
	    );

    } else {
	result = add_cib_object(update_section, input);
	update_results(failed, input, op, result);
    }
	
    if(xml_has_children(failed)) {
	CRM_CHECK(result != cib_ok, result = cib_unknown);
    }

    if (result != cib_ok) {
	crm_log_xml_err(failed, "CIB Update failures");
	*answer = failed;

    } else {
	free_xml(failed);
    }

    return result;
}

enum cib_errors 
cib_process_diff(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
	unsigned int log_level = LOG_DEBUG;
	const char *reason = NULL;
	gboolean apply_diff = TRUE;
	enum cib_errors result = cib_ok;

	int this_updates = 0;
	int this_epoch  = 0;
	int this_admin_epoch = 0;

	int diff_add_updates = 0;
	int diff_add_epoch  = 0;
	int diff_add_admin_epoch = 0;

	int diff_del_updates = 0;
	int diff_del_epoch  = 0;
	int diff_del_admin_epoch = 0;

	crm_debug_2("Processing \"%s\" event", op);

	cib_diff_version_details(
		input,
		&diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, 
		&diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);

	crm_element_value_int(existing_cib, XML_ATTR_GENERATION, &this_epoch);
	crm_element_value_int(existing_cib, XML_ATTR_NUMUPDATES, &this_updates);
	crm_element_value_int(existing_cib, XML_ATTR_GENERATION_ADMIN, &this_admin_epoch);

	if(this_epoch < 0) { this_epoch = 0; }
	if(this_updates < 0) { this_updates = 0; }
	if(this_admin_epoch < 0) { this_admin_epoch = 0; }

	if(diff_del_admin_epoch == diff_add_admin_epoch
	   && diff_del_epoch == diff_add_epoch
	   && diff_del_updates == diff_add_updates) {
		if(options & cib_force_diff) {
			apply_diff = FALSE;
			log_level = LOG_ERR;
			reason = "+ and - versions in the diff did not change in global update";
			crm_log_xml_warn(input, "Bad global update");
			
		} else if(diff_add_admin_epoch == -1 && diff_add_epoch == -1 && diff_add_updates == -1) {
			diff_add_epoch = this_epoch;
			diff_add_updates = this_updates + 1;
			diff_add_admin_epoch = this_admin_epoch;
			diff_del_epoch = this_epoch;
			diff_del_updates = this_updates;
			diff_del_admin_epoch = this_admin_epoch;

		} else {
			apply_diff = FALSE;
			log_level = LOG_ERR;
			reason = "+ and - versions in the diff did not change";
			log_cib_diff(LOG_ERR, input, __FUNCTION__);
		}
	}

	if(apply_diff && diff_del_admin_epoch > this_admin_epoch) {
		result = cib_diff_resync;
		apply_diff = FALSE;
		log_level = LOG_INFO;
		reason = "current \""XML_ATTR_GENERATION_ADMIN"\" is less than required";
		
	} else if(apply_diff && diff_del_admin_epoch < this_admin_epoch) {
		apply_diff = FALSE;
		log_level = LOG_WARNING;
		reason = "current \""XML_ATTR_GENERATION_ADMIN"\" is greater than required";

	} else if(apply_diff && diff_del_epoch > this_epoch) {
		result = cib_diff_resync;
		apply_diff = FALSE;
		log_level = LOG_INFO;
		reason = "current \""XML_ATTR_GENERATION"\" is less than required";
		
	} else if(apply_diff && diff_del_epoch < this_epoch) {
		apply_diff = FALSE;
		log_level = LOG_WARNING;
		reason = "current \""XML_ATTR_GENERATION"\" is greater than required";

	} else if(apply_diff && diff_del_updates > this_updates) {
		result = cib_diff_resync;
		apply_diff = FALSE;
		log_level = LOG_INFO;
		reason = "current \""XML_ATTR_NUMUPDATES"\" is less than required";
		
	} else if(apply_diff && diff_del_updates < this_updates) {
		apply_diff = FALSE;
		log_level = LOG_WARNING;
		reason = "current \""XML_ATTR_NUMUPDATES"\" is greater than required";
	}

	if(apply_diff) {
		free_xml(*result_cib);
		*result_cib = NULL;
		if(apply_xml_diff(existing_cib, input, result_cib) == FALSE) {
		    log_level = LOG_NOTICE;
		    reason = "Failed application of an update diff";
		    
		    if(options & cib_force_diff) {
			result = cib_diff_resync;
		    }
		}
	}
	
	if(reason != NULL) {
		do_crm_log(
			log_level,
			"Diff %d.%d.%d -> %d.%d.%d not applied to %d.%d.%d: %s",
			diff_del_admin_epoch,diff_del_epoch,diff_del_updates,
			diff_add_admin_epoch,diff_add_epoch,diff_add_updates,
			this_admin_epoch,this_epoch,this_updates, reason);

		if(result == cib_ok) {
		    result = cib_diff_failed;
		}
		
	} else if(apply_diff) {
		crm_debug_2("Diff %d.%d.%d -> %d.%d.%d was applied to %d.%d.%d",
			    diff_del_admin_epoch,diff_del_epoch,diff_del_updates,
			    diff_add_admin_epoch,diff_add_epoch,diff_add_updates,
			    this_admin_epoch,this_epoch,this_updates);
	    
	}
	return result;
}

gboolean
apply_cib_diff(xmlNode *old, xmlNode *diff, xmlNode **new)
{
	gboolean result = TRUE;
	const char *value = NULL;

	int this_updates = 0;
	int this_epoch  = 0;
	int this_admin_epoch = 0;

	int diff_add_updates = 0;
	int diff_add_epoch  = 0;
	int diff_add_admin_epoch = 0;

	int diff_del_updates = 0;
	int diff_del_epoch  = 0;
	int diff_del_admin_epoch = 0;

	CRM_CHECK(diff != NULL, return FALSE);
	CRM_CHECK(old != NULL, return FALSE);
	
	value = crm_element_value(old, XML_ATTR_GENERATION_ADMIN);
	this_admin_epoch = crm_parse_int(value, "0");
	crm_debug_3("%s=%d (%s)", XML_ATTR_GENERATION_ADMIN,
		  this_admin_epoch, value);
	
	value = crm_element_value(old, XML_ATTR_GENERATION);
	this_epoch = crm_parse_int(value, "0");
	crm_debug_3("%s=%d (%s)", XML_ATTR_GENERATION, this_epoch, value);
	
	value = crm_element_value(old, XML_ATTR_NUMUPDATES);
	this_updates = crm_parse_int(value, "0");
	crm_debug_3("%s=%d (%s)", XML_ATTR_NUMUPDATES, this_updates, value);
	
	cib_diff_version_details(
		diff,
		&diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, 
		&diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);

	value = NULL;
	if(result && diff_del_admin_epoch != this_admin_epoch) {
		value = XML_ATTR_GENERATION_ADMIN;
		result = FALSE;
		crm_debug_3("%s=%d", value, diff_del_admin_epoch);

	} else if(result && diff_del_epoch != this_epoch) {
		value = XML_ATTR_GENERATION;
		result = FALSE;
		crm_debug_3("%s=%d", value, diff_del_epoch);

	} else if(result && diff_del_updates != this_updates) {
		value = XML_ATTR_NUMUPDATES;
		result = FALSE;
		crm_debug_3("%s=%d", value, diff_del_updates);
	}

	if(result) {
		xmlNode *tmp = NULL;
		xmlNode *diff_copy = copy_xml(diff);
		
		tmp = find_xml_node(diff_copy, "diff-removed", TRUE);
		if(tmp != NULL) {
			xml_remove_prop(tmp, XML_ATTR_GENERATION_ADMIN);
			xml_remove_prop(tmp, XML_ATTR_GENERATION);
			xml_remove_prop(tmp, XML_ATTR_NUMUPDATES);
		}
		
		tmp = find_xml_node(diff_copy, "diff-added", TRUE);
		if(tmp != NULL) {
			xml_remove_prop(tmp, XML_ATTR_GENERATION_ADMIN);
			xml_remove_prop(tmp, XML_ATTR_GENERATION);
			xml_remove_prop(tmp, XML_ATTR_NUMUPDATES);
		}
		
		result = apply_xml_diff(old, diff_copy, new);
		free_xml(diff_copy);
		
	} else {
		crm_err("target and diff %s values didnt match", value);
	}
	
	
	return result;
}

gboolean
cib_config_changed(xmlNode *last, xmlNode *next, xmlNode **diff)
{
    gboolean config_changes = FALSE;
    xmlXPathObject *xpathObj = NULL;

    CRM_ASSERT(diff != NULL);

    *diff = diff_xml_object(last, next, FALSE);
    if(*diff == NULL) {
	char *digest_last = calculate_xml_digest(last, FALSE, TRUE);
	char *digest_next = calculate_xml_digest(next, FALSE, TRUE);

	/* Detect ordering changes - important for groups and resource sets */
	if(safe_str_neq(digest_last, digest_next)) {
	    config_changes = TRUE;
	    crm_info("Detected ordering change: %s vs %s", digest_last, digest_next);

	    /* Create a fake diff so that notifications will be sent */
	    *diff = create_xml_node(NULL, "diff");
	    create_xml_node(*diff, "diff-removed");
	    create_xml_node(*diff, "diff-added");
	}
	crm_free(digest_last);
	crm_free(digest_next);
	goto done;
    }
    
    xpathObj = xpath_search(*diff, "//"XML_CIB_TAG_CONFIGURATION);
    if(xpathObj && xpathObj->nodesetval->nodeNr > 0) {
	config_changes = TRUE;
	goto done;

    } else if(xpathObj) {
	xmlXPathFreeObject(xpathObj);
    }
    
    xpathObj = xpath_search(*diff, "//"XML_TAG_CIB);
    if(xpathObj) {
	int lpc = 0, max = xpathObj->nodesetval->nodeNr;
	for(lpc = 0; lpc < max; lpc++) {
	    xmlNode *top = getXpathResult(xpathObj, lpc);
	    xml_prop_iter(top, name, value,
			  if(crm_str_eq(XML_ATTR_NUMUPDATES, name, TRUE) == FALSE) {
			      config_changes = TRUE;
			      goto done;
			  }
		);
	}	    
    }

  done:
    if(xpathObj) {
	xmlXPathFreeObject(xpathObj);
    }
    return config_changes;
}

xmlNode *
diff_cib_object(xmlNode *old_cib, xmlNode *new_cib, gboolean suppress)
{
	xmlNode *dest = NULL;
	xmlNode *src = NULL;
	const char *name = NULL;
	const char *value = NULL;

	xmlNode *diff = diff_xml_object(old_cib, new_cib, suppress);
	
	/* add complete version information */
	src = old_cib;
	dest = find_xml_node(diff, "diff-removed", FALSE);
	if(src != NULL && dest != NULL) {
		name = XML_ATTR_GENERATION_ADMIN;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);

		name = XML_ATTR_GENERATION;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);

		name = XML_ATTR_NUMUPDATES;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);
	}
	
	src = new_cib;
	dest = find_xml_node(diff, "diff-added", FALSE);
	if(src != NULL && dest != NULL) {
		name = XML_ATTR_GENERATION_ADMIN;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);

		name = XML_ATTR_GENERATION;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);

		name = XML_ATTR_NUMUPDATES;
		value = crm_element_value(src, name);
		if(value == NULL) {
			value = "0";
		}
		crm_xml_add(dest, name, value);
	}
	return diff;
}

enum cib_errors 
cib_process_xpath(
	const char *op, int options, const char *section, xmlNode *req, xmlNode *input,
	xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer)
{
    int lpc = 0;
    int max = 0;
    int rc = cib_ok;
    gboolean is_query = safe_str_eq(op, CIB_OP_QUERY);
    
    xmlXPathObjectPtr xpathObj = NULL;
    crm_debug_2("Processing \"%s\" event", op);

    if(is_query) {
	xpathObj = xpath_search(existing_cib, section);
    } else {
	xpathObj = xpath_search(*result_cib, section);
    }
    
    if(xpathObj != NULL && xpathObj->nodesetval != NULL) {
	max = xpathObj->nodesetval->nodeNr;
    }

    if(max < 1 && safe_str_eq(op, CIB_OP_DELETE)) {
	crm_debug("%s was already removed", section);

    } else if(max < 1) {
	crm_debug("%s: %s does not exist", op, section);
	rc = cib_NOTEXISTS;

    } else if(is_query) {
	if(max > 1) {
	    *answer = create_xml_node(NULL, "xpath-query");
	}
    }

    for(lpc = 0; lpc < max; lpc++) {
	xmlChar *path = NULL;
	xmlNode *match = getXpathResult(xpathObj, lpc);
	if(match == NULL) {
	    continue;
	}
	
	path = xmlGetNodePath(match);
	crm_debug("Processing %s op for %s (%s)", op, section, path);
	free(path);
	
	if(safe_str_eq(op, CIB_OP_DELETE)) {
	    free_xml_from_parent(NULL, match);
	    if((options & cib_multiple) == 0) {
		break;
	    }
	    
	} else if(safe_str_eq(op, CIB_OP_MODIFY)) {
	    if(update_xml_child(match, input) == FALSE) {
		rc = cib_NOTEXISTS;		
	    } else if((options & cib_multiple) == 0) {
		break;
	    }
	    
	} else if(safe_str_eq(op, CIB_OP_CREATE)) {
	    add_node_copy(match, input);
	    break;

	} else if(safe_str_eq(op, CIB_OP_QUERY)) {

	    if(options & cib_no_children) {
		const char *tag = TYPE(match);
		xmlNode *shallow = create_xml_node(*answer, tag);
		copy_in_properties(shallow, match);

		if(*answer == NULL) {
		    *answer = shallow;
		}
		
	    } else if(*answer) {
		add_node_copy(*answer, match);

	    } else {
		*answer = match;
	    }
	    
	} else if(safe_str_eq(op, CIB_OP_REPLACE)) {
	    xmlNode *parent = match->parent;
	    free_xml_from_parent(NULL, match);
	    if(input != NULL) {
		add_node_copy(parent, input);
	    }
	    
	    if((options & cib_multiple) == 0) {
		break;
	    }
	}
    }
	
    if(xpathObj) {
	xmlXPathFreeObject(xpathObj);
    }
	    
    return rc;
}

/* remove this function */
gboolean
update_results(
	xmlNode *failed, xmlNode *target, const char* operation, int return_code)
{
	xmlNode *xml_node = NULL;
	gboolean was_error = FALSE;
	const char *error_msg = NULL;
	
    
	if (return_code != cib_ok) {
		error_msg = cib_error2string(return_code);

		xml_node = create_xml_node(failed, XML_FAIL_TAG_CIB);

		was_error = TRUE;

		add_node_copy(xml_node, target);
		
		crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID,      ID(target));
		crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, TYPE(target));
		crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP,      operation);
		crm_xml_add(xml_node, XML_FAILCIB_ATTR_REASON,  error_msg);

		crm_warn("Action %s failed: %s (cde=%d)",
			  operation, error_msg, return_code);
	}

	return was_error;
}
