/*
 * Stonith module for APCSmart Stonith device
 * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Original version of this UPS code was taken from:
 *   'Network UPS Tools' by Russell Kroll <rkroll@exploits.org>
 *   homepage: http://www.networkupstools.org/
 *
 *  Significantly mangled by Alan Robertson <alanr@unix.sh>
 */

#include <lha_internal.h>

#define	DEVICE	                "APCSmart"

#include "stonith_plugin_common.h"

/*
 * APCSmart (tested with old 900XLI, APC SmartUPS 700 and SmartUPS-1000)
 *
 * The reset is a combined reset: "S" and "@000"
 * The "S" command tells the ups that if it is on-battery, it should
 * remain offline until the power is back. 
 * If that command is not accepted, the "@000" command will be sent
 * to tell the ups to turn off and back on right away.
 * In both cases, if the UPS supports a 20 second shutdown grace
 * period (such as on the 900XLI), the shutdown will delay that long,
 * otherwise the shutdown will happen immediately (the code searches
 * for the smallest possible delay).
 */

#define CFG_FILE		"/etc/ha.d/apcsmart.cfg"

#define MAX_DEVICES		1

#define SERIAL_TIMEOUT		3	/* timeout in sec */
#define SEND_DELAY		50000	/* in microseconds */
#define ENDCHAR			10	/* use LF */
#define MAX_STRING              512
#define MAX_DELAY_STRING        16
#define SWITCH_TO_NEXT_VAL	"-"	/* APC cmd for cycling through
					 * the values
					 */

#define CMD_SMART_MODE          "Y"
#define RSP_SMART_MODE		"SM"
#define CMD_GET_STATUS		"Q"
#define RSP_GET_STATUS		NULL
#define CMD_RESET               "S"	/* turn off & stay off if on battery */
#define CMD_RESET2              "@000"	/* turn off & immediately turn on */
#define RSP_RESET		"*"	/* RESET response from older models */
#define RSP_RESET2		"OK"	/* RESET response from newer models */
#define	RSP_NA			"NA"
#define	CMD_READREG1		"~"
#define CMD_OFF			"Z"
#define CMD_ON			"\016" /* (control-n) */
#define CMD_SHUTDOWN_DELAY	"p"
#define CMD_WAKEUP_DELAY	"r"

#define CR			13

struct pluginDevice {
	StonithPlugin	sp;
	const char *	pluginid; /* of object				*/
	const char *	idinfo;   /* type of device			*/
	char **		hostlist; /* served by the device (only 1)	*/
	int		hostcount;/* of hosts (1)			*/
	char *		upsdev;   /*					*/
	int		upsfd;    /* for serial port			*/
	int		retries;
	char		shutdown_delay[MAX_DELAY_STRING];
	char		old_shutdown_delay[MAX_DELAY_STRING];
	char		wakeup_delay[MAX_DELAY_STRING];
	char		old_wakeup_delay[MAX_DELAY_STRING];
};

/* saving old settings */
/* FIXME!  These should be part of pluginDevice struct above */
static struct termios old_tio;

static int f_serialtimeout;	/* flag for timeout */
static const char *pluginid = "APCSmart-Stonith";
static const char *NOTpluginID = "APCSmart device has been destroyed";

/*
 * stonith prototypes 
 */

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

#include "stonith_signal.h"

static StonithPlugin *	apcsmart_new(const char *);
static void		apcsmart_destroy(StonithPlugin *);
static const char**	apcsmart_get_confignames(StonithPlugin*);
static int		apcsmart_set_config(StonithPlugin *, StonithNVpair*);
static const char *	apcsmart_get_info(StonithPlugin * s, int InfoType);
static int		apcsmart_status(StonithPlugin * );
static int		apcsmart_reset_req(StonithPlugin * s, int request, const char * host);
static char **		apcsmart_hostlist(StonithPlugin  *);

static struct stonith_ops apcsmartOps ={
	apcsmart_new,		  /* Create new STONITH object		*/
	apcsmart_destroy,	  /* Destroy STONITH object		*/
	apcsmart_get_info,	  /* Return STONITH info string		*/
	apcsmart_get_confignames, /* Return STONITH info string		*/
	apcsmart_set_config,	  /* Get configuration from NVpairs	*/
	apcsmart_status,	  /* Return STONITH device status	*/
	apcsmart_reset_req,	  /* Request a reset 			*/
	apcsmart_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
	,	&apcsmartOps
	,	NULL		/*close */
	,	&OurInterface
	,	(void*)&OurImports
	,	&interfprivate); 
}

#include "stonith_config_xml.h"

static const char *apcsmartXML = 
  XML_PARAMETERS_BEGIN
    XML_TTYDEV_PARM
    XML_HOSTLIST_PARM
  XML_PARAMETERS_END;

/*
 * own prototypes 
 */

int APC_open_serialport(const char *port, speed_t speed);
void APC_close_serialport(const char *port, int upsfd);
void APC_sh_serial_timeout(int sig);
int APC_send_cmd(int upsfd, const char *cmd);
int APC_recv_rsp(int upsfd, char *rsp);
int APC_enter_smartmode(int upsfd);
int APC_set_ups_var(int upsfd, const char *cmd, char *newval);
int APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay);
int APC_init( struct pluginDevice *ad );
void APC_deinit( struct pluginDevice *ad );

/*
 * Signal handler for serial port timeouts 
 */

void
APC_sh_serial_timeout(int sig)
{
	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}

	STONITH_IGNORE_SIG(SIGALRM);

	if (Debug) {
		LOG(PIL_DEBUG, "%s: serial port timed out.", __FUNCTION__);
	}

	f_serialtimeout = TRUE;

    return;
}

/*
 * Open serial port and set it to b2400 
 */

int
APC_open_serialport(const char *port, speed_t speed)
{
	struct termios tio;
	int fd;
	int rc;
	int errno_save;
	int fflags;

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

	if ((rc = OurImports->TtyLock(port)) < 0) {
		LOG(PIL_CRIT, "%s: Could not lock tty %s [rc=%d]."
		,	__FUNCTION__, port, rc);
		return -1;
	}

	STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout);
	alarm(SERIAL_TIMEOUT);
	f_serialtimeout = FALSE;

	fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL);
	errno_save = errno;

	alarm(0);
	STONITH_IGNORE_SIG(SIGALRM);

	if (fd < 0) {
		LOG(PIL_CRIT, "%s: Open of %s %s [%s].", __FUNCTION__
		,	port
		,	f_serialtimeout ? "timed out" : "failed"
		,	strerror(errno_save));
		OurImports->TtyUnlock(port);
		return -1;
	}

	if ((fflags = fcntl(fd, F_GETFL)) < 0
	||	fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
		LOG(PIL_CRIT, "%s: Setting flags on %s failed [%s]."
		,	__FUNCTION__
		,	port
		,	strerror(errno_save));
		close(fd);
		OurImports->TtyUnlock(port);
		return -1;
	}

	if (tcgetattr(fd, &old_tio) < 0) {
		LOG(PIL_CRIT, "%s: tcgetattr of %s failed [%s].", __FUNCTION__
		,	port
		,	strerror(errno));
		close(fd);
		OurImports->TtyUnlock(port);
		return -1;
	}

	memcpy(&tio, &old_tio, sizeof(struct termios));
	tio.c_cflag = CS8 | CLOCAL | CREAD;
	tio.c_iflag = IGNPAR;
	tio.c_oflag = 0;
	tio.c_lflag = 0;
	tio.c_cc[VMIN] = 1;
	tio.c_cc[VTIME] = 0;

	cfsetispeed(&tio, speed);
	cfsetospeed(&tio, speed);

	tcflush(fd, TCIOFLUSH);
	tcsetattr(fd, TCSANOW, &tio);

	return (fd);
}

/*
 * Close serial port and restore old settings 
 */

void
APC_close_serialport(const char *port, int upsfd)
{

	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	if (upsfd < 0) {
		return;
	}

	tcflush(upsfd, TCIFLUSH);
	tcsetattr(upsfd, TCSANOW, &old_tio);
	close(upsfd);
	if (port != NULL) {
		OurImports->TtyUnlock(port);
	}
}

/*
 * Send a command to the ups 
 */

int
APC_send_cmd(int upsfd, const char *cmd)
{
	int i;

	if (Debug) {
		LOG(PIL_DEBUG, "%s(\"%s\")", __FUNCTION__, cmd);
	}

	tcflush(upsfd, TCIFLUSH);
	for (i = strlen(cmd); i > 0; i--) {
		if (write(upsfd, cmd++, 1) != 1) {
			return (S_ACCESS);
		}

		usleep(SEND_DELAY);
	}
	return (S_OK);
}

/*
 * Get the response from the ups 
 */

int
APC_recv_rsp(int upsfd, char *rsp)
{
	char *p = rsp;
	char inp;
	int num = 0;

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

	*p = '\0';

	STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout);

	alarm(SERIAL_TIMEOUT);

	while (num < MAX_STRING) {

		if (read(upsfd, &inp, 1) == 1) {

	    		/* shutdown sends only a '*' without LF  */
			if ((inp == '*') && (num == 0)) {
				*p++ = inp;
				num++;
				inp = ENDCHAR;
			}

			if (inp == ENDCHAR) {
				alarm(0);
				STONITH_IGNORE_SIG(SIGALRM);

				*p = '\0';
				if (Debug) {
					LOG(PIL_DEBUG, "return(\"%s\")/*%s*/;"
					,	rsp, __FUNCTION__);
				}
				return (S_OK);
			}

			if (inp != CR) {
				*p++ = inp;
				num++;
			}
		}else{
	    		alarm(0);
			STONITH_IGNORE_SIG(SIGALRM);
			*p = '\0';
			LOG(PIL_DEBUG, "%s: %s.", __FUNCTION__,
				f_serialtimeout ? "timeout" :
				"can't access device" );
			return (f_serialtimeout ? S_TIMEOUT : S_ACCESS);
		}
	}
	return (S_ACCESS);
}

/*
 *  Enter smart mode
 */

int
APC_enter_smartmode(int upsfd)
{
    int rc;
    char resp[MAX_STRING];

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

	strcpy(resp, RSP_SMART_MODE);

	if (((rc = APC_send_cmd(upsfd, CMD_SMART_MODE)) == S_OK)
	&&	((rc = APC_recv_rsp(upsfd, resp)) == S_OK)
	&&	(strcmp(RSP_SMART_MODE, resp) == 0)) {
			return (S_OK);
	}

	return (S_ACCESS);
}

/* 
 * Set a value in the hardware using the <cmdchar> '-' (repeat) approach
 */

int
APC_set_ups_var(int upsfd, const char *cmd, char *newval)
{
	char resp[MAX_STRING];
	char orig[MAX_STRING];
	int rc;

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

	if (((rc = APC_enter_smartmode(upsfd)) != S_OK)
	||	((rc = APC_send_cmd(upsfd, cmd)) != S_OK)
	||	((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) {
			return (rc);
	}

	if (Debug) {
		LOG(PIL_DEBUG, "%s: var '%s' original val %s"
		,	__FUNCTION__, cmd, orig);
	}

	if (strcmp(orig, newval) == 0) {
		return (S_OK);		/* already set */
	}

	*resp = '\0';

	while (strcmp(resp, orig) != 0) {
		if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK)
		||	((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) {
	    			return (rc);
		}

		if (((rc = APC_enter_smartmode(upsfd)) != S_OK)
		||	((rc = APC_send_cmd(upsfd, cmd)) != S_OK)
		||	((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) {
	    			return (rc);
		}

		if (strcmp(resp, newval) == 0) {
			if (Debug) {
				LOG(PIL_DEBUG, "%s: var '%s' set to %s"
				,	__FUNCTION__, cmd, newval);
			}

			strcpy(newval, orig);	/* return the old value */
			return (S_OK);		/* got it */
		}
	}

	LOG(PIL_CRIT, "%s(): Could not set variable '%s' to %s!"
	,	__FUNCTION__, cmd, newval);
	LOG(PIL_CRIT, "%s(): This UPS may not support STONITH :-("
	,	 __FUNCTION__);

	return (S_OOPS);
}

/* 
 * Query the smallest delay supported by the hardware using the 
 * <cmdchar> '-' (repeat) approach and looping through all possible values,
 * saving the smallest
 */

int
APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay)
{
	char resp[MAX_DELAY_STRING];
	char orig[MAX_DELAY_STRING];
	int delay, smallest;
	int rc;

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

	if (((rc = APC_enter_smartmode(upsfd)) != S_OK)
	||	((rc = APC_send_cmd(upsfd, cmd)) != S_OK)
	||	((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) {
			return (rc);
	}

	smallest = atoi(orig);
	strcpy(smdelay, orig);

	*resp = '\0';

	/* search for smallest delay; need to loop through all possible
	 * values so that we leave delay the way we found it */
	while (strcmp(resp, orig) != 0) {
		if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK)
		||	((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) {
	    			return (rc);
		}

		if (((rc = APC_enter_smartmode(upsfd)) != S_OK)
		||	((rc = APC_send_cmd(upsfd, cmd)) != S_OK)
		||	((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) {
	    			return (rc);
		}

		if ((delay = atoi(resp)) < smallest) {
			smallest = delay;
			strcpy(smdelay, resp);
		}
	}

	return (S_OK);
}

/*
 * Initialize the ups
 */

int
APC_init(struct pluginDevice *ad)
{
	int upsfd;
	char value[MAX_DELAY_STRING];

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

	/* if ad->upsfd != -1 device has already been configured. */
	/* Just enter smart mode again because otherwise a SmartUPS-1000 */
	/*   has been observed to sometimes not respond. */
	if(ad->upsfd >= 0) {
		if(APC_enter_smartmode(ad->upsfd) != S_OK) {
			return(S_OOPS);
		}
		return S_OK;
	}

	/* open serial port and store the fd in ad->upsfd */
	if ((upsfd = APC_open_serialport(ad->upsdev, B2400)) == -1) {
		return S_OOPS;
	}

	/* switch into smart mode */
	if (APC_enter_smartmode(upsfd) != S_OK) {
		APC_close_serialport(ad->upsdev, upsfd);
		ad->upsfd = -1;
		return S_OOPS;
	}

	/* get the smallest possible delays for this particular hardware */
	if (APC_get_smallest_delay(upsfd, CMD_SHUTDOWN_DELAY
		, ad->shutdown_delay) != S_OK
	|| APC_get_smallest_delay(upsfd, CMD_WAKEUP_DELAY
		, ad->wakeup_delay) != S_OK) {
		LOG(PIL_CRIT, "%s: couldn't retrieve smallest delay from UPS"
		,	__FUNCTION__);
		APC_close_serialport(ad->upsdev, upsfd);
		ad->upsfd = -1;
		return S_OOPS;
	}

	/* get the old settings and store them */
	strcpy(value, ad->shutdown_delay);
	if (APC_set_ups_var(upsfd, CMD_SHUTDOWN_DELAY, value) != S_OK) {
		LOG(PIL_CRIT, "%s: couldn't set shutdown delay to %s"
		,	__FUNCTION__, ad->shutdown_delay);
		APC_close_serialport(ad->upsdev, upsfd);
		ad->upsfd = -1;
		return S_OOPS;
	}
	strcpy(ad->old_shutdown_delay, value);
	strcpy(value, ad->wakeup_delay);
	if (APC_set_ups_var(upsfd, CMD_WAKEUP_DELAY, value) != S_OK) {
		LOG(PIL_CRIT, "%s: couldn't set wakeup delay to %s"
		,	__FUNCTION__, ad->wakeup_delay);
		APC_close_serialport(ad->upsdev, upsfd);
		ad->upsfd = -1;
		return S_OOPS;
	}
	strcpy(ad->old_wakeup_delay, value);

	ad->upsfd = upsfd;
	return S_OK;
}

/*
 * Restore original settings and close the port
 */

void
APC_deinit(struct pluginDevice *ad)
{
	APC_enter_smartmode( ad->upsfd );

	APC_set_ups_var(ad->upsfd, CMD_SHUTDOWN_DELAY, ad->old_shutdown_delay);
	APC_set_ups_var(ad->upsfd, CMD_WAKEUP_DELAY, ad->old_wakeup_delay);

	/* close serial port */
	if (ad->upsfd >= 0) {
		APC_close_serialport(ad->upsdev, ad->upsfd);
		ad->upsfd = -1;
	}
}
static const char**
apcsmart_get_confignames(StonithPlugin* sp)
{
	static const char * names[] =  {ST_TTYDEV, ST_HOSTLIST, NULL};
	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	return names;
}

/*
 * Stash away the config info we've been given...
 */

static int
apcsmart_set_config(StonithPlugin * s, StonithNVpair* list)
{
	struct pluginDevice *	ad = (struct pluginDevice*)s;
	StonithNamesToGet	namestocopy [] =
	{	{ST_TTYDEV,	NULL}
	,	{ST_HOSTLIST,	NULL}
	,	{NULL,		NULL}
	};
	int			rc;

	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	ERRIFWRONGDEV(s, S_OOPS);

	if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) {
		return rc;
	}
	ad->upsdev = namestocopy[0].s_value;
	ad->hostlist =	OurImports->StringToHostList(namestocopy[1].s_value);
	FREE(namestocopy[1].s_value);

	if (ad->hostlist == NULL) {
		LOG(PIL_CRIT,"StringToHostList() failed");
		return S_OOPS;
	}
	for (ad->hostcount = 0; ad->hostlist[ad->hostcount]
	;	ad->hostcount++) {
		g_strdown(ad->hostlist[ad->hostcount]);
	}
	if (access(ad->upsdev, R_OK|W_OK|F_OK) < 0) {
		LOG(PIL_CRIT,"Cannot access tty [%s]", ad->upsdev);
		return S_BADCONFIG;
	}

	return ad->hostcount ? S_OK : S_BADCONFIG;
}

/*
 * return the status for this device 
 */

static int
apcsmart_status(StonithPlugin * s)
{
	struct pluginDevice *ad = (struct pluginDevice *) s;
	char resp[MAX_STRING];
	int rc;

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

	ERRIFNOTCONFIGED(s,S_OOPS);


	/* get status */
	if (((rc = APC_init( ad )) == S_OK)
	&&	((rc = APC_send_cmd(ad->upsfd, CMD_GET_STATUS)) == S_OK)
	&&	((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)) {
		return (S_OK);		/* everything ok. */
	}
	if (Debug) {
		LOG(PIL_DEBUG, "%s: failed, rc=%d.", __FUNCTION__, rc);
	}
	return (rc);
}


/*
 * return the list of hosts configured for this device 
 */

static char **
apcsmart_hostlist(StonithPlugin * s)
{
	struct pluginDevice *ad = (struct pluginDevice *) s;

	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	ERRIFNOTCONFIGED(s,NULL);

	return OurImports->CopyHostList((const char **)ad->hostlist);
}

static gboolean
apcsmart_RegisterBitsSet(struct pluginDevice * ad, int nreg, unsigned bits
,	gboolean* waserr)
{
	const char*	reqregs[4] = {"?", "~", "'", "8"};
	unsigned	regval;
	char		resp[MAX_STRING];

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


	if (APC_enter_smartmode(ad->upsfd) != S_OK
	||	APC_send_cmd(ad->upsfd, reqregs[nreg]) != S_OK
	||	APC_recv_rsp(ad->upsfd, resp) != S_OK
	||	(sscanf(resp, "%02x", &regval) != 1)) {
		if (waserr){
			*waserr = TRUE;
		}
		return FALSE;
	}
	if (waserr){
		*waserr = FALSE;
	}
	return ((regval & bits) == bits);
}

#define	apcsmart_IsPoweredOff(ad, err) apcsmart_RegisterBitsSet(ad,1,0x40,err)
#define	apcsmart_ResetHappening(ad,err) apcsmart_RegisterBitsSet(ad,3,0x08,err)


static int
apcsmart_ReqOnOff(struct pluginDevice * ad, int request)
{
	const char *	cmdstr;
	int		rc;

	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	cmdstr = (request == ST_POWEROFF ? CMD_OFF : CMD_ON);
	/* enter smartmode, send on/off command */
	if ((rc =APC_enter_smartmode(ad->upsfd)) != S_OK
	||	(rc = APC_send_cmd(ad->upsfd, cmdstr)) != S_OK) {
		return rc;
	}
	sleep(2);
	if ((rc = APC_send_cmd(ad->upsfd, cmdstr)) == S_OK) {
		gboolean ison;
		gboolean waserr;
		sleep(1);
		ison = !apcsmart_IsPoweredOff(ad, &waserr);
		if (waserr) {
			return S_RESETFAIL;
		}
		if (request == ST_POWEROFF) {
			return ison ?  S_RESETFAIL : S_OK;
		}else{
			return ison ?  S_OK : S_RESETFAIL;
		}
	}
	return rc;
}

/*
 * reset the host 
 */

static int
apcsmart_ReqGenericReset(struct pluginDevice *ad)
{
	char		resp[MAX_STRING];
	int		rc = S_RESETFAIL;

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

	/* send reset command(s) */
	if (((rc = APC_init(ad)) == S_OK)
	&& ((rc = APC_send_cmd(ad->upsfd, CMD_RESET)) == S_OK)) {
		if (((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)
		&& (strcmp(resp, RSP_RESET) == 0
		|| strcmp(resp, RSP_RESET2) == 0)) {
			/* first kind of reset command was accepted */
		} else if (((rc = APC_send_cmd(ad->upsfd, CMD_RESET2)) == S_OK)
		&& ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)
		&& (strcmp(resp, RSP_RESET) == 0
		|| strcmp(resp, RSP_RESET2) == 0)) {
			/* second kind of command was accepted */
		} else {
			if (Debug) {
				LOG(PIL_DEBUG, "APC: neither reset command "
					"was accepted");
			}
			rc = S_RESETFAIL;
		}
	}
	if (rc == S_OK) {
		/* we wait grace period + up to 10 seconds after shutdown */
		int	maxdelay = atoi(ad->shutdown_delay)+10;
		int	j;

		for (j=0; j < maxdelay; ++j) {
			gboolean	err;
			if (apcsmart_ResetHappening(ad, &err)) {
				return err ? S_RESETFAIL : S_OK;
			}
			sleep(1);
		}
		LOG(PIL_CRIT, "%s: timed out waiting for reset to end."
		,	__FUNCTION__);
		return S_RESETFAIL;

	}else{
		if (strcmp(resp, RSP_NA) == 0){
			gboolean iserr;
			/* This means it's currently powered off */
			/* or busy on a previous command... */
			if (apcsmart_IsPoweredOff(ad, &iserr)) {
				if (iserr) {
					LOG(PIL_DEBUG, "%s: power off "
					"detection failed.", __FUNCTION__);
					return S_RESETFAIL;
				}
				if (Debug) {
					LOG(PIL_DEBUG, "APC: was powered off, "
						"powering back on.");
				}
				return apcsmart_ReqOnOff(ad, ST_POWERON);
			}
		}
	}
	strcpy(resp, "?");

	/* reset failed */

	return S_RESETFAIL;
}

static int
apcsmart_reset_req(StonithPlugin * s, int request, const char *host)
{
	char **			hl;
	int			b_found=FALSE;
	struct pluginDevice *	ad = (struct pluginDevice *) s;
	int			rc;

	ERRIFNOTCONFIGED(s, S_OOPS);

	if (host == NULL) {
		LOG(PIL_CRIT, "%s: invalid hostname argument.", __FUNCTION__);
		return (S_INVAL);
	}
    
	/* look through the hostlist */
	hl = ad->hostlist;

	while (*hl && !b_found ) {
		if( strcasecmp( *hl, host ) == 0 ) {
			b_found = TRUE;
			break;
		}else{
        		++hl;
		}
	}

    	/* host not found in hostlist */
	if( !b_found ) {
		LOG(PIL_CRIT, "%s: host '%s' not in hostlist."
		,	__FUNCTION__, host);
		return S_BADHOST;
	}
	if ((rc = APC_init(ad)) != S_OK) {
		return rc;
	}

	if (request == ST_POWERON || request == ST_POWEROFF) {
		return apcsmart_ReqOnOff(ad, request);
	}
	return apcsmart_ReqGenericReset(ad);
}


/*
 * get info about the stonith device 
 */

static const char *
apcsmart_get_info(StonithPlugin * s, int reqtype)
{
	struct pluginDevice *ad = (struct pluginDevice *) s;
	const char *ret;

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

	ERRIFWRONGDEV(s,NULL);
   

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

		case ST_DEVICENAME:
		ret = ad->upsdev;
		break;

		case ST_DEVICEDESCR:
		ret = "APC Smart UPS\n"
			" (via serial port - NOT USB!). \n"
			" Works with higher-end APC UPSes, like\n"
			" Back-UPS Pro, Smart-UPS, Matrix-UPS, etc.\n"
			" (Smart-UPS may have to be >= Smart-UPS 700?).\n"
		" See http://www.networkupstools.org/protocols/apcsmart.html\n"
			" for protocol compatibility details.";
			break;

		case ST_DEVICEURL:
			ret = "http://www.apc.com/";
			break;

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

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

/*
 * APC Stonith destructor... 
 */

static void
apcsmart_destroy(StonithPlugin * s)
{
    struct pluginDevice *ad = (struct pluginDevice *) s;

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

	if (ad->upsfd >= 0 && ad->upsdev) {
		APC_deinit( ad );
	}

	ad->pluginid = NOTpluginID;

	if (ad->hostlist) {
		stonith_free_hostlist(ad->hostlist);
		ad->hostlist = NULL;
	}
	if (ad->upsdev != NULL) {
		FREE(ad->upsdev);
		ad->upsdev = NULL;
	}

	ad->hostcount = -1;
	ad->upsfd = -1;

	FREE(ad);

}

/*
 * Create a new APC Stonith device.  Too bad this function can't be
 * static 
 */

static StonithPlugin *
apcsmart_new(const char *subplugin)
{
    struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice);

	if (Debug) {
		LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
	}
	if (ad == NULL) {
		LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__);
		return (NULL);
	}

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

	ad->pluginid = pluginid;
	ad->hostlist = NULL;
	ad->hostcount = -1;
	ad->upsfd = -1;
	ad->idinfo = DEVICE;
	ad->sp.s_ops = &apcsmartOps;

	if (Debug) {
		LOG(PIL_DEBUG, "%s: returning successfully.", __FUNCTION__);
	}
	return &(ad->sp);
}
