/* 
 * 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 <sys/param.h>

#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>

#include <crm/pengine/rules.h>
#include <crm/common/cluster.h>

#include <crmd.h>
#include <crmd_fsa.h>
#include <fsa_proto.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crmd_lrm.h>

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


char *ipc_server = NULL;

extern void post_cache_update(int seq);
extern void crmd_ha_connection_destroy(gpointer user_data);

void crm_shutdown(int nsig);
gboolean crm_read_options(gpointer user_data);

gboolean      fsa_has_quorum = FALSE;
GHashTable   *ipc_clients = NULL;
crm_trigger_t  *fsa_source = NULL;
crm_trigger_t  *config_read = NULL;

/*	 A_HA_CONNECT	*/
#if SUPPORT_AIS	
extern void crmd_ha_msg_filter(xmlNode * msg);

static gboolean crm_ais_dispatch(AIS_Message *wrapper, char *data, int sender) 
{
    int seq = 0;
    xmlNode *xml = NULL;
    const char *seq_s = NULL;

    xml = string2xml(data);
    if(xml == NULL) {
	crm_err("Could not parse message content (%d): %.100s", wrapper->header.id, data);
	return TRUE;
    }
    
    switch(wrapper->header.id) {
	case crm_class_members:
	    seq_s = crm_element_value(xml, "id");
	    seq = crm_int_helper(seq_s, NULL);
	    set_bit_inplace(fsa_input_register, R_PEER_DATA);
	    post_cache_update(seq);

	    /* fall through */
	case crm_class_quorum:
	    crm_update_quorum(crm_have_quorum, FALSE);
	    if(AM_I_DC) {
		const char *votes = crm_element_value(xml, "expected");
		if(votes == NULL || check_number(votes) == FALSE) {
		    crm_log_xml_err(xml, "Invalid quorum/membership update");

		} else {
		    int rc = update_attr(
			fsa_cib_conn, cib_quorum_override|cib_scope_local|cib_inhibit_notify,
			XML_CIB_TAG_CRMCONFIG, NULL, NULL, NULL, XML_ATTR_EXPECTED_VOTES, votes, FALSE);

		    crm_info("Setting expected votes to %s", votes);
		    if(cib_ok > rc) {
			crm_err("Quorum update failed: %s", cib_error2string(rc));
		    }
		}
	    }
	    break;
	    
	case crm_class_cluster:
	    crm_xml_add(xml, F_ORIG, wrapper->sender.uname);
	    crm_xml_add_int(xml, F_SEQ, wrapper->id);
	    crmd_ha_msg_filter(xml);
	    break;

	case crm_class_rmpeer:
	    /* Ignore */
	    break;

	case crm_class_notify:
	case crm_class_nodeid:
	    crm_err("Unexpected message class (%d): %.100s", wrapper->header.id, data);
	    break;

	default:
	    crm_err("Invalid message class (%d): %.100s", wrapper->header.id, data);
    }
    
    free_xml(xml);    
    return TRUE;
}

static void
crm_ais_destroy(gpointer user_data)
{
    crm_err("AIS connection terminated");
    ais_fd_sync = -1;
    exit(1);
}
#endif

void
do_ha_control(long long action,
	       enum crmd_fsa_cause cause,
	       enum crmd_fsa_state cur_state,
	       enum crmd_fsa_input current_input,
	       fsa_data_t *msg_data)
{
	gboolean registered = FALSE;
	
	if(action & A_HA_DISCONNECT) {
	    if(is_openais_cluster()) {
		crm_peer_destroy();
		crm_info("Disconnected from OpenAIS");
#if SUPPORT_HEARTBEAT
	    } else if(fsa_cluster_conn != NULL) {
		set_bit_inplace(fsa_input_register, R_HA_DISCONNECTED);
		fsa_cluster_conn->llc_ops->signoff(fsa_cluster_conn, FALSE);
		crm_info("Disconnected from Heartbeat");
#endif
	    }
	}
	
	if(action & A_HA_CONNECT) {
	    void *dispatch = NULL;
	    void *destroy = NULL;
	    
	    if(is_openais_cluster()) {
#if SUPPORT_AIS
		destroy = crm_ais_destroy;
		dispatch = crm_ais_dispatch;
		
#endif
	    } else if(is_heartbeat_cluster()) {
#if SUPPORT_HEARTBEAT
		dispatch = crmd_ha_msg_callback;
		destroy = crmd_ha_connection_destroy;
#endif
	    }
	    
	    crm_set_status_callback(&ais_status_callback);
	    registered = crm_cluster_connect(
		&fsa_our_uname, &fsa_our_uuid, dispatch, destroy,
#if SUPPORT_HEARTBEAT
		&fsa_cluster_conn
#else
		NULL
#endif
		);
	    
#if SUPPORT_HEARTBEAT
	    if(is_heartbeat_cluster()) {	
		crm_debug_3("Be informed of Node Status changes");
		if (registered &&
		    fsa_cluster_conn->llc_ops->set_nstatus_callback(
			fsa_cluster_conn, crmd_ha_status_callback,
			fsa_cluster_conn) != HA_OK){
		    
		    crm_err("Cannot set nstatus callback: %s",
			    fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn));
		    registered = FALSE;
		}
		
		crm_debug_3("Be informed of CRM Client Status changes");
		if (registered &&
		    fsa_cluster_conn->llc_ops->set_cstatus_callback(
			fsa_cluster_conn, crmd_client_status_callback,
			fsa_cluster_conn) != HA_OK) {
		    
		    crm_err("Cannot set cstatus callback: %s",
			    fsa_cluster_conn->llc_ops->errmsg(fsa_cluster_conn));
		    registered = FALSE;
		}

		if(registered) {
		    crm_debug_3("Requesting an initial dump of CRMD client_status");
		    fsa_cluster_conn->llc_ops->client_status(
			fsa_cluster_conn, NULL, CRM_SYSTEM_CRMD, -1);
		}
	    }
#endif

	    if(registered == FALSE) {
		set_bit_inplace(fsa_input_register, R_HA_DISCONNECTED);
		register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
		return;
	    }

	    clear_bit_inplace(fsa_input_register, R_HA_DISCONNECTED);
	    crm_info("Connected to the cluster");
	} 
	
	if(action & ~(A_HA_CONNECT|A_HA_DISCONNECT)) {
		crm_err("Unexpected action %s in %s",
		       fsa_action2string(action), __FUNCTION__);
	}
}

/*	 A_SHUTDOWN	*/
void
do_shutdown(long long action,
	    enum crmd_fsa_cause cause,
	    enum crmd_fsa_state cur_state,
	    enum crmd_fsa_input current_input,
	    fsa_data_t *msg_data)
{
	/* just in case */
	set_bit_inplace(fsa_input_register, R_SHUTDOWN);

	if(is_heartbeat_cluster()) {
	    if(is_set(fsa_input_register, pe_subsystem->flag_connected)) {
		crm_info("Terminating the %s", pe_subsystem->name);
		if(stop_subsystem(pe_subsystem, TRUE) == FALSE) {
		    /* its gone... */
		    crm_err("Faking %s exit", pe_subsystem->name);
		    clear_bit_inplace(fsa_input_register,
				      pe_subsystem->flag_connected);
		} else {
		    crm_info("Waiting for subsystems to exit");
		    crmd_fsa_stall(NULL);
		}
	    }
	    crm_info("All subsystems stopped, continuing");
	}
}

/*	 A_SHUTDOWN_REQ	*/
void
do_shutdown_req(long long action,
	    enum crmd_fsa_cause cause,
	    enum crmd_fsa_state cur_state,
	    enum crmd_fsa_input current_input,
	    fsa_data_t *msg_data)
{
	xmlNode *msg = NULL;
 	
	crm_info("Sending shutdown request to DC: %s", crm_str(fsa_our_dc));
	msg = create_request(
		CRM_OP_SHUTDOWN_REQ, NULL, NULL,
		CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL);
 
/* 	set_bit_inplace(fsa_input_register, R_STAYDOWN); */
	if(send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) {
	    register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
	}
	free_xml(msg);
}

extern char *max_generation_from;
extern xmlNode *max_generation_xml;
extern GHashTable *resources;
extern GHashTable *voted;
extern GHashTable *reload_hash;

void log_connected_client(gpointer key, gpointer value, gpointer user_data);

void
log_connected_client(gpointer key, gpointer value, gpointer user_data)
{
	crmd_client_t *client = value;
	crm_err("%s is still connected at exit", client->table_key);
}


static void free_mem(fsa_data_t *msg_data) 
{
	g_main_loop_quit(crmd_mainloop);
	g_main_loop_unref(crmd_mainloop);
	
#if SUPPORT_HEARTBEAT
	if(fsa_cluster_conn) {
		fsa_cluster_conn->llc_ops->delete(fsa_cluster_conn);
		fsa_cluster_conn = NULL;
	}
#endif	
	slist_destroy(fsa_data_t, fsa_data, fsa_message_queue, 
		      crm_info("Dropping %s: [ state=%s cause=%s origin=%s ]",
			       fsa_input2string(fsa_data->fsa_input),
			       fsa_state2string(fsa_state),
			       fsa_cause2string(fsa_data->fsa_cause),
			       fsa_data->origin);
		      delete_fsa_input(fsa_data);
		);
	delete_fsa_input(msg_data);

	if(ipc_clients) {
		crm_debug("Number of connected clients: %d",
			  g_hash_table_size(ipc_clients));
/* 		g_hash_table_foreach(ipc_clients, log_connected_client, NULL); */
		g_hash_table_destroy(ipc_clients);
	}

	empty_uuid_cache();
	crm_peer_destroy();
	clear_bit_inplace(fsa_input_register, R_CCM_DATA);

	if(te_subsystem->client && te_subsystem->client->client_source) {
		crm_debug("Full destroy: TE");
		G_main_del_IPC_Channel(te_subsystem->client->client_source);
	} else {
		crm_debug("Partial destroy: TE");
		crmd_ipc_connection_destroy(te_subsystem->client);
	}
	crm_free(te_subsystem);
	
	if(pe_subsystem->client && pe_subsystem->client->client_source) {
		crm_debug("Full destroy: PE");
		G_main_del_IPC_Channel(pe_subsystem->client->client_source);
	} else {
		crm_debug("Partial destroy: PE");
		crmd_ipc_connection_destroy(pe_subsystem->client);
	}
	crm_free(pe_subsystem);
	
	crm_free(cib_subsystem);
	
	if(integrated_nodes) {
		g_hash_table_destroy(integrated_nodes);
	}
	if(finalized_nodes) {
		g_hash_table_destroy(finalized_nodes);
	}
	if(confirmed_nodes) {
		g_hash_table_destroy(confirmed_nodes);
	}
	if(reload_hash) {
		g_hash_table_destroy(reload_hash);
	}
	if(resources) {
		g_hash_table_destroy(resources);
	}
	if(voted) {
		g_hash_table_destroy(voted);
	}

	cib_delete(fsa_cib_conn);
	fsa_cib_conn = NULL;

	if(fsa_lrm_conn) {
		fsa_lrm_conn->lrm_ops->delete(fsa_lrm_conn);
	}
	
	crm_free(integration_timer);
	crm_free(finalization_timer);
	crm_free(election_trigger);
	crm_free(election_timeout);
	crm_free(shutdown_escalation_timer);
	crm_free(wait_timer);
	crm_free(recheck_timer);

	crm_free(fsa_our_dc_version);
	crm_free(fsa_our_uname);
	crm_free(fsa_our_uuid);
	crm_free(fsa_our_dc);
	crm_free(ipc_server);

 	crm_free(max_generation_from);
 	free_xml(max_generation_xml);

	xmlCleanupParser();
}

/*	 A_EXIT_0, A_EXIT_1	*/
void
do_exit(long long action,
	enum crmd_fsa_cause cause,
	enum crmd_fsa_state cur_state,
	enum crmd_fsa_input current_input,
	fsa_data_t *msg_data)
{
	int exit_code = 0;
	int log_level = LOG_INFO;
	const char *exit_type = "gracefully";
	
	if(action & A_EXIT_1) {
		exit_code = 1;
		log_level = LOG_ERR;
		exit_type = "forcefully";
	}
	
	verify_stopped(cur_state, LOG_ERR);
	do_crm_log(log_level, "Performing %s - %s exiting the CRMd",
		      fsa_action2string(action), exit_type);
	
	if(is_set(fsa_input_register, R_IN_RECOVERY)) {
		crm_err("Could not recover from internal error");
		exit_code = 2;		
	} 
	if(is_set(fsa_input_register, R_STAYDOWN)) {
		crm_warn("Inhibiting respawn by Heartbeat");
		exit_code = 100;
	}

	free_mem(msg_data);
	
	crm_info("[%s] stopped (%d)", crm_system_name, exit_code);
	cl_flush_logs();
	exit(exit_code);
}

/*	 A_STARTUP	*/
void
do_startup(long long action,
	   enum crmd_fsa_cause cause,
	   enum crmd_fsa_state cur_state,
	   enum crmd_fsa_input current_input,
	   fsa_data_t *msg_data)
{
	int was_error = 0;
	int interval = 1; /* seconds between DC heartbeats */

	crm_debug("Registering Signal Handlers");
	mainloop_add_signal(SIGTERM, crm_shutdown);

	fsa_source = mainloop_add_trigger(G_PRIORITY_HIGH, crm_fsa_trigger, NULL);
	config_read = mainloop_add_trigger(G_PRIORITY_HIGH, crm_read_options, NULL);

	ipc_clients = g_hash_table_new(g_str_hash, g_str_equal);
	
	crm_debug("Creating CIB and LRM objects");
	fsa_cib_conn = cib_new();
	fsa_lrm_conn = ll_lrm_new(XML_CIB_TAG_LRM);	
	
	/* set up the timers */
	crm_malloc0(integration_timer, sizeof(fsa_timer_t));
	crm_malloc0(finalization_timer, sizeof(fsa_timer_t));
	crm_malloc0(election_trigger, sizeof(fsa_timer_t));
	crm_malloc0(election_timeout, sizeof(fsa_timer_t));
	crm_malloc0(shutdown_escalation_timer, sizeof(fsa_timer_t));
	crm_malloc0(wait_timer, sizeof(fsa_timer_t));
	crm_malloc0(recheck_timer, sizeof(fsa_timer_t));

	interval = interval * 1000;

	if(election_trigger != NULL) {
		election_trigger->source_id = 0;
		election_trigger->period_ms = -1;
		election_trigger->fsa_input = I_DC_TIMEOUT;
		election_trigger->callback = crm_timer_popped;
		election_trigger->repeat = FALSE;
	} else {
		was_error = TRUE;
	}
	
	if(election_timeout != NULL) {
		election_timeout->source_id = 0;
		election_timeout->period_ms = -1;
		election_timeout->fsa_input = I_ELECTION_DC;
		election_timeout->callback = crm_timer_popped;
		election_timeout->repeat = FALSE;
	} else {
		was_error = TRUE;
	}
	
	if(integration_timer != NULL) {
		integration_timer->source_id = 0;
		integration_timer->period_ms = -1;
		integration_timer->fsa_input = I_INTEGRATED;
		integration_timer->callback = crm_timer_popped;
		integration_timer->repeat = FALSE;
	} else {
		was_error = TRUE;
	}
	
	if(finalization_timer != NULL) {
		finalization_timer->source_id = 0;
		finalization_timer->period_ms = -1;
		finalization_timer->fsa_input = I_FINALIZED;
		finalization_timer->callback = crm_timer_popped;
		finalization_timer->repeat = FALSE;
		/* for possible enabling... a bug in the join protocol left
		 *    a slave in S_PENDING while we think its in S_NOT_DC
		 *
		 * raising I_FINALIZED put us into a transition loop which is
		 *    never resolved.
		 * in this loop we continually send probes which the node
		 *    NACK's because its in S_PENDING
		 *
		 * if we have nodes where heartbeat is active but the
		 *    CRM is not... then this will be handled in the
		 *    integration phase
		 */
		finalization_timer->fsa_input = I_ELECTION;

	} else {
		was_error = TRUE;
	}
	
	if(shutdown_escalation_timer != NULL) {
		shutdown_escalation_timer->source_id = 0;
		shutdown_escalation_timer->period_ms = -1;
		shutdown_escalation_timer->fsa_input = I_STOP;
		shutdown_escalation_timer->callback = crm_timer_popped;
		shutdown_escalation_timer->repeat = FALSE;
	} else {
		was_error = TRUE;
	}
	
	if(wait_timer != NULL) {
		wait_timer->source_id = 0;
		wait_timer->period_ms = 2000;
		wait_timer->fsa_input = I_NULL;
		wait_timer->callback = crm_timer_popped;
		wait_timer->repeat = FALSE;
	} else {
		was_error = TRUE;
	}

	if(recheck_timer != NULL) {
		recheck_timer->source_id = 0;
		recheck_timer->period_ms = -1;
		recheck_timer->fsa_input = I_PE_CALC;
		recheck_timer->callback = crm_timer_popped;
		recheck_timer->repeat = FALSE;
	} else {
		was_error = TRUE;
	}
	
	/* set up the sub systems */
	crm_malloc0(cib_subsystem, sizeof(struct crm_subsystem_s));
	crm_malloc0(te_subsystem,  sizeof(struct crm_subsystem_s));
	crm_malloc0(pe_subsystem,  sizeof(struct crm_subsystem_s));

	if(cib_subsystem != NULL) {
		cib_subsystem->pid      = -1;	
		cib_subsystem->path     = CRM_DAEMON_DIR;
		cib_subsystem->name     = CRM_SYSTEM_CIB;
		cib_subsystem->command  = CRM_DAEMON_DIR"/"CRM_SYSTEM_CIB;
		cib_subsystem->args     = "-VVc";
		cib_subsystem->flag_connected = R_CIB_CONNECTED;	
		cib_subsystem->flag_required  = R_CIB_REQUIRED;	

	} else {
		was_error = TRUE;
	}
	
	if(te_subsystem != NULL) {
		te_subsystem->pid      = -1;	
		te_subsystem->path     = CRM_DAEMON_DIR;
		te_subsystem->name     = CRM_SYSTEM_TENGINE;
		te_subsystem->command  = CRM_DAEMON_DIR"/"CRM_SYSTEM_TENGINE;
		te_subsystem->args     = NULL;
		te_subsystem->flag_connected = R_TE_CONNECTED;	
		te_subsystem->flag_required  = R_TE_REQUIRED;	
		
	} else {
		was_error = TRUE;
	}
	
	if(pe_subsystem != NULL) {
		pe_subsystem->pid      = -1;	
		pe_subsystem->path     = CRM_DAEMON_DIR;
		pe_subsystem->name     = CRM_SYSTEM_PENGINE;
		pe_subsystem->command  = CRM_DAEMON_DIR"/"CRM_SYSTEM_PENGINE;
		pe_subsystem->args     = NULL;
		pe_subsystem->flag_connected = R_PE_CONNECTED;	
		pe_subsystem->flag_required  = R_PE_REQUIRED;	
		
	} else {
		was_error = TRUE;
	}

	if(was_error) {
		register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
	}
	
	welcomed_nodes = g_hash_table_new_full(
		g_str_hash, g_str_equal,
		g_hash_destroy_str, g_hash_destroy_str);
	integrated_nodes = g_hash_table_new_full(
		g_str_hash, g_str_equal,
		g_hash_destroy_str, g_hash_destroy_str);
	finalized_nodes = g_hash_table_new_full(
		g_str_hash, g_str_equal,
		g_hash_destroy_str, g_hash_destroy_str);
	confirmed_nodes = g_hash_table_new_full(
		g_str_hash, g_str_equal,
		g_hash_destroy_str, g_hash_destroy_str);

	set_sigchld_proctrack(G_PRIORITY_HIGH,DEFAULT_MAXDISPATCHTIME);
}

/*	 A_STOP	*/
void
do_stop(long long action,
	enum crmd_fsa_cause cause,
	enum crmd_fsa_state cur_state,
	enum crmd_fsa_input current_input,
	fsa_data_t *msg_data)
{
    register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
}

/*	 A_STARTED	*/
void
do_started(long long action,
	   enum crmd_fsa_cause cause,
	   enum crmd_fsa_state cur_state,
	   enum crmd_fsa_input current_input,
	   fsa_data_t *msg_data)
{
	if(cur_state != S_STARTING) {
	    crm_err("Start cancelled... %s", fsa_state2string(cur_state));
	    return;
	    
	} else if(is_set(fsa_input_register, R_CCM_DATA) == FALSE) {
		crm_info("Delaying start, CCM (%.16llx) not connected",
			 R_CCM_DATA);

		crmd_fsa_stall(NULL);
		return;

	} else if(is_set(fsa_input_register, R_LRM_CONNECTED) == FALSE) {
		crm_info("Delaying start, LRM (%.16llx) not connected",
			 R_LRM_CONNECTED);

		crmd_fsa_stall(NULL);
		return;

	} else if(is_set(fsa_input_register, R_CIB_CONNECTED) == FALSE) {
		crm_info("Delaying start, CIB (%.16llx) not connected",
			 R_CIB_CONNECTED);

		crmd_fsa_stall(NULL);
		return;

	} else if(is_set(fsa_input_register, R_READ_CONFIG) == FALSE) {
		crm_info("Delaying start, Config not read (%.16llx)",
			 R_READ_CONFIG);

		crmd_fsa_stall(NULL);
		return;

	} else if(is_set(fsa_input_register, R_PEER_DATA) == FALSE) {
		HA_Message *msg = NULL;

		/* try reading from HA */
		crm_info("Delaying start, Peer data (%.16llx) not received",
			 R_PEER_DATA);

		crm_debug_3("Looking for a HA message");
#if SUPPORT_HEARTBEAT
		if(is_heartbeat_cluster()) {
		    msg = fsa_cluster_conn->llc_ops->readmsg(fsa_cluster_conn, 0);
		}
#endif		
		if(msg != NULL) {
			crm_debug_3("There was a HA message");
 			crm_msg_del(msg);
		}
		crmd_fsa_stall(NULL);
		return;
	}
	
	crm_debug("Init server comms");
	if(ipc_server == NULL) {
		ipc_server = crm_strdup(CRM_SYSTEM_CRMD);
	}

	if(init_server_ipc_comms(ipc_server, crmd_client_connect,
				 default_ipc_connection_destroy)) {
	    crm_err("Couldn't start IPC server");
	    register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
	}

	crm_info("The local CRM is operational");
	clear_bit_inplace(fsa_input_register, R_STARTING);
	register_fsa_input(msg_data->fsa_cause, I_PENDING, NULL);
}

/*	 A_RECOVER	*/
void
do_recover(long long action,
	   enum crmd_fsa_cause cause,
	   enum crmd_fsa_state cur_state,
	   enum crmd_fsa_input current_input,
	   fsa_data_t *msg_data)
{
	set_bit_inplace(fsa_input_register, R_IN_RECOVERY);
	crm_err("Action %s (%.16llx) not supported",
	       fsa_action2string(action), action);

	register_fsa_input(C_FSA_INTERNAL, I_TERMINATE, NULL);
}

pe_cluster_option crmd_opts[] = {
	/* name, old-name, validate, default, description */
	{ "dc-version", NULL, "string", NULL, "none", NULL, "Version of Pacemaker on the cluster's DC.", "Includes the hash which identifies the exact Mercurial changeset it was built from.  Used for diagnostic purposes." },
	{ "cluster-infrastructure", NULL, "string", NULL, "heartbeat", NULL, "The messaging stack on which Pacemaker is currently running.", "Used for informational and diagnostic purposes." },
	{ XML_CONFIG_ATTR_DC_DEADTIME, "dc_deadtime", "time", NULL, "60s", &check_time, "How long to wait for a response from other nodes during startup.", "The \"correct\" value will depend on the speed/load of your network and the type of switches used." },
	{ XML_CONFIG_ATTR_RECHECK, "cluster_recheck_interval", "time",
	  "Zero disables polling.  Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)", "15min", &check_timer,
	  "Polling interval for time based changes to options, resource parameters and constraints.",
	  "The Cluster is primarily event driven, however the configuration can have elements that change based on time."
	  "  To ensure these changes take effect, we can optionally poll the cluster's status for changes." },
	{ XML_CONFIG_ATTR_ELECTION_FAIL, "election_timeout", "time", NULL, "2min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
	{ XML_CONFIG_ATTR_FORCE_QUIT, "shutdown_escalation", "time", NULL, "20min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
	{ "crmd-integration-timeout", NULL, "time", NULL, "3min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
	{ "crmd-finalization-timeout", NULL, "time", NULL, "30min", &check_timer, "*** Advanced Use Only ***.", "If you need to adjust this value, it probably indicates the presence of a bug." },
	{ XML_ATTR_EXPECTED_VOTES, NULL, "integer", NULL, "2", &check_number, "The number of nodes expected to be in the cluster", "Used to calculate quorum in openais based clusters." },
};

void
crmd_metadata(void)
{
	config_metadata("CRM Daemon", "1.0",
			"CRM Daemon Options",
			"This is a fake resource that details the options that can be configured for the CRM Daemon.",
			crmd_opts, DIMOF(crmd_opts));
}

static void
verify_crmd_options(GHashTable *options)
{
	verify_all_options(options, crmd_opts, DIMOF(crmd_opts));
}

static const char *
crmd_pref(GHashTable *options, const char *name)
{
	return get_cluster_pref(options, crmd_opts, DIMOF(crmd_opts), name);
}

static void
config_query_callback(xmlNode *msg, int call_id, int rc,
		      xmlNode *output, void *user_data) 
{
	const char *value = NULL;
	GHashTable *config_hash = NULL;
	ha_time_t *now = new_ha_date(TRUE);

	if(rc != cib_ok) {
		fsa_data_t *msg_data = NULL;
		crm_err("Local CIB query resulted in an error: %s",
			cib_error2string(rc));
		register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);

		if(rc == cib_bad_permissions
		   || rc == cib_dtd_validation
		   || rc == cib_bad_digest
		   || rc == cib_bad_config) {
			crm_err("The cluster is mis-configured - shutting down and staying down");
			set_bit_inplace(fsa_input_register, R_STAYDOWN);
		}
		goto bail;
	}

	crm_debug("Call %d : Parsing CIB options", call_id);
	config_hash = g_hash_table_new_full(
		g_str_hash,g_str_equal, g_hash_destroy_str,g_hash_destroy_str);

	unpack_instance_attributes(
		output, output, XML_CIB_TAG_PROPSET, NULL, config_hash,
		CIB_OPTIONS_FIRST, FALSE, now);
	
	verify_crmd_options(config_hash);

	value = crmd_pref(config_hash, XML_CONFIG_ATTR_DC_DEADTIME);
	election_trigger->period_ms = crm_get_msec(value);
	
	value = crmd_pref(config_hash, XML_CONFIG_ATTR_FORCE_QUIT);
	shutdown_escalation_timer->period_ms = crm_get_msec(value);

	value = crmd_pref(config_hash, XML_CONFIG_ATTR_ELECTION_FAIL);
	election_timeout->period_ms = crm_get_msec(value);
	
	value = crmd_pref(config_hash, XML_CONFIG_ATTR_RECHECK);
	recheck_timer->period_ms = crm_get_msec(value);
	crm_info("Checking for expired actions every %dms", recheck_timer->period_ms);

	value = crmd_pref(config_hash, "crmd-integration-timeout");
	integration_timer->period_ms  = crm_get_msec(value);

	value = crmd_pref(config_hash, "crmd-finalization-timeout");
	finalization_timer->period_ms = crm_get_msec(value);

#if SUPPORT_AIS
	if(is_openais_cluster()) {
	    value = crmd_pref(config_hash, XML_ATTR_EXPECTED_VOTES);
	    crm_info("Sending expected-votes=%s to corosync", value);
	    send_ais_text(crm_class_quorum, value, TRUE, NULL, crm_msg_ais);	
	}
#endif
	
	set_bit_inplace(fsa_input_register, R_READ_CONFIG);
	crm_debug_3("Triggering FSA: %s", __FUNCTION__);
	mainloop_set_trigger(fsa_source);
	
	g_hash_table_destroy(config_hash);
  bail:
	free_ha_date(now);
}

gboolean
crm_read_options(gpointer user_data)
{
    int call_id = fsa_cib_conn->cmds->query(
	fsa_cib_conn, XML_CIB_TAG_CRMCONFIG, NULL, cib_scope_local);
    
    add_cib_op_callback(fsa_cib_conn, call_id, FALSE, NULL, config_query_callback);
    crm_debug_2("Querying the CIB... call %d", call_id);
    return TRUE;
}

/*	 A_READCONFIG	*/
void
do_read_config(long long action,
	       enum crmd_fsa_cause cause,
	       enum crmd_fsa_state cur_state,
	       enum crmd_fsa_input current_input,
	       fsa_data_t *msg_data)
{
    mainloop_set_trigger(config_read);	    
}


void
crm_shutdown(int nsig)
{
	if (crmd_mainloop != NULL && g_main_is_running(crmd_mainloop)) {
		if(is_set(fsa_input_register, R_SHUTDOWN)) {
			crm_err("Escalating the shutdown");
			register_fsa_input_before(C_SHUTDOWN, I_ERROR, NULL);

		} else {
			crm_info("Requesting shutdown");
			set_bit_inplace(fsa_input_register, R_SHUTDOWN);
			register_fsa_input(C_SHUTDOWN,I_SHUTDOWN,NULL);

			if(shutdown_escalation_timer->period_ms < 1) {
				const char *value = crmd_pref(NULL, XML_CONFIG_ATTR_FORCE_QUIT);
				int msec = crm_get_msec(value);
				crm_info("Using default shutdown escalation: %dms", msec);
				shutdown_escalation_timer->period_ms = msec;
			}

			/* cant rely on this... */
			crm_timer_start(shutdown_escalation_timer);
		}
		
	} else {
		crm_info("exit from shutdown");
		exit(LSB_EXIT_OK);
	    
	}
}

static void
default_cib_update_callback(xmlNode *msg, int call_id, int rc,
			    xmlNode *output, void *user_data) 
{
	if(rc != cib_ok) {
		fsa_data_t *msg_data = NULL;
		crm_err("CIB Update failed: %s", cib_error2string(rc));
		crm_log_xml_warn(output, "update:failed");
		
		register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
	}
}

#if SUPPORT_HEARTBEAT
static void
populate_cib_nodes_ha(gboolean with_client_status)
{
	int call_id = 0;
	const char *ha_node = NULL;
	xmlNode *cib_node_list = NULL;

	if(fsa_cluster_conn == NULL) {
	    crm_debug("Not connected");
	    return;
	}
	
	/* Async get client status information in the cluster */
	crm_info("Requesting the list of configured nodes");
	fsa_cluster_conn->llc_ops->init_nodewalk(fsa_cluster_conn);

	cib_node_list = create_xml_node(NULL, XML_CIB_TAG_NODES);
	do {
		const char *ha_node_type = NULL;
		const char *ha_node_uuid = NULL;
		xmlNode *cib_new_node = NULL;

		ha_node = fsa_cluster_conn->llc_ops->nextnode(fsa_cluster_conn);
		if(ha_node == NULL) {
			continue;
		}
		
		ha_node_type = fsa_cluster_conn->llc_ops->node_type(
			fsa_cluster_conn, ha_node);
		if(safe_str_neq(NORMALNODE, ha_node_type)) {
			crm_debug("Node %s: skipping '%s'",
				  ha_node, ha_node_type);
			continue;
		}

		ha_node_uuid = get_uuid(ha_node);
		if(ha_node_uuid == NULL) {
			crm_warn("Node %s: no uuid found", ha_node);
			continue;	
		}
		
		crm_debug("Node: %s (uuid: %s)", ha_node, ha_node_uuid);
		cib_new_node = create_xml_node(cib_node_list, XML_CIB_TAG_NODE);
		crm_xml_add(cib_new_node, XML_ATTR_ID,    ha_node_uuid);
		crm_xml_add(cib_new_node, XML_ATTR_UNAME, ha_node);
		crm_xml_add(cib_new_node, XML_ATTR_TYPE,  ha_node_type);

	} while(ha_node != NULL);

	fsa_cluster_conn->llc_ops->end_nodewalk(fsa_cluster_conn);
	
	/* Now update the CIB with the list of nodes */
	fsa_cib_update(
		XML_CIB_TAG_NODES, cib_node_list,
		cib_scope_local|cib_quorum_override, call_id);
	add_cib_op_callback(fsa_cib_conn, call_id, FALSE, NULL, default_cib_update_callback);

	free_xml(cib_node_list);
	crm_debug_2("Complete");
}

#endif

static void create_cib_node_definition(
    gpointer key, gpointer value, gpointer user_data)
{
    crm_node_t *node = value;
    xmlNode *cib_nodes = user_data;
    xmlNode *cib_new_node = NULL;
    
    cib_new_node = create_xml_node(cib_nodes, XML_CIB_TAG_NODE);
    crm_xml_add(cib_new_node, XML_ATTR_ID,    node->uuid);
    crm_xml_add(cib_new_node, XML_ATTR_UNAME, node->uname);
    crm_xml_add(cib_new_node, XML_ATTR_TYPE,  NORMALNODE);
}

void
populate_cib_nodes(gboolean with_client_status)
{
    int call_id = 0;
    xmlNode *cib_node_list = NULL;
#if SUPPORT_HEARTBEAT
    if(is_heartbeat_cluster()) {
	populate_cib_nodes_ha(with_client_status);
	return;
    }
#endif	

    cib_node_list = create_xml_node(NULL, XML_CIB_TAG_NODES);
    g_hash_table_foreach(
	crm_peer_cache, create_cib_node_definition, cib_node_list);    
    
    fsa_cib_update(
	XML_CIB_TAG_NODES, cib_node_list, cib_scope_local|cib_quorum_override, call_id);
    add_cib_op_callback(fsa_cib_conn, call_id, FALSE, NULL, default_cib_update_callback);
    
    free_xml(cib_node_list);
    crm_debug_2("Complete");
}
