/*
*
*  Copyright 2001 Mission Critical Linux, Inc.
*
*  All Rights Reserved.
*/
/*
 *	Stonith module for APC Master Switch (AP9211)
 *
 *  Copyright (c) 2001 Mission Critical Linux, Inc.
 *  author: mike ledoux <mwl@mclinux.com>
 *  author: Todd Wheeling <wheeling@mclinux.com>
 *  mangled by Sun Jiang Dong, <sunjd@cn.ibm.com>, IBM, 2005
 *
 *  Based strongly on original code from baytech.c by Alan Robertson.
 *
 * 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
 *
 */

/*                          Observations/Notes
 * 
 * 1. The APC MasterSwitch, unlike the BayTech network power switch,
 *    accepts only one (telnet) connection/session at a time. When one
 *    session is active, any subsequent attempt to connect to the MasterSwitch 
 *    will result in a connection refused/closed failure. In a cluster 
 *    environment or other environment utilizing polling/monitoring of the 
 *    MasterSwitch (from multiple nodes), this can clearly cause problems. 
 *    Obviously the more nodes and the shorter the polling interval, the more 
 *    frequently such errors/collisions may occur.
 *
 * 2. We observed that on busy networks where there may be high occurances
 *    of broadcasts, the MasterSwitch became unresponsive.  In some 
 *    configurations this necessitated placing the power switch onto a 
 *    private subnet.
 */

/*
 * Version string that is filled in by CVS
 */
static const char *version __attribute__ ((unused)) = "$Revision: 1.27 $"; 
#include <lha_internal.h>

#define	DEVICE	"APC MasterSwitch"

#define DOESNT_USE_STONITHKILLCOMM	1

#include "stonith_plugin_common.h"

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

#include "stonith_signal.h"

static StonithPlugin *	apcmaster_new(const char *);
static void		apcmaster_destroy(StonithPlugin *);
static const char **	apcmaster_get_confignames(StonithPlugin *);
static int		apcmaster_set_config(StonithPlugin *, StonithNVpair *);
static const char *	apcmaster_getinfo(StonithPlugin * s, int InfoType);
static int		apcmaster_status(StonithPlugin * );
static int		apcmaster_reset_req(StonithPlugin * s, int request, const char * host);
static char **		apcmaster_hostlist(StonithPlugin  *);

static struct stonith_ops apcmasterOps ={
	apcmaster_new,		/* Create new STONITH object	*/
	apcmaster_destroy,		/* Destroy STONITH object	*/
	apcmaster_getinfo,		/* Return STONITH info string	*/
	apcmaster_get_confignames,	/* Get configuration parameters */
	apcmaster_set_config,		/* Set configuration */
	apcmaster_status,		/* Return STONITH device status	*/
	apcmaster_reset_req,		/* Request a reset */
	apcmaster_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;

#include "stonith_expect_helpers.h"

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
	,	&apcmasterOps
	,	NULL		/*close */
	,	&OurInterface
	,	(void*)&OurImports
	,	&interfprivate); 
}

/*
 *	I have an AP9211.  This code has been tested with this switch.
 */

struct pluginDevice {
	StonithPlugin	sp;
	const char *	pluginid;
	const char *	idinfo;
	pid_t		pid;
	int		rdfd;
	int		wrfd;
	char *		device;
        char *		user;
	char *		passwd;
};

static const char * pluginid = "APCMS-Stonith";
static const char * NOTpluginID = "APCMS device has been destroyed";

/*
 *	Different expect strings that we get from the APC MasterSwitch
 */

#define APCMSSTR	"American Power Conversion"

static struct Etoken EscapeChar[] =	{ {"Escape character is '^]'.", 0, 0}
					,	{NULL,0,0}};
static struct Etoken login[] = 		{ {"User Name :", 0, 0}, {NULL,0,0}};
static struct Etoken password[] =	{ {"Password  :", 0, 0} ,{NULL,0,0}};
static struct Etoken Prompt[] =	{ {"> ", 0, 0} ,{NULL,0,0}};
static struct Etoken LoginOK[] =	{ {APCMSSTR, 0, 0}
                    , {"User Name :", 1, 0} ,{NULL,0,0}};
static struct Etoken Separator[] =	{ {"-----", 0, 0} ,{NULL,0,0}};

/* We may get a notice about rebooting, or a request for confirmation */
static struct Etoken Processing[] =	{ {"Press <ENTER> to continue", 0, 0}
				,	{"Enter 'YES' to continue", 1, 0}
				,	{NULL,0,0}};

#include "stonith_config_xml.h"

static const char *apcmasterXML = 
  XML_PARAMETERS_BEGIN
    XML_IPADDR_PARM
    XML_LOGIN_PARM
    XML_PASSWD_PARM
  XML_PARAMETERS_END;

static int	MS_connect_device(struct pluginDevice * ms);
static int	MSLogin(struct pluginDevice * ms);
static int	MSRobustLogin(struct pluginDevice * ms);
static int	MSNametoOutlet(struct pluginDevice*, const char * name);
static int	MSReset(struct pluginDevice*, int outletNum, const char * host);
static int	MSLogout(struct pluginDevice * ms);

#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int	apcmaster_onoff(struct pluginDevice*, int outletnum, const char * unitid
,		int request);
#endif

/* Login to the APC Master Switch */

static int
MSLogin(struct pluginDevice * ms)
{
        EXPECT(ms->rdfd, EscapeChar, 10);

  	/* 
	 * We should be looking at something like this:
         *	User Name :
	 */
	EXPECT(ms->rdfd, login, 10);
	SEND(ms->wrfd, ms->user);       
	SEND(ms->wrfd, "\r");

	/* Expect "Password  :" */
	EXPECT(ms->rdfd, password, 10);
	SEND(ms->wrfd, ms->passwd);
	SEND(ms->wrfd, "\r");
 
	switch (StonithLookFor(ms->rdfd, LoginOK, 30)) {

		case 0:	/* Good! */
			LOG(PIL_INFO, "Successful login to %s.", ms->idinfo); 
			break;

		case 1:	/* Uh-oh - bad password */
			LOG(PIL_CRIT, "Invalid password for %s.", ms->idinfo);
			return(S_ACCESS);

		default:
			return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
	} 

	return(S_OK);
}

/* Attempt to login up to 20 times... */

static int
MSRobustLogin(struct pluginDevice * ms)
{
	int rc = S_OOPS;
	int j = 0;

	for ( ; ; ) {
	  if (MS_connect_device(ms) == S_OK) {	
		rc = MSLogin(ms);
		if( rc == S_OK ) {
			break;
	    	}
	  }
	  if ((++j) == 20) {
		break;
	  } else {
		sleep(1);
	  }
	}

	return rc;
}

/* Log out of the APC Master Switch */

static 
int MSLogout(struct pluginDevice* ms)
{
	int	rc;

	/* Make sure we're in the right menu... */
 	/*SEND(ms->wrfd, "\033\033\033\033\033\033\033"); */
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	
	/* Expect "> " */
	rc = StonithLookFor(ms->rdfd, Prompt, 5);

	/* "4" is logout */
	SEND(ms->wrfd, "4\r");

	close(ms->wrfd);
	close(ms->rdfd);
	ms->wrfd = ms->rdfd = -1;

	return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS));
}
/* Reset (power-cycle) the given outlets */
static int
MSReset(struct pluginDevice* ms, int outletNum, const char *host)
{
  	char		unum[32];

	/* Make sure we're in the top level menu */
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	
	/* Expect ">" */
	EXPECT(ms->rdfd, Prompt, 5);

	/* Request menu 1 (Device Control) */
	SEND(ms->wrfd, "1\r");

	/* Select requested outlet */
	EXPECT(ms->rdfd, Prompt, 5);
	snprintf(unum, sizeof(unum), "%i\r", outletNum);
  	SEND(ms->wrfd, unum);

	/* Select menu 1 (Control Outlet) */
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "1\r");

	/* Select menu 3 (Immediate Reboot) */
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "3\r");

	/* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */
	retry:
	switch (StonithLookFor(ms->rdfd, Processing, 5)) {
		case 0: /* Got "Press <ENTER>" Do so */
			SEND(ms->wrfd, "\r");
			break;

		case 1: /* Got that annoying command confirmation :-( */
			SEND(ms->wrfd, "YES\r");
			goto retry;

		default: 
			return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
	}

	
	LOG(PIL_INFO, "Host being rebooted: %s", host); 

	/* Expect ">" */
	if (StonithLookFor(ms->rdfd, Prompt, 10) < 0) {
		return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
	}

	/* All Right!  Power is back on.  Life is Good! */

	LOG(PIL_INFO, "Power restored to host: %s", host);

	/* Return to top level menu */
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");

	return(S_OK);
}

#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int
apcmaster_onoff(struct pluginDevice* ms, int outletNum, const char * unitid, int req)
{
	char		unum[32];

	const char *	onoff = (req == ST_POWERON ? "1\r" : "2\r");
	int	rc;

	if ((rc = MSRobustLogin(ms) != S_OK)) {
		LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
		return(rc);
	}
	
	/* Make sure we're in the top level menu */
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
        SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");

	/* Expect ">" */
	EXPECT(ms->rdfd, Prompt, 5);

	/* Request menu 1 (Device Control) */
	SEND(ms->wrfd, "1\r");

	/* Select requested outlet */
  	snprintf(unum, sizeof(unum), "%d\r", outletNum); 
  	SEND(ms->wrfd, unum); 

	/* Select menu 1 (Control Outlet) */
	SEND(ms->wrfd, "1\r");

	/* Send ON/OFF command for given outlet */
	SEND(ms->wrfd, onoff);

	/* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */
	retry:
	switch (StonithLookFor(ms->rdfd, Processing, 5)) {
		case 0: /* Got "Press <ENTER>" Do so */
			SEND(ms->wrfd, "\r");
			break;

		case 1: /* Got that annoying command confirmation :-( */
			SEND(ms->wrfd, "YES\r");
			goto retry;

		default: 
			return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
	}
	
	EXPECT(ms->rdfd, Prompt, 10);

	/* All Right!  Command done. Life is Good! */
	LOG(PIL_INFO, "Power to MS outlet(s) %d turned %s", outletNum, onoff);
	/* Pop back to main menu */
	SEND(ms->wrfd, "\033\033\033\033\033\033\033\r");
	return(S_OK);
}
#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */

/*
 *	Map the given host name into an (AC) Outlet number on the power strip
 */

static int
MSNametoOutlet(struct pluginDevice* ms, const char * name)
{
	char	NameMapping[128];
	int	sockno;
	char	sockname[32];
	int times = 0;
	int ret = -1;

	/* Verify that we're in the top-level menu */
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");	
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");

	/* Expect ">" */
	EXPECT(ms->rdfd, Prompt, 5);
	
	/* Request menu 1 (Device Control) */
	SEND(ms->wrfd, "1\r");

	/* Expect: "-----" so we can skip over it... */
	EXPECT(ms->rdfd, Separator, 5);
	EXPECT(ms->rdfd, CRNL, 5);
	EXPECT(ms->rdfd, CRNL, 5);

	/* Looks Good!  Parse the status output */

	do {
		times++;
		NameMapping[0] = EOS;
		SNARF(ms->rdfd, NameMapping, 5);
		if (sscanf(NameMapping
		,	"%d- %23c",&sockno, sockname) == 2) {

			char *	last = sockname+23;
			*last = EOS;
			--last;

			/* Strip off trailing blanks */
			for(; last > sockname; --last) {
				if (*last == ' ') {
					*last = EOS;
				}else{
					break;
				}
			}
			if (strcasecmp(name, sockname) == 0) {
				ret = sockno;
			}
		}
	} while (strlen(NameMapping) > 2 && times < 8);

	/* Pop back out to the top level menu */
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");	
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "\033");
	return(ret);
}

static int
apcmaster_status(StonithPlugin  *s)
{
	struct pluginDevice*	ms;
	int	rc;

	ERRIFNOTCONFIGED(s,S_OOPS);

	ms = (struct pluginDevice*) s;

	if ((rc = MSRobustLogin(ms) != S_OK)) {
		LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
		return(rc);
	}

	/* Expect ">" */
	SEND(ms->wrfd, "\033\r");
	EXPECT(ms->rdfd, Prompt, 5);

	return(MSLogout(ms));
}

/*
 *	Return the list of hosts (outlet names) for the devices on this MS unit
 */

static char **
apcmaster_hostlist(StonithPlugin  *s)
{
	char		NameMapping[128];
	char*		NameList[64];
	unsigned int	numnames = 0;
	char **		ret = NULL;
	struct pluginDevice*	ms;
	unsigned int	i;

	ERRIFNOTCONFIGED(s,NULL);

	ms = (struct pluginDevice*) s;
		
	if (MSRobustLogin(ms) != S_OK) {
		LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
		return(NULL);
	}

	/* Expect ">" */
	NULLEXPECT(ms->rdfd, Prompt, 10);

	/* Request menu 1 (Device Control) */
	SEND(ms->wrfd, "1\r");

	/* Expect: "-----" so we can skip over it... */
	NULLEXPECT(ms->rdfd, Separator, 5);
	NULLEXPECT(ms->rdfd, CRNL, 5);
	NULLEXPECT(ms->rdfd, CRNL, 5);

	/* Looks Good!  Parse the status output */
	do {
		int	sockno;
		char	sockname[64];
		NameMapping[0] = EOS;
		NULLSNARF(ms->rdfd, NameMapping, 5);
		if (sscanf(NameMapping
		,	"%d- %23c",&sockno, sockname) == 2) {

			char *	last = sockname+23;
			char *	nm;
			*last = EOS;
			--last;

			/* Strip off trailing blanks */
			for(; last > sockname; --last) {
				if (*last == ' ') {
					*last = EOS;
				}else{
					break;
				}
			}
			if (numnames >= DIMOF(NameList)-1) {
				break;
			}
			if ((nm = (char*)STRDUP(sockname)) == NULL) {
				goto out_of_memory;
			}
			g_strdown(nm);
			NameList[numnames] = nm;
			++numnames;
			NameList[numnames] = NULL;
		}
	} while (strlen(NameMapping) > 2);

	/* Pop back out to the top level menu */
    	SEND(ms->wrfd, "\033");
        NULLEXPECT(ms->rdfd, Prompt, 10);
    	SEND(ms->wrfd, "\033");
        NULLEXPECT(ms->rdfd, Prompt, 10);
    	SEND(ms->wrfd, "\033");
        NULLEXPECT(ms->rdfd, Prompt, 10);
    	SEND(ms->wrfd, "\033");
	NULLEXPECT(ms->rdfd, Prompt, 10);
      

	if (numnames >= 1) {
		ret = (char **)MALLOC((numnames+1)*sizeof(char*));
		if (ret == NULL) {
			goto out_of_memory;
		}else{
			memcpy(ret, NameList, (numnames+1)*sizeof(char*));
		}
	}
	(void)MSLogout(ms);
	return(ret);

out_of_memory:
	LOG(PIL_CRIT, "out of memory");
	for (i=0; i<numnames; i++) {
		FREE(NameList[i]);
	}
	return(NULL);
}

/*
 *	Connect to the given MS device.  We should add serial support here
 *	eventually...
 */
static int
MS_connect_device(struct pluginDevice * ms)
{
	int fd = OurImports->OpenStreamSocket(ms->device
	,	TELNET_PORT, TELNET_SERVICE);

	if (fd < 0) {
		return(S_OOPS);
	}
	ms->rdfd = ms->wrfd = fd;
	return(S_OK);
}

/*
 *	Reset the given host on this StonithPlugin device.  
 */
static int
apcmaster_reset_req(StonithPlugin * s, int request, const char * host)
{
	int	rc = 0;
	int	lorc = 0;
	struct pluginDevice*	ms;

	ERRIFNOTCONFIGED(s,S_OOPS);

	ms = (struct pluginDevice*) s;

	if ((rc = MSRobustLogin(ms)) != S_OK) {
		LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
		return(rc);
	}else{
		int noutlet; 
		noutlet = MSNametoOutlet(ms, host);
		if (noutlet < 1) {
			LOG(PIL_WARN, "%s doesn't control host [%s]"
			,	ms->device, host);
			return(S_BADHOST);
		}
		switch(request) {

#if defined(ST_POWERON) && defined(ST_POWEROFF)
		case ST_POWERON:
		        rc = apcmaster_onoff(ms, noutlet, host, request);
			break;
		case ST_POWEROFF:
			rc = apcmaster_onoff(ms, noutlet, host, request);
			break;
#endif
		case ST_GENERIC_RESET:
			rc = MSReset(ms, noutlet, host);
			break;
		default:
			rc = S_INVAL;
			break;
		}
	}

	lorc = MSLogout(ms);
	return(rc != S_OK ? rc : lorc);
}

/*
 *	Get the configuration parameters names
 */
static const char **
apcmaster_get_confignames(StonithPlugin * s)
{
	static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL};
	return ret;
}

/*
 *	Set the configuration parameters
 */
static int
apcmaster_set_config(StonithPlugin * s, StonithNVpair * list)
{
	struct pluginDevice* sd = (struct pluginDevice *)s;
	int		rc;
	StonithNamesToGet	namestocopy [] =
	{	{ST_IPADDR,	NULL}
	,	{ST_LOGIN,	NULL}
	,	{ST_PASSWD,	NULL}
	,	{NULL,		NULL}
	};

	ERRIFWRONGDEV(s, S_OOPS);
	if (sd->sp.isconfigured) {
		return S_OOPS;
	}

	if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) {
		return rc;
	}
	sd->device = namestocopy[0].s_value;
	sd->user = namestocopy[1].s_value;
	sd->passwd = namestocopy[2].s_value;

	return(S_OK);
}

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

	ERRIFWRONGDEV(s,NULL);

	/*
	 *	We look in the ST_TEXTDOMAIN catalog for our messages
	 */
	ms = (struct pluginDevice *)s;

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

		case ST_DEVICENAME:		/* Which particular device? */
			ret = ms->device;
			break;

		case ST_DEVICEDESCR:
			ret = "APC MasterSwitch (via telnet)\n"
 			"NOTE: The APC MasterSwitch accepts only one (telnet)\n"
			"connection/session a time. When one session is active,\n"
			"subsequent attempts to connect to the MasterSwitch"
			" will fail.";
			break;

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

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

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

/*
 *	APC MasterSwitch StonithPlugin destructor...
 */
static void
apcmaster_destroy(StonithPlugin *s)
{
	struct pluginDevice* ms;

	VOIDERRIFWRONGDEV(s);

	ms = (struct pluginDevice *)s;

	ms->pluginid = NOTpluginID;
	if (ms->rdfd >= 0) {
		close(ms->rdfd);
		ms->rdfd = -1;
	}
	if (ms->wrfd >= 0) {
		close(ms->wrfd);
		ms->wrfd = -1;
	}
	if (ms->device != NULL) {
		FREE(ms->device);
		ms->device = NULL;
	}
	if (ms->user != NULL) {
		FREE(ms->user);
		ms->user = NULL;
	}
	if (ms->passwd != NULL) {
		FREE(ms->passwd);
		ms->passwd = NULL;
	}
	FREE(ms);
}

/* Create a new APC Master Switch StonithPlugin device. */

static StonithPlugin *
apcmaster_new(const char *subplugin)
{
	struct pluginDevice*	ms = ST_MALLOCT(struct pluginDevice);

	if (ms == NULL) {
		LOG(PIL_CRIT, "out of memory");
		return(NULL);
	}
	memset(ms, 0, sizeof(*ms));
	ms->pluginid = pluginid;
	ms->pid = -1;
	ms->rdfd = -1;
	ms->wrfd = -1;
	ms->user = NULL;
	ms->device = NULL;
	ms->passwd = NULL;
	ms->idinfo = DEVICE;
	ms->sp.s_ops = &apcmasterOps;

	return(&(ms->sp));
}
