/*
 * Stonith module for IBM Hardware Management Console (HMC)
 *
 * Author: Huang Zhen <zhenh@cn.ibm.com>
 * Support for HMC V4+ added by Dave Blaschke <debltc@us.ibm.com>
 *
 * Copyright (c) 2004 International Business Machines
 *
 * 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, see <http://www.gnu.org/licenses/>
 *
 */

/*
 *
 * This code has been tested in following environment:
 *
 *	Hardware Management Console (HMC): Release 3, Version 2.4
 *	- Both FullSystemPartition and LPAR Partition:
 *		- p630 7028-6C4 two LPAR partitions
 *		- p650 7038-6M2 one LPAR partition and FullSystemPartition
 *
 *	Hardware Management Console (HMC): Version 4, Release 2.1
 *	- OP720 1000-6CA three LPAR partitions
 *
 *	Note:  Only SSH access to the HMC devices are supported.
 *
 * This command would make a nice status command:
 *
 *	lshmc -r -F ssh
 *
 * The following V3 command will get the list of systems we control and their 
 * mode:
 *
 *	lssyscfg -r sys -F name:mode --all
 *
 *		0 indicates full system partition
 *	      255 indicates the system is partitioned
 *
 * The following V4 command will get the list of systems we control:
 *
 *	lssyscfg -r sys -F name
 *
 * The following V3 command will get the list of partitions for a given managed
 * system running partitioned:
 *
 *	lssyscfg -m managed-system -r lpar -F name --all
 *
 *	Note that we should probably only consider partitions whose boot mode
 *	is normal (1).  (that's my guess, anyway...)
 *
 * The following V4 command will get the list of partitions for a given managed
 * system running partitioned:
 *
 *	lssyscfg -m managed-system -r lpar -F name
 *
 * The following V3 commands provide the reset/on/off actions:
 *
 *	FULL SYSTEM:
 *	  on:	chsysstate -m %1 -r sys -o on -n %1 -c full
 *	  off:	chsysstate -m %1 -r sys -o off -n %1 -c full -b norm
 *	  reset:chsysstate -m %1 -r sys -o reset -n %1 -c full -b norm
 *
 *	Partitioned SYSTEM:
 *	  on:	chsysstate -m %1 -r lpar -o on -n %2
 *	  off:	reset_partition -m %1 -p %2 -t hard
 *	  reset:do off action above, followed by on action...
 *
 *	where %1 is managed-system, %2 is-lpar name
 *
 * The following V4 commands provide the reset/on/off actions:
 *
 *	  on:	chsysstate -m %1 -r lpar -o on -n %2 -f %3
 *	  off:	chsysstate -m %1 -r lpar -o shutdown -n %2 --immed
 *	  reset:chsysstate -m %1 -r lpar -o shutdown -n %2 --immed --restart
 *
 *	where %1 is managed-system, %2 is lpar-name, %3 is profile-name
 *
 * Of course, to do all this, we need to track which partition name goes with
 * which managed system's name, and which systems on the HMC are partitioned
 * and which ones aren't...
 */

#include <lha_internal.h>

#define DEVICE		"IBM HMC"

#include "stonith_plugin_common.h"

#ifndef	SSH_CMD
#	define SSH_CMD	"ssh"
#endif
#ifndef	HMCROOT
#	define HMCROOT	"hscroot"
#endif

#define PIL_PLUGIN              ibmhmc
#define PIL_PLUGIN_S            "ibmhmc"
#define PIL_PLUGINLICENSE 	LICENSE_LGPL
#define PIL_PLUGINLICENSEURL 	URL_LGPL
#include <pils/plugin.h>

#define MAX_HOST_NAME_LEN	(256*4)
#define MAX_CMD_LEN		2048
#define FULLSYSTEMPARTITION	"FullSystemPartition"
#define MAX_POWERON_RETRY	10
#define MAX_HMC_NAME_LEN	256

#define ST_MANSYSPAT		"managedsyspat"
#define NOPASS			"nopass"

#define STATE_UNKNOWN		-1
#define STATE_OFF		0
#define STATE_ON		1
#define STATE_INVALID		2

#define HMCURL	"http://publib-b.boulder.ibm.com/redbooks.nsf/RedbookAbstracts"\
		"/SG247038.html"

static StonithPlugin *	ibmhmc_new(const char *);
static void		ibmhmc_destroy(StonithPlugin *);
static const char *	ibmhmc_getinfo(StonithPlugin * s, int InfoType);
static const char * const *	ibmhmc_get_confignames(StonithPlugin* p);
static int		ibmhmc_status(StonithPlugin * );
static int		ibmhmc_reset_req(StonithPlugin * s,int request,const char* host);
static char **		ibmhmc_hostlist(StonithPlugin  *);
static int		ibmhmc_set_config(StonithPlugin *, StonithNVpair*);

static struct stonith_ops ibmhmcOps = {
	ibmhmc_new,		/* Create new STONITH object	*/
	ibmhmc_destroy,		/* Destroy STONITH object	*/
	ibmhmc_getinfo,		/* Return STONITH info string	*/
	ibmhmc_get_confignames,	/* Return configuration parameters */
	ibmhmc_set_config,      /* Set configuration            */
	ibmhmc_status,		/* Return STONITH device status	*/
	ibmhmc_reset_req,	/* Request a reset */
	ibmhmc_hostlist,	/* Return list of supported hosts */
};

PIL_PLUGIN_BOILERPLATE2("1.0", Debug)

static const PILPluginImports*  PluginImports;
static PILPlugin*               OurPlugin;
static PILInterface*		OurInterface;
static StonithImports*		OurImports;
static void*			interfprivate;


PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports);

PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports)
{
	/* Force the compiler to do a little type checking */
	(void)(PILPluginInitFun)PIL_PLUGIN_INIT;

	PluginImports = imports;
	OurPlugin = us;

	/* Register ourself as a plugin */
	imports->register_plugin(us, &OurPIExports);  

	/*  Register our interface implementation */
 	return imports->register_interface(us, PIL_PLUGINTYPE_S
	,	PIL_PLUGIN_S
	,	&ibmhmcOps
	,	NULL		/*close */
	,	&OurInterface
	,	(void*)&OurImports
	,	&interfprivate); 
}

struct pluginDevice {	
	StonithPlugin		sp;
	const char *		pluginid;
	char *			idinfo;
	char *			hmc;
	GList*		 	hostlist;
	int			hmcver;
	char *			password;
	char **			mansyspats;
};

static const char * pluginid = "HMCDevice-Stonith";
static const char * NOTpluginID = "IBM HMC device has been destroyed";

#include "stonith_config_xml.h"

#define XML_MANSYSPAT_SHORTDESC \
	XML_PARM_SHORTDESC_BEGIN("en") \
	ST_MANSYSPAT \
	XML_PARM_SHORTDESC_END

#define XML_MANSYSPAT_LONGDESC \
	XML_PARM_LONGDESC_BEGIN("en") \
	  "White-space delimited list of patterns used to match managed system names; if last character is '*', all names that begin with the pattern are matched" \
	XML_PARM_LONGDESC_END

#define XML_MANSYSPAT_PARM \
	XML_PARAMETER_BEGIN(ST_MANSYSPAT, "string", "0", "0") \
	  XML_MANSYSPAT_SHORTDESC \
	  XML_MANSYSPAT_LONGDESC \
	XML_PARAMETER_END

#define XML_OPTPASSWD_LONGDESC \
	XML_PARM_LONGDESC_BEGIN("en") \
	  "Password for " HMCROOT " if passwordless ssh access to HMC has NOT been setup (to do so, it is necessary to create a public/private key pair with empty passphrase - see \"Configure the OpenSSH Client\" in the redbook at " HMCURL " for more details)" \
	XML_PARM_LONGDESC_END

#define XML_OPTPASSWD_PARM \
	XML_PARAMETER_BEGIN(ST_PASSWD, "string", "0", "0") \
	  XML_PASSWD_SHORTDESC \
	  XML_OPTPASSWD_LONGDESC \
	XML_PARAMETER_END

static const char *ibmhmcXML = 
  XML_PARAMETERS_BEGIN
    XML_IPADDR_PARM
    XML_MANSYSPAT_PARM
    XML_OPTPASSWD_PARM
  XML_PARAMETERS_END;

static int get_hmc_hostlist(struct pluginDevice* dev);
static void free_hmc_hostlist(struct pluginDevice* dev);
static int get_hmc_mansyspats(struct pluginDevice* dev, const char* mansyspats);
static void free_hmc_mansyspats(struct pluginDevice* dev);
static char* do_shell_cmd(const char* cmd, int* status, const char* password);
static int check_hmc_status(struct pluginDevice* dev);
static int get_num_tokens(char *str);
static gboolean pattern_match(char **patterns, char *string);
/* static char* do_shell_cmd_fake(const char* cmd, int* status); */

static int
ibmhmc_status(StonithPlugin  *s)
{
	struct pluginDevice* dev = NULL;
	
	if(Debug){
		LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__);
	}

	ERRIFWRONGDEV(s,S_OOPS);

	dev = (struct pluginDevice*) s;
	
	return check_hmc_status(dev);
}


/*
 *	Return the list of hosts configured for this HMC device
 */

static char **
ibmhmc_hostlist(StonithPlugin  *s)
{
	int j;
	struct pluginDevice* dev;
	int numnames = 0;
	char** ret = NULL;
	GList* node = NULL;

	if(Debug){
		LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__);
	}

	ERRIFWRONGDEV(s,NULL);

	dev = (struct pluginDevice*) s;

	/* refresh the hostlist */
	free_hmc_hostlist(dev);
	if (S_OK != get_hmc_hostlist(dev)){
		LOG(PIL_CRIT, "unable to obtain list of managed systems in %s"
		,	__FUNCTION__);
		return NULL;
	}

	numnames = g_list_length(dev->hostlist);
	if (numnames < 0) {
		LOG(PIL_CRIT, "unconfigured stonith object in %s"
		,	__FUNCTION__);
		return(NULL);
	}

	ret = (char **)MALLOC((numnames+1)*sizeof(char*));
	if (ret == NULL) {
		LOG(PIL_CRIT, "out of memory");
		return ret;
	}

	memset(ret, 0, (numnames+1)*sizeof(char*));
	for (node = g_list_first(dev->hostlist), j = 0
	;	NULL != node
	;	j++, node = g_list_next(node))	{
		char* host = strchr((char*)node->data, '/');
		ret[j] = STRDUP(++host);
		if (ret[j] == NULL) {
			LOG(PIL_CRIT, "out of memory");
			stonith_free_hostlist(ret);
			return NULL;
		}
		strdown(ret[j]);
	}
	return ret;
}


static const char * const *
ibmhmc_get_confignames(StonithPlugin* p)
{
	static const char * names[] = {ST_IPADDR, NULL};
	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	return names;
}


/*
 *	Reset the given host, and obey the request type.
 *	We should reset without power cycle for the non-partitioned case
 */

static int
ibmhmc_reset_req(StonithPlugin * s, int request, const char * host)
{
	GList*			node = NULL;
	struct pluginDevice*	dev = NULL;
	char			off_cmd[MAX_CMD_LEN];
	char			on_cmd[MAX_CMD_LEN];
	char			reset_cmd[MAX_CMD_LEN];
	gchar**			names = NULL;
	int			i;
	int			is_lpar = FALSE;
	int			status;
	char*			pch;
	char*			output = NULL;
	char			state_cmd[MAX_CMD_LEN];
	int			state = STATE_UNKNOWN;
	
	status = 0;
	if(Debug){
		LOG(PIL_DEBUG, "%s: called, host=%s\n", __FUNCTION__, host);
	}
	
	ERRIFWRONGDEV(s,S_OOPS);
	
	if (NULL == host) {
		LOG(PIL_CRIT, "invalid argument to %s", __FUNCTION__);
		return(S_OOPS);
	}

	dev = (struct pluginDevice*) s;

	for (node = g_list_first(dev->hostlist)
	;	NULL != node
	;	node = g_list_next(node)) {
		if(Debug){
			LOG(PIL_DEBUG, "%s: node->data=%s\n"
			,	__FUNCTION__, (char*)node->data);
		}
		
		if ((pch = strchr((char*)node->data, '/')) != NULL
		&&  0 == strcasecmp(++pch, host)) {
			break;
		}
	}

	if (!node) {
		LOG(PIL_CRIT
		,	"Host %s is not configured in this STONITH module. "
			"Please check your configuration information.", host);
		return (S_OOPS);
	}

	names = g_strsplit((char*)node->data, "/", 2);
	/* names[0] will be the name of managed system */
	/* names[1] will be the name of the lpar partition */
	if(Debug){
		LOG(PIL_DEBUG, "%s: names[0]=%s, names[1]=%s\n"
		,	__FUNCTION__, names[0], names[1]);
	}

	if (dev->hmcver < 4) {
		if (0 == strcasecmp(names[1], FULLSYSTEMPARTITION)) {
			is_lpar = FALSE;
		
			snprintf(off_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s chsysstate"
			" -r sys -m %s -o off -n %s -c full"
			,	dev->hmc, dev->hmc, names[0]);

			snprintf(on_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s chsysstate"
			" -r sys -m %s -o on -n %s -c full -b norm"
			,	dev->hmc, names[0], names[0]);

			snprintf(reset_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s chsysstate"
			" -r sys -m %s -o reset -n %s -c full -b norm"
			,	dev->hmc, names[0], names[0]);
		
			*state_cmd = 0;
		}else{
			is_lpar = TRUE;
		
			snprintf(off_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s reset_partition"
			" -m %s -p %s -t hard"
			,	dev->hmc, names[0], names[1]);

			snprintf(on_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s chsysstate"
			" -r lpar -m %s -o on -n %s"
			,	dev->hmc, names[0], names[1]);

			*reset_cmd = 0;

			snprintf(state_cmd, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT " %s lssyscfg"
			" -r lpar -m %s -F state -n %s"
			,	dev->hmc, names[0], names[1]);
		}
	}else{
		is_lpar = TRUE;

		snprintf(off_cmd, MAX_CMD_LEN
		,	SSH_CMD " -l " HMCROOT " %s chsysstate"
		" -m %s -r lpar -o shutdown -n \"%s\" --immed"
		,	dev->hmc, names[0], names[1]);

		snprintf(on_cmd, MAX_CMD_LEN
		,	SSH_CMD " -l " HMCROOT " %s lssyscfg"
		" -m %s -r lpar -F \"default_profile\""
		" --filter \"lpar_names=%s\""
		,	dev->hmc, names[0], names[1]);

		output = do_shell_cmd(on_cmd, &status, dev->password);
		if (output == NULL) {
			LOG(PIL_CRIT, "command %s failed", on_cmd);
			return (S_OOPS);
		}
		if ((pch = strchr(output, '\n')) != NULL) {
			*pch = 0;
		}
		snprintf(on_cmd, MAX_CMD_LEN
		,	SSH_CMD " -l " HMCROOT " %s chsysstate"
		" -m %s -r lpar -o on -n %s -f %s"
		,	dev->hmc, names[0], names[1], output);
		FREE(output);
		output = NULL;

		snprintf(reset_cmd, MAX_CMD_LEN
		,	SSH_CMD " -l " HMCROOT " %s chsysstate"
		" -m %s -r lpar -o shutdown -n %s --immed --restart"
		,	dev->hmc, names[0], names[1]);

		snprintf(state_cmd, MAX_CMD_LEN
		,	SSH_CMD " -l " HMCROOT " %s lssyscfg"
		" -m %s -r lpar -F state --filter \"lpar_names=%s\""
		,	dev->hmc, names[0], names[1]);
	}
	g_strfreev(names);
	
	if(Debug){
		LOG(PIL_DEBUG, "%s: off_cmd=%s, on_cmd=%s,"
			"reset_cmd=%s, state_cmd=%s\n" 
		,	__FUNCTION__, off_cmd, on_cmd, reset_cmd, state_cmd);
	}

	output = do_shell_cmd(state_cmd, &status, dev->password);
	if (output == NULL) {
		LOG(PIL_CRIT, "command %s failed", on_cmd);
		return S_OOPS;
	}
	if ((pch = strchr(output, '\n')) != NULL) {
		*pch = 0;
	}
	if (strcmp(output, "Running") == 0
	|| strcmp(output, "Starting") == 0
	|| strcmp(output, "Open Firmware") == 0) {
		state = STATE_ON;
	}else if (strcmp(output, "Shutting Down") == 0
	|| strcmp(output, "Not Activated") == 0
	|| strcmp(output, "Ready") == 0) {
		state = STATE_OFF;
	}else if (strcmp(output, "Not Available") == 0
	|| strcmp(output, "Error") == 0) {
		state = STATE_INVALID;
	}
	FREE(output);
	output = NULL;

	if (state == STATE_INVALID) {
		LOG(PIL_CRIT, "host %s in invalid state", host);
		return S_OOPS;
	}

	switch (request) {
	case ST_POWERON:
		if (state == STATE_ON) {
			LOG(PIL_INFO, "host %s already on", host);
			return S_OK;
		}

		output = do_shell_cmd(on_cmd, &status, dev->password);
		if (0 != status) {
			LOG(PIL_CRIT, "command %s failed", on_cmd);
			return S_OOPS;
		}
		break;
	case ST_POWEROFF:
		if (state == STATE_OFF) {
			LOG(PIL_INFO, "host %s already off", host);
			return S_OK;
		}

		output = do_shell_cmd(off_cmd, &status, dev->password);
		if (0 != status) {
			LOG(PIL_CRIT, "command %s failed", off_cmd);
			return S_OOPS;
		}
		break;
	case ST_GENERIC_RESET:
		if (dev->hmcver < 4) {
			if (is_lpar) {
				if (state == STATE_ON) {
					output = do_shell_cmd(off_cmd
					, &status, dev->password);
					if (0 != status) {
						LOG(PIL_CRIT, "command %s "
							"failed", off_cmd);
						return S_OOPS;
					}
				}
				for (i = 0; i < MAX_POWERON_RETRY; i++) {
					char *output2;
					output2 = do_shell_cmd(on_cmd
					, &status, dev->password);
					if (output2 != NULL) {
						FREE(output2);
					}
					if (0 != status) {
						sleep(1);
					}else{
						break;
					}
				}
				if (MAX_POWERON_RETRY == i) {
					LOG(PIL_CRIT, "command %s failed"
					,	on_cmd);
					return S_OOPS;
				}
			}else{
				output = do_shell_cmd(reset_cmd
				, &status, dev->password);
				if (0 != status) {
					LOG(PIL_CRIT, "command %s failed"						,	reset_cmd);
					return S_OOPS;
				}
				break;
			}
		}else{
			if (state == STATE_ON) {
				output = do_shell_cmd(reset_cmd
				, &status, dev->password);
			}else{
				output = do_shell_cmd(on_cmd
				, &status, dev->password);
			}
			if (0 != status) {
				LOG(PIL_CRIT, "command %s failed", reset_cmd);
				return S_OOPS;
			}
		}
		break;
	default:
		return S_INVAL;
	}

	if (output != NULL) {
		FREE(output);
	}
		
	LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request);

	return S_OK;
}


/*
 *	Parse the information in the given configuration file,
 *	and stash it away...
 */

static int
ibmhmc_set_config(StonithPlugin * s, StonithNVpair* list)
{
	struct pluginDevice* dev = NULL;
	StonithNamesToGet	namestocopy [] =
	{	{ST_IPADDR,	NULL}
	,	{NULL,		NULL}
	};
	int rc;
	char get_hmcver[MAX_CMD_LEN];
	char firstchar;
	int firstnum;
	char* output = NULL;
	int status;
	const char *mansyspats;
	int len;
	
	ERRIFWRONGDEV(s,S_OOPS);

	if(Debug){
		LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__);
	}
	
	dev = (struct pluginDevice*) s;

	if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) {
		return rc;
	}
	if(Debug){
		LOG(PIL_DEBUG, "%s: ipaddr=%s\n", __FUNCTION__
		,	namestocopy[0].s_value);	
	}

	if (get_num_tokens(namestocopy[0].s_value) == 1) {
		/* name=value pairs on command line, look for managedsyspat */
		mansyspats = OurImports->GetValue(list, ST_MANSYSPAT);
		if (mansyspats != NULL) {
			if (get_hmc_mansyspats(dev, mansyspats) != S_OK) {
				FREE(namestocopy[0].s_value);
				return S_OOPS;
			}
		}
		/* look for password */
		dev->password = STRDUP(OurImports->GetValue(list, ST_PASSWD));
		dev->hmc = namestocopy[0].s_value;
	}else{
		/* -p or -F option with args "ipaddr [managedsyspat]..." */
		char *pch = namestocopy[0].s_value;

		/* skip over ipaddr and null-terminate */
		pch += strcspn(pch, WHITESPACE);
		*pch = EOS;

		/* skip over white-space up to next token */
		pch++;
		pch += strspn(pch, WHITESPACE);
		if (get_hmc_mansyspats(dev, pch) != S_OK) {
			FREE(namestocopy[0].s_value);
			return S_OOPS;
		}

		dev->hmc = STRDUP(namestocopy[0].s_value);
		FREE(namestocopy[0].s_value);
	}
	
	/* check whether the HMC has ssh command enabled */
	if (check_hmc_status(dev) != S_OK) {
		LOG(PIL_CRIT, "HMC %s does not have remote "
		"command execution using the ssh facility enabled", dev->hmc);
		return S_BADCONFIG;
	}		

	/* get the HMC's version info */
	snprintf(get_hmcver, MAX_CMD_LEN
	,	SSH_CMD " -l " HMCROOT " %s lshmc -v | grep RM", dev->hmc);
	if (Debug) {
		LOG(PIL_DEBUG, "%s: get_hmcver=%s", __FUNCTION__, get_hmcver);
	}

	output = do_shell_cmd(get_hmcver, &status, dev->password);
	if (Debug) {
		LOG(PIL_DEBUG, "%s: output=%s\n", __FUNCTION__
		, output ? output : "(nil)");
	}
	if (output == NULL) {
		return S_BADCONFIG;
	}		

	/* parse the HMC's version info (i.e. "*RM V4R2.1" or "*RM R3V2.6") */
	if ((sscanf(output, "*RM %c%1d", &firstchar, &firstnum) == 2)
	&& ((firstchar == 'V') || (firstchar == 'R'))) {
		dev->hmcver = firstnum;
		if(Debug){
			LOG(PIL_DEBUG, "%s: HMC %s version is %d"
			,	__FUNCTION__, dev->hmc, dev->hmcver);
		}
	}else{
		LOG(PIL_CRIT, "%s: unable to determine HMC %s version"
		,	__FUNCTION__, dev->hmc);
		FREE(output);
		return S_BADCONFIG;
	}

	len = strlen(output+4) + sizeof(DEVICE) + 1;
	if (dev->idinfo != NULL) {
		FREE(dev->idinfo);
		dev->idinfo = NULL;
	}
	dev->idinfo = MALLOC(len * sizeof(char));
	if (dev->idinfo == NULL) {
		LOG(PIL_CRIT, "out of memory");
		FREE(output);
		return S_OOPS;
	}
	snprintf(dev->idinfo, len, "%s %s", DEVICE, output+4);
	FREE(output);

	if (S_OK != get_hmc_hostlist(dev)){
		LOG(PIL_CRIT, "unable to obtain list of managed systems in %s"
		,	__FUNCTION__);
		return S_BADCONFIG;
	}
	
	return S_OK;
}


static const char*
ibmhmc_getinfo(StonithPlugin* s, int reqtype)
{
	struct pluginDevice* dev;
	const char* ret;

	ERRIFWRONGDEV(s,NULL);

	dev = (struct pluginDevice *)s;

	switch (reqtype) {
		case ST_DEVICEID:
			ret = dev->idinfo;
			break;

		case ST_DEVICENAME:
			ret = dev->hmc;
			break;

		case ST_DEVICEDESCR:
			ret = "IBM Hardware Management Console (HMC)\n"
			"Use for IBM i5, p5, pSeries and OpenPower systems "
			"managed by HMC\n"
			"  Optional parameter name " ST_MANSYSPAT " is "
			"white-space delimited list of\n"
			"patterns used to match managed system names; if last "
			"character is '*',\n"
			"all names that begin with the pattern are matched\n"
			"  Optional parameter name " ST_PASSWD " is password "
			"for " HMCROOT " if passwordless\n"
			"ssh access to HMC has NOT been setup (to do so, it "
			"is necessary to create\n"
			"a public/private key pair with empty passphrase - "
			"see \"Configure the\n"
			"OpenSSH client\" in the redbook for more details)";
			break;

		case ST_DEVICEURL:
			ret = HMCURL;
			break;

		case ST_CONF_XML:		/* XML metadata */
			ret = ibmhmcXML;
			break;

		default:
			ret = NULL;
			break;
	}
	return ret;
}


/*
 *	HMC Stonith destructor...
 */

static void
ibmhmc_destroy(StonithPlugin *s)
{
	struct pluginDevice* dev;

	if(Debug){
		LOG(PIL_DEBUG, "%s : called\n", __FUNCTION__);
	}

	VOIDERRIFWRONGDEV(s);

	dev = (struct pluginDevice *)s;

	dev->pluginid = NOTpluginID;
	if (dev->hmc) {
		FREE(dev->hmc);
		dev->hmc = NULL;
	}
	if (dev->password) {
		FREE(dev->password);
		dev->password = NULL;
	}
	if (dev->idinfo) {
		FREE(dev->idinfo);
		dev->idinfo = NULL;
	}
	free_hmc_hostlist(dev);
	free_hmc_mansyspats(dev);
	
	FREE(dev);
}


static StonithPlugin *
ibmhmc_new(const char *subplugin)
{
	struct pluginDevice* dev = ST_MALLOCT(struct pluginDevice);
	
	if(Debug){
		LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__);
	}
	
	if (dev == NULL) {
		LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
		return(NULL);
	}

	memset(dev, 0, sizeof(*dev));

	dev->pluginid = pluginid;
	dev->hmc = NULL;
	dev->password = NULL;
	dev->hostlist = NULL;
	dev->mansyspats = NULL;
	dev->hmcver = -1;
	REPLSTR(dev->idinfo, DEVICE);
	if (dev->idinfo == NULL) {
		FREE(dev);
		return(NULL);
	}
	dev->sp.s_ops = &ibmhmcOps;

	if(Debug){
		LOG(PIL_DEBUG, "%s: returning successfully\n", __FUNCTION__);
	}

	return((void *)dev);
}

static int
get_hmc_hostlist(struct pluginDevice* dev)
{
	int i, j, status;
	char* output = NULL;
	char get_syslist[MAX_CMD_LEN];
	char host[MAX_HOST_NAME_LEN];
	gchar** syslist = NULL;
	gchar** name_mode = NULL;
	char get_lpar[MAX_CMD_LEN];
	gchar** lparlist = NULL;
	char* pch;

	if(Debug){
		LOG(PIL_DEBUG, "%s: called, dev->hmc=%s\n", __FUNCTION__
		,	dev->hmc);
	}

	if (dev->hmc == NULL || *dev->hmc == 0){
		return S_BADCONFIG;
	}
	
	/* get the managed system's names of the hmc */
	if (dev->hmcver < 4) {
		snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD " -l " HMCROOT
			" %s lssyscfg -r sys -F name:mode --all", dev->hmc);
	}else{
		snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD 
			" -l " HMCROOT " %s lssyscfg -r sys -F name", dev->hmc);
	}
	if(Debug){
		LOG(PIL_DEBUG, "%s: get_syslist=%s", __FUNCTION__, get_syslist);
	}

	output = do_shell_cmd(get_syslist, &status, dev->password);
	if (output == NULL) {
		return S_BADCONFIG;
	}		
	syslist = g_strsplit(output, "\n", 0);
	FREE(output);

	/* for each managed system */
	for (i = 0; syslist[i] != NULL && syslist[i][0] != 0; i++) {
		if (dev->hmcver < 4) {
			name_mode = g_strsplit(syslist[i], ":", 2);
			if(Debug){
			LOG(PIL_DEBUG, "%s: name_mode0=%s, name_mode1=%s\n"
			,	__FUNCTION__, name_mode[0], name_mode[1]);
			}

			if (dev->mansyspats != NULL
			&& !pattern_match(dev->mansyspats, name_mode[0])) {
				continue;
			}

			/* if it is in fullsystempartition */
			if (NULL != name_mode[1]
			&& 0 == strncmp(name_mode[1], "0", 1)) {
				/* add the FullSystemPartition */
				snprintf(host, MAX_HOST_NAME_LEN
				,	"%s/FullSystemPartition", name_mode[0]);
				dev->hostlist = g_list_append(dev->hostlist 
				,	STRDUP(host));
			}else if (NULL != name_mode[1]
			&& 0 == strncmp(name_mode[1], "255", 3)){
				/* get its lpars */
				snprintf(get_lpar, MAX_CMD_LEN
				,	SSH_CMD " -l " HMCROOT
				" %s lssyscfg -m %s -r lpar -F name --all"
				,	dev->hmc, name_mode[0]);
				if(Debug){
					LOG(PIL_DEBUG, "%s: get_lpar=%s\n"
					,	__FUNCTION__, get_lpar);
				}

				output = do_shell_cmd(get_lpar
				, &status, dev->password);
				if (output == NULL) {
					g_strfreev(name_mode);
					g_strfreev(syslist);
					return S_BADCONFIG;
				}		
				lparlist = g_strsplit(output, "\n", 0);
				FREE(output);
	
				/* for each lpar */
				for (j = 0
				; NULL != lparlist[j] && 0 != lparlist[j][0]
				; j++) {
					/* skip the full system partition */
					if (0 == strncmp(lparlist[j]
					,	FULLSYSTEMPARTITION
					,	strlen(FULLSYSTEMPARTITION))) {
						continue;
					}
					/* add the lpar */
					snprintf(host, MAX_HOST_NAME_LEN
					,	"%s/%s", name_mode[0]
					,	lparlist[j]);
					dev->hostlist = 
						g_list_append(dev->hostlist
						,	STRDUP(host));
				}
				g_strfreev(lparlist);
			}
			g_strfreev(name_mode);
		}else{
			if (dev->mansyspats != NULL
			&& !pattern_match(dev->mansyspats, syslist[i])) {
				continue;
			}

			/* get its state */
			snprintf(get_lpar, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT
				 " %s lssyscfg -m %s -r sys -F state"
			,	dev->hmc, syslist[i]);
			if(Debug){
				LOG(PIL_DEBUG, "%s: get_lpar=%s\n"
				,	__FUNCTION__, get_lpar);
			}

			output = do_shell_cmd(get_lpar, &status, dev->password);
			if (output == NULL) {
				g_strfreev(syslist);
				return S_BADCONFIG;
			}		
			if ((pch = strchr(output, '\n')) != NULL) {
				*pch = 0;
			}
			if (!strcmp(output, "No Connection")){
				FREE(output);
				continue;
			}
			FREE(output);

			/* get its lpars */
			snprintf(get_lpar, MAX_CMD_LEN
			,	SSH_CMD " -l " HMCROOT
				 " %s lssyscfg -m %s -r lpar -F name"
			,	dev->hmc, syslist[i]);
			if(Debug){
				LOG(PIL_DEBUG, "%s: get_lpar=%s\n"
				,	__FUNCTION__, get_lpar);
			}

			output = do_shell_cmd(get_lpar, &status, dev->password);
			if (output == NULL) {
				g_strfreev(syslist);
				return S_BADCONFIG;
			}		
			lparlist = g_strsplit(output, "\n", 0);
			FREE(output);

			/* for each lpar */
			for (j = 0
			; NULL != lparlist[j] && 0 != lparlist[j][0]
			; j++) {
				/* add the lpar */
				snprintf(host, MAX_HOST_NAME_LEN
				,	"%s/%s", syslist[i],lparlist[j]);
				dev->hostlist = g_list_append(dev->hostlist
						,	STRDUP(host));
			}
			g_strfreev(lparlist);
		}
	}
	g_strfreev(syslist);
	
	return S_OK;
}

static void
free_hmc_hostlist(struct pluginDevice* dev)
{
	if (dev->hostlist) {
		GList* node;
		while (NULL != (node=g_list_first(dev->hostlist))) {
			dev->hostlist = g_list_remove_link(dev->hostlist, node);
			FREE(node->data);
			g_list_free(node);
		}
		dev->hostlist = NULL;
	}
}

static int
get_hmc_mansyspats(struct pluginDevice * dev, const char *mansyspats)
{
	char *patscopy;
	int numpats;	
	int i;
	char *tmp;

	if(Debug){
		LOG(PIL_DEBUG, "%s: called, mansyspats=%s\n"
		,	__FUNCTION__, mansyspats);
	}
	
	patscopy = STRDUP(mansyspats);
	if (patscopy == NULL) {
		LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
		return S_OOPS;
	}

	numpats = get_num_tokens(patscopy);	
	if (numpats > 0) {
		dev->mansyspats = MALLOC((numpats+1)*sizeof(char *));
		if (dev->mansyspats == NULL) {
			LOG(PIL_CRIT, "%s: out of memory"
			,	__FUNCTION__);
			FREE(patscopy);
			return S_OOPS;
		}

		memset(dev->mansyspats, 0, (numpats+1)*sizeof(char *));

		/* White-space split the output here */
		i = 0;
		tmp = strtok(patscopy, WHITESPACE);
		while (tmp != NULL) {
			dev->mansyspats[i] = STRDUP(tmp);
			if (dev->mansyspats[i] == NULL) {
				LOG(PIL_CRIT, "%s: out of memory"
				,	__FUNCTION__);
				free_hmc_mansyspats(dev);
				dev->mansyspats = NULL;
				FREE(patscopy);
				return S_OOPS;
			}
	
			if(Debug){
				LOG(PIL_DEBUG, "%s: adding pattern %s\n"
				,	__FUNCTION__, dev->mansyspats[i]);
			}

			/* no patterns necessary if all specified */
			if (strcmp(dev->mansyspats[i], "*") == 0) {
				stonith_free_hostlist(dev->mansyspats);
				dev->mansyspats = NULL;
				break;
			}

			i++;
			tmp = strtok(NULL, WHITESPACE);
		}
	}
	FREE(patscopy);
	return S_OK;
}

static void
free_hmc_mansyspats(struct pluginDevice* dev)
{
	if (dev->mansyspats) {
		stonith_free_hostlist(dev->mansyspats);
		dev->mansyspats = NULL;
	}
}

static char*
do_shell_cmd(const char* cmd, int* status, const char* password)
{
	const int BUFF_LEN=4096;
	int read_len = 0;
	char buff[BUFF_LEN];
	char cmd_password[MAX_CMD_LEN];
	char* data = NULL;
	GString* g_str_tmp = NULL;

	FILE* file;
	if (NULL == password) {
		file = popen(cmd, "r");
	} else {
		snprintf(cmd_password, MAX_CMD_LEN
		,"umask 077;"
		 "if [ ! -d  " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc ];"
		 "then mkdir " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc 2>/dev/null;"
		 "fi;"
		 "export ibmhmc_tmp=`mktemp -p " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc/`;" 
		 "echo \"echo '%s'\">$ibmhmc_tmp;" 
		 "chmod +x $ibmhmc_tmp;"
		 "unset SSH_AGENT_SOCK SSH_AGENT_PID;"
		 "SSH_ASKPASS=$ibmhmc_tmp DISPLAY=ibmhmc_foo setsid %s;"
		 "rm $ibmhmc_tmp -f;"
		 "unset ibmhmc_tmp"
		,password, cmd);
		file = popen(cmd_password, "r"); 
	}		
	if (NULL == file) {
		return NULL;
	}

	g_str_tmp = g_string_new("");
	while(!feof(file)) {
		memset(buff, 0, BUFF_LEN);
		read_len = fread(buff, 1, BUFF_LEN, file);
		if (0 < read_len) {
			g_string_append(g_str_tmp, buff);
		}else{
			sleep(1);
		}
	}
	data = (char*)MALLOC(g_str_tmp->len+1);
	if (data != NULL) {
		data[0] = data[g_str_tmp->len] = 0;
		strncpy(data, g_str_tmp->str, g_str_tmp->len);
	}
	g_string_free(g_str_tmp, TRUE);
	*status = pclose(file);
	return data;
}

static int
check_hmc_status(struct pluginDevice* dev)
{
	int status;
	char check_status[MAX_CMD_LEN];
	char* output = NULL;
	int rc = S_OK;

	if(Debug){
		LOG(PIL_DEBUG, "%s: called, hmc=%s\n", __FUNCTION__, dev->hmc);
	}

	snprintf(check_status, MAX_CMD_LEN
	,	SSH_CMD " -l " HMCROOT " %s lshmc -r -F ssh", dev->hmc);
	if(Debug){
		LOG(PIL_DEBUG, "%s: check_status %s\n", __FUNCTION__
		,	check_status);
	}

	output = do_shell_cmd(check_status, &status, dev->password);
	if (Debug) {
		LOG(PIL_DEBUG, "%s: status=%d, output=%s\n", __FUNCTION__
		,	status, output ? output : "(nil)");
	}

	if (NULL == output || strncmp(output, "enable", 6) != 0) {
		rc = S_BADCONFIG;
	}
	if (NULL != output) {
		FREE(output);
	}
	return rc;
}

static int
get_num_tokens(char *str)
{
	int namecount = 0;

	while (*str != EOS) {
		str += strspn(str, WHITESPACE);
		if (*str == EOS)
			break;
		str += strcspn(str, WHITESPACE);
		namecount++;
	}
	return namecount;
}

static gboolean
pattern_match(char **patterns, char *string)
{
	char **pattern;

	if(Debug){
		LOG(PIL_DEBUG, "%s: called, string=%s\n", __FUNCTION__, string);
	}

	for (pattern = patterns; *pattern; pattern++) {
		int patlen = strlen(*pattern);

		if (pattern[0][patlen-1] == '*') {
			/* prefix match */
			if (strncmp(string, *pattern, patlen-1) == 0) {
				return TRUE;
			}
		}else{
			/* exact match */
			if (strcmp(string, *pattern) == 0) {
				return TRUE;
			}
		}
	}

	return FALSE;
}

/*
static char*
do_shell_cmd_fake(const char* cmd, int* status)
{
	printf("%s()\n", __FUNCTION__);
	printf("cmd:%s\n", cmd);
	*status=0;
	return NULL;
}
*/
