mirror of https://github.com/sipwise/heartbeat.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1029 lines
23 KiB
1029 lines
23 KiB
/*
|
|
* 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", ®val) != 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);
|
|
}
|