/* $Id: cyclades.c,v 1.1.2.8 2005/05/20 19:43:33 blaschke Exp $ */
/*
* Stonith module for Cyclades AlterPath PM
* Bases off the SSH plugin
*
* Copyright (c) 2004 Cyclades corp.
*
* Author: Jon Taylor <jon.taylor@cyclades.com>
*
* Rewritten from scratch using baytech.c structure and code
* and currently maintained by
* Marcelo Tosatti <marcelo.tosatti@cyclades.com>
*
* 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
*
*/
#include <portability.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <libintl.h>
#include <sys/wait.h>
#include <glib.h>
#include <stonith/stonith.h>
#include "stonith_signal.h"
#define PIL_PLUGINTYPE STONITH_TYPE
#define PIL_PLUGINTYPE_S STONITH_TYPE_S
#define PIL_PLUGIN cyclades
#define PIL_PLUGIN_S "cyclades"
#define PIL_PLUGINLICENSE LICENSE_LGPL
#define PIL_PLUGINLICENSEURL URL_LGPL
#include <pils/plugin.h>
/*
* cycladesclose is called as part of unloading the cyclades STONITH plugin.
* If there was any global data allocated, or file descriptors opened, etc.
* which is associated with the plugin, and not a single interface
* in particular, here's our chance to clean it up.
*/
static void
cycladesclosepi(PILPlugin*pi)
{
}
/*
* cycladescloseintf called as part of shutting down the cyclades STONITH
* interface. If there was any global data allocated, or file descriptors
* opened, etc. which is associated with the ssh implementation,
* here's our chance to clean it up.
*/
static PIL_rc
cycladescloseintf(PILInterface* pi, void* pd)
{
return PIL_OK;
}
static void * cyclades_new(void);
static void cyclades_destroy(Stonith *);
static int cyclades_set_config_file(Stonith *, const char * cfgname);
static int cyclades_set_config_info(Stonith *, const char * info);
static const char * cyclades_getinfo(Stonith * s, int InfoType);
static int cyclades_status(Stonith * );
static int cyclades_reset_req(Stonith * s, int request, const char * host);
static char ** cyclades_hostlist(Stonith *);
static void cyclades_free_hostlist(char **);
static struct stonith_ops cycladesOps ={
cyclades_new, /* Create new STONITH object */
cyclades_destroy, /* Destroy STONITH object */
cyclades_set_config_file, /* set configuration from file */
cyclades_set_config_info, /* Get configuration from file */
cyclades_getinfo, /* Return STONITH info string */
cyclades_status, /* Return STONITH device status */
cyclades_reset_req, /* Request a reset */
cyclades_hostlist, /* Return list of supported hosts */
cyclades_free_hostlist /* free above list */
};
PIL_PLUGIN_BOILERPLATE("1.0", Debug, cycladesclosepi);
static const PILPluginImports* PluginImports;
static PILPlugin* OurPlugin;
static PILInterface* OurInterface;
static StonithImports* OurImports;
static void* interfprivate;
#define LOG PluginImports->log
#define MALLOC PluginImports->alloc
#define STRDUP PluginImports->mstrdup
#define FREE PluginImports->mfree
#define EXPECT_TOK OurImports->ExpectToken
#define STARTPROC OurImports->StartProcess
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
, &cycladesOps
, cycladescloseintf /*close */
, &OurInterface
, (void*)&OurImports
, &interfprivate);
}
#define DEVICE "Cyclades PM"
#define WHITESPACE " \t\n\r\f"
/*
* Cyclades STONITH device
*
*/
struct cycladesDevice {
const char * cycladesid;
char * device;
char * user;
int config;
int serial_port;
/* pid of ssh client process and its in/out file descriptors */
pid_t pid;
int rdfd, wrfd;
};
static struct Etoken StatusOutput[] = {
{ "Outlet\t\tName\t\tStatus\t\tUsers\t\tInterval (s)", 0, 0},
{ NULL, 0, 0}
};
static struct Etoken CRNL[] = { {"\n",0,0},{"\r\n",0,0},{NULL,0,0}};
/* Commands of PM devices */
static char status_all[] = "status all";
static char cycle[] = "cycle";
static int CYC_robust_cmd(struct cycladesDevice *, char *);
static void CYCkillcomm(struct cycladesDevice *);
static int CYCScanLine(struct cycladesDevice*, int, char *, int);
static const char * cycladesid = "CycladesDevice-Stonith";
static const char * NOTcycladesID = "Cyclades device has been destroyed";
#define MAX_OUTLETS 128
#define ISCYCLADESDEV(i) (((i)!= NULL && (i)->pinfo != NULL) \
&& ((struct cycladesDevice *)(i->pinfo))->cycladesid == cycladesid)
#ifndef MALLOCT
# define MALLOCT(t) ((t *)(MALLOC(sizeof(t))))
#endif
#define N_(text) (text)
#define _(text) dgettext(ST_TEXTDOMAIN, text)
#define EXPECT(p,t,b,l) { \
if (LookFor(sd, p, t, b, l) < 0) \
return(errno == ETIMEDOUT \
? S_TIMEOUT : S_OOPS); \
}
#define NULLEXPECT(p,t,b,l) { \
if (LookFor(sd, p, t, b, l) < 0) \
return(NULL); \
}
#define ZEROEXPECT(p,t,b,l) { \
if (LookFor(sd, p, t, b, l) < 0) \
return(0); \
}
#define RESETEXPECT(p,t,b,l) { \
if (LookFor(sd, p, t, b, l) < 0) { \
FREE(outletstr); \
return(errno == ETIMEDOUT \
? S_RESETFAIL : S_OOPS); \
} \
}
static int
CYCScanLine(struct cycladesDevice* sd, int timeout, char * buf, int max)
{
if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max) < 0) {
CYCkillcomm(sd);
sd->pid = -1;
return(S_OOPS);
}
return(S_OK);
}
#define MAXSAVE 512
static int
LookFor(struct cycladesDevice* sd, struct Etoken * tlist, int timeout, char *buf,
int max)
{
int rc;
if ((rc = EXPECT_TOK(sd->rdfd, tlist, timeout, buf, max)) < 0){
if (tlist == StatusOutput) {
/* if status all returns nothing, unknown device */
syslog(LOG_ERR, _("Unknown " DEVICE " device %s")
, sd->device);
}else{
syslog(LOG_ERR, _("Did not find string: '%s' from "
DEVICE "."), tlist[0].string);
syslog(LOG_ERR, _("Got '%s' from " DEVICE " instead.")
, buf);
}
CYCkillcomm(sd);
return(-1);
}
#if 0
syslog(LOG_INFO
, "Cyclades: Expecting [%s] Received [%s]"
, tlist[0].string
, buf);
#endif
return(rc);
}
static int
cyclades_status(Stonith *s)
{
struct cycladesDevice *sd = s->pinfo;
char *cmd = status_all;
char savebuf[MAXSAVE];
savebuf[MAXSAVE-1] = EOS;
savebuf[0] = EOS;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "invalid argument to cyclades_status");
return(S_OOPS);
}
if (CYC_robust_cmd(sd, cmd) != S_OK) {
syslog(LOG_ERR, "can't run status all command");
return(S_OOPS);
}
EXPECT(StatusOutput, 50, savebuf, sizeof(savebuf));
return (S_OK);
}
static void CYCkillcomm(struct cycladesDevice *sd)
{
if (sd->pid > 0) {
STONITH_KILL(sd->pid, SIGKILL);
(void)waitpid(sd->pid, NULL, 0);
sd->pid = -1;
}
}
static int CYC_run_command(struct cycladesDevice *sd, char *cmd)
{
char SshCommand[MAX_OUTLETS*4];
snprintf(SshCommand, sizeof(SshCommand),
"exec ssh -q %s@%s /bin/pmCommand %d %s 2>/dev/null",
sd->user, sd->device, sd->serial_port, cmd);
sd->pid = STARTPROC(SshCommand, &sd->rdfd, &sd->wrfd);
if (sd->pid <= 0)
return(S_OOPS);
return(S_OK);
}
static int
CYC_robust_cmd(struct cycladesDevice *sd, char *cmd)
{
int rc = S_OOPS;
int i;
for (i=0; i < 20 && rc != S_OK; i++) {
if (sd->pid > 0)
CYCkillcomm(sd);
if (CYC_run_command(sd, cmd) != S_OK) {
CYCkillcomm(sd);
continue;
}
rc = S_OK;
}
return rc;
}
static int CYCNametoOutlet(struct cycladesDevice *sd, const char *host, int *outlets, int maxoutlet)
{
char *cmd = status_all;
char savebuf[MAXSAVE];
int err;
int outlet, numoutlet = 0;
char name[10], locked[10], on[4];
if (CYC_robust_cmd(sd, cmd) != S_OK) {
syslog(LOG_ERR, "can't run status all command");
return 0;
}
ZEROEXPECT(StatusOutput, 50, savebuf, sizeof(savebuf));
ZEROEXPECT(CRNL, 50, savebuf, sizeof(savebuf));
do {
memset(savebuf, 0, sizeof(savebuf));
memset(name, 0, sizeof(name));
memset(locked, 0, sizeof(locked));
memset(on, 0, sizeof(on));
err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf));
if ((err == S_OK) &&
(sscanf(savebuf,"%3d %10s %10s %3s", &outlet,
name, locked, on) > 0)) {
g_strdown(name);
if (strstr(locked, "ocked") && !strcmp(name, host)) {
if (numoutlet >= maxoutlet) {
syslog(LOG_ERR, "too many outlets");
return 0;
}
outlets[numoutlet++] = outlet;
}
}
} while (err == S_OK);
return numoutlet;
}
/*
* Return the list of hosts configured for this Cyclades device
*/
static char **
cyclades_hostlist(Stonith *s)
{
struct cycladesDevice* sd;
char *cmd = status_all;
char savebuf[MAXSAVE];
int err, i;
int outlet;
int numnames = 0;
char name[10], locked[10], on[4];
char *NameList[MAX_OUTLETS];
char **ret = NULL;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "invalid argument to cyclades_hostlist");
return(NULL);
}
sd = (struct cycladesDevice*) s->pinfo;
if (CYC_robust_cmd(sd, cmd) != S_OK) {
syslog(LOG_ERR, "can't run status all command");
return (NULL);
}
memset(savebuf, 0, sizeof(savebuf));
NULLEXPECT(StatusOutput, 50, savebuf, sizeof(savebuf));
NULLEXPECT(CRNL, 50, savebuf, sizeof(savebuf));
do {
char *nm;
memset(savebuf, 0, sizeof(savebuf));
memset(name, 0, sizeof(name));
memset(locked, 0, sizeof(locked));
memset(on, 0, sizeof(on));
err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf));
if ((err == S_OK) &&
(sscanf(savebuf,"%3d %10s %10s %3s", &outlet,
name, locked, on) > 0)) {
if (strstr(locked, "ocked")) {
nm = (char *) STRDUP (name);
if (!nm) {
syslog(LOG_ERR, "out of memory");
goto out_of_memory;
}
g_strdown(nm);
NameList[numnames] = nm;
numnames++;
NameList[numnames] = NULL;
}
}
} while (err == S_OK);
if (numnames) {
ret = (char **)MALLOC((numnames+1)*sizeof(char*));
if (ret == NULL) {
syslog(LOG_ERR, "out of memory");
goto out_of_memory;
} else {
memcpy(ret, NameList, (numnames+1)*sizeof(char*));
}
return (ret);
}
out_of_memory:
for (i=0; i<numnames; i++)
free(NameList[i]);
return (NULL);
}
static void
cyclades_free_hostlist (char ** hlist)
{
char ** hl = hlist;
if (hl == NULL) {
return;
}
while (*hl) {
FREE(*hl);
*hl = NULL;
++hl;
}
FREE(hlist);
hlist = NULL;
}
/*
* Parse the config information, and stash it away...
*/
static int
cyclades_parse_config_info(struct cycladesDevice* sd, const char * info)
{
static char dev[1024];
static char user[1024];
int serial_port;
if (sd->config)
return(S_OOPS);
/* sscanf is dangerous ? */
if (sscanf(info, "%s %s %d", dev, user, &serial_port) == 3) {
if ((sd->device = STRDUP(dev)) == NULL) {
syslog(LOG_ERR, "out of memory");
return (S_OOPS);
}
if ((sd->user = STRDUP(user)) == NULL) {
FREE(sd->device);
sd->device = NULL;
syslog(LOG_ERR, "out of memory");
return (S_OOPS);
}
sd->serial_port = serial_port;
sd->config = 1;
return(S_OK);
}
return(S_BADCONFIG);
}
static char *cyclades_outletstr(int *outlet, int numoutlet)
{
int i, len;
char *ret;
/* maximum length per outlet is currently four (outlet is one to
* three digits, followed by either a comma or null), so add one
* for good measure */
len = numoutlet * 5 * sizeof(char);
if ((ret = MALLOC(len)) != NULL) {
snprintf(ret, len, "%d", outlet[0]);
for (i = 1; i < numoutlet; i++) {
char buf[5];
snprintf(buf, sizeof(buf), ",%d", outlet[i]);
strcat(ret, buf);
}
}
return(ret);
}
static int cyclades_onoff(struct cycladesDevice *sd, int *outlet, int numoutlet, const char *unitid, int req)
{
char savebuf[MAXSAVE];
const char * onoff;
char cmd[MAX_OUTLETS*4], expstring[64];
struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}};
char *outletstr;
int i;
onoff = (req == ST_POWERON ? "on" : "off");
memset(cmd, 0, sizeof(cmd));
outletstr = cyclades_outletstr(outlet, numoutlet);
if (outletstr == NULL) {
syslog(LOG_ERR, "out of memory");
return(S_OOPS);
}
snprintf(cmd, sizeof(cmd), "%s %s", onoff, outletstr);
if (CYC_robust_cmd(sd, cmd) != S_OK) {
syslog(LOG_ERR, "can't run %s command", onoff);
FREE(outletstr);
return(S_OOPS);
}
EXPECT(CRNL, 50, savebuf, sizeof(savebuf));
for (i = 0; i < numoutlet; i++) {
memset(expstring, 0, sizeof(expstring));
snprintf(expstring, sizeof(expstring), "%d: Outlet turned %s."
, outlet[i], onoff);
exp[0].string = expstring;
/* FIXME: should handle "already powered on/off" case and
inform to syslog */
EXPECT(exp, 50, savebuf, sizeof(savebuf));
}
syslog(LOG_NOTICE, _("Power to host %s turned %s."), unitid, onoff);
FREE(outletstr);
return (S_OK);
}
static int cyclades_reset(struct cycladesDevice *sd, int *outlet, int numoutlet, const char *unitid)
{
char savebuf[MAXSAVE];
char cmd[MAX_OUTLETS*4], expstring[64];
struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}};
char *outletstr;
int i;
memset(cmd, 0, sizeof(cmd));
outletstr = cyclades_outletstr(outlet, numoutlet);
if (outletstr == NULL) {
syslog(LOG_ERR, "out of memory");
return(S_OOPS);
}
snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr);
syslog(LOG_INFO, _("Host %s being rebooted."), unitid);
if (CYC_robust_cmd(sd, cmd) != S_OK) {
syslog(LOG_ERR, "can't run cycle command");
FREE(outletstr);
return(S_OOPS);
}
RESETEXPECT(CRNL, 50, savebuf, sizeof(savebuf));
for (i = 0; i < numoutlet; i++) {
memset(expstring, 0, sizeof(expstring));
snprintf(expstring, sizeof(expstring), "%d: Outlet turned off."
, outlet[i]);
exp[0].string = expstring;
RESETEXPECT(exp, 50, savebuf, sizeof(savebuf));
}
for (i = 0; i < numoutlet; i++) {
memset(expstring, 0, sizeof(expstring));
snprintf(expstring, sizeof(expstring), "%d: Outlet turned on."
, outlet[i]);
exp[0].string = expstring;
RESETEXPECT(exp, 50, savebuf, sizeof(savebuf));
}
FREE(outletstr);
return (S_OK);
}
/*
* Reset the given host on this Stonith device.
*/
static int
cyclades_reset_req(Stonith * s, int request, const char * host)
{
struct cycladesDevice *sd;
int rc = 0;
int numoutlet, outlets[MAX_OUTLETS];
if (!ISCYCLADESDEV(s)) {
syslog (LOG_ERR, "invalid argument to cyclades_reset");
return (S_OOPS);
}
sd = (struct cycladesDevice *)s->pinfo;
numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS);
if (!numoutlet) {
syslog (LOG_ERR, "Unknown host %s to " DEVICE, host);
return (S_OOPS);
}
switch (request) {
case ST_POWERON:
case ST_POWEROFF:
rc = cyclades_onoff(sd, outlets, numoutlet, host, request);
break;
case ST_GENERIC_RESET:
rc = cyclades_reset(sd, outlets, numoutlet, host);
break;
default:
rc = S_INVAL;
break;
}
return rc;
}
/*
* Parse the information in the given configuration file,
* and stash it away...
*/
static int
cyclades_set_config_file(Stonith* s, const char * configname)
{
FILE * cfgfile;
char line[256];
struct cycladesDevice* sd;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "invalid argument to cyclades_set_config_file");
return(S_OOPS);
}
sd = (struct cycladesDevice*) s->pinfo;
if ((cfgfile = fopen(configname, "r")) == NULL) {
syslog(LOG_ERR, "Cannot open %s", configname);
return(S_BADCONFIG);
}
while (fgets(line, sizeof(line), cfgfile) != NULL){
if (*line == '#' || *line == '\n' || *line == EOS) {
continue;
}
return(cyclades_parse_config_info(sd, line));
}
return(S_BADCONFIG);
}
/*
* Parse the config information in the given string, and stash it away...
*/
static int
cyclades_set_config_info(Stonith* s, const char * info)
{
struct cycladesDevice* sd;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "%s: invalid argument", __FUNCTION__);
return(S_OOPS);
}
sd = (struct cycladesDevice *)s->pinfo;
return(cyclades_parse_config_info(sd, info));
}
static const char *
cyclades_getinfo(Stonith * s, int reqtype)
{
struct cycladesDevice * sd;
const char * ret;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "cyclades_getinfo: invalid argument");
return NULL;
}
/*
* We look in the ST_TEXTDOMAIN catalog for our messages
*/
sd = (struct cycladesDevice *)s->pinfo;
switch (reqtype) {
case ST_DEVICEID:
/* FIXME: could inform the exact PM model */
ret = _("Cyclades AlterPath PM");
break;
case ST_CONF_INFO_SYNTAX:
ret = _("IP-address login serial-port\n"
"The IP address, login and serial-port are white-space delimited. "
"login is the username on the TS/ACS (usually root) and "
"serial-port indicates in which port the PM is connected to.");
break;
case ST_CONF_FILE_SYNTAX:
ret = _("IP-address login serial-port\n"
"The IP-address, login and serial-port are white-space delimited. "
"All three items must be on one line. "
"Blank lines and lines beginning with # are ignored");
break;
case ST_DEVICEDESCR: /* Description of device type */
ret = _("Cyclades AlterPath PM "
"series power switches (via TS/ACS/KVM).");
break;
case ST_DEVICEURL:
ret = "http://www.cyclades.com/";
break;
default:
ret = NULL;
break;
}
return ret;
}
/*
* Cyclades Stonith destructor...
*/
static void
cyclades_destroy(Stonith *s)
{
struct cycladesDevice* sd;
if (!ISCYCLADESDEV(s)) {
syslog(LOG_ERR, "%s: invalid argument", __FUNCTION__);
return;
}
sd = (struct cycladesDevice *)s->pinfo;
sd->cycladesid = NOTcycladesID;
CYCkillcomm(sd);
if (sd->device != NULL) {
FREE(sd->device);
sd->device = NULL;
}
if (sd->user != NULL) {
FREE(sd->user);
sd->user = NULL;
}
FREE(sd);
}
/* Create a new cyclades Stonith device */
static void *
cyclades_new(void)
{
struct cycladesDevice* sd = MALLOCT(struct cycladesDevice);
if (sd == NULL) {
syslog(LOG_ERR, "out of memory");
return(NULL);
}
memset(sd, 0, sizeof(*sd));
sd->cycladesid = cycladesid;
sd->rdfd = -1;
sd->wrfd = -1;
return((void *)sd);
}
syntax highlighted by Code2HTML, v. 0.9.1