/* * Stonith module for Cyclades AlterPath PM * Bases off the SSH plugin * * Copyright (c) 2004 Cyclades corp. * * Author: Jon Taylor * * Rewritten from scratch using baytech.c structure and code * and currently maintained by * Marcelo Tosatti * * 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 #define DEVICE "Cyclades AlterPath PM" #define DOESNT_USE_STONITHSCANLINE #include "stonith_plugin_common.h" #define PIL_PLUGIN cyclades #define PIL_PLUGIN_S "cyclades" #define PIL_PLUGINLICENSE LICENSE_LGPL #define PIL_PLUGINLICENSEURL URL_LGPL #include #include "stonith_signal.h" static StonithPlugin * cyclades_new(const char *); static void cyclades_destroy(StonithPlugin *); static int cyclades_set_config(StonithPlugin *, StonithNVpair *); static const char * const * cyclades_get_confignames(StonithPlugin * s); static const char * cyclades_get_info(StonithPlugin * s, int InfoType); static int cyclades_status(StonithPlugin *); static int cyclades_reset_req(StonithPlugin * s, int request, const char * host); static char ** cyclades_hostlist(StonithPlugin *); static struct stonith_ops cycladesOps ={ cyclades_new, /* Create new STONITH object */ cyclades_destroy, /* Destroy STONITH object */ cyclades_get_info, /* Return STONITH info string */ cyclades_get_confignames, /* Return STONITH config vars */ cyclades_set_config, /* set configuration from vars */ cyclades_status, /* Return STONITH device status */ cyclades_reset_req, /* Request a reset */ cyclades_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 , &cycladesOps , NULL /*close */ , &OurInterface , (void*)&OurImports , &interfprivate); } /* * Cyclades STONITH device * */ struct pluginDevice { StonithPlugin sp; const char * pluginid; const char * idinfo; char * device; char * user; 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)", 1, 0}, { "Outlet\tName\t\t\tStatus\t\tInterval (s)\tUsers", 2, 0}, { "Outlet Name Status Post-on Delay(s)", 3, 0}, { NULL, 0, 0} }; static struct Etoken CRNL[] = { {"\n\r",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 pluginDevice *, char *); static const char * pluginid = "CycladesDevice-Stonith"; static const char * NOTpluginID = "Cyclades device has been destroyed"; #define MAX_OUTLETS 128 #define ST_SERIALPORT "serialport" #define ZEROEXPECT(fd,p,t) { \ if (StonithLookFor(fd, p, t) < 0) \ return(0); \ } #define RESETEXPECT(fd,p,t) { \ if (StonithLookFor(fd, p, t) < 0) { \ FREE(outletstr); \ return(errno == ETIMEDOUT \ ? S_RESETFAIL : S_OOPS); \ } \ } #include "stonith_config_xml.h" #define XML_SERIALPORT_SHORTDESC \ XML_PARM_SHORTDESC_BEGIN("en") \ ST_SERIALPORT \ XML_PARM_SHORTDESC_END #define XML_SERIALPORT_LONGDESC \ XML_PARM_LONGDESC_BEGIN("en") \ "The serial port of the IPDU which can powercycle the node" \ XML_PARM_LONGDESC_END #define XML_SERIALPORT_PARM \ XML_PARAMETER_BEGIN(ST_SERIALPORT, "string", "1", "0") \ XML_SERIALPORT_SHORTDESC \ XML_SERIALPORT_LONGDESC \ XML_PARAMETER_END static const char *cycladesXML = XML_PARAMETERS_BEGIN XML_IPADDR_PARM XML_LOGIN_PARM XML_SERIALPORT_PARM XML_PARAMETERS_END; static int CYCScanLine(struct pluginDevice *sd, int timeout, char * buf, int max) { if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max, Debug) < 0) { Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); return(S_OOPS); } return(S_OK); } static int cyclades_status(StonithPlugin *s) { struct pluginDevice *sd; char *cmd = status_all; ERRIFNOTCONFIGED(s,S_OOPS); sd = (struct pluginDevice*) s; if (CYC_robust_cmd(sd, cmd) != S_OK) { LOG(PIL_CRIT, "can't run status all command"); return(S_OOPS); } EXPECT(sd->rdfd, StatusOutput, 50); return(S_OK); } static int CYC_run_command(struct pluginDevice *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 pluginDevice *sd, char *cmd) { int rc = S_OOPS; int i; for (i=0; i < 20 && rc != S_OK; i++) { if (sd->pid > 0) { Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); } if (CYC_run_command(sd, cmd) != S_OK) { Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); continue; } rc = S_OK; } return rc; } #define MAXSAVE 512 static int CYCNametoOutlet(struct pluginDevice *sd, const char *host, int *outlets, int maxoutlet) { char *cmd = status_all; char savebuf[MAXSAVE]; int err; int outlet, numoutlet = 0; char name[17], locked[11], on[4]; if (CYC_robust_cmd(sd, cmd) != S_OK) { LOG(PIL_CRIT, "can't run status all command"); return 0; } ZEROEXPECT(sd->rdfd, StatusOutput, 50); ZEROEXPECT(sd->rdfd, CRNL, 50); 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 %16s %10s %3s", &outlet, name, locked, on) > 0)) { if (!strncasecmp(name, host, strlen(host))) { if (numoutlet >= maxoutlet) { LOG(PIL_CRIT, "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(StonithPlugin *s) { struct pluginDevice* sd; char *cmd = status_all; char savebuf[MAXSAVE]; int err, i; int outlet; int numnames = 0; char name[17], locked[11], on[4]; char *NameList[MAX_OUTLETS]; char **ret = NULL; ERRIFNOTCONFIGED(s,NULL); sd = (struct pluginDevice*) s; if (CYC_robust_cmd(sd, cmd) != S_OK) { LOG(PIL_CRIT, "can't run status all command"); return (NULL); } memset(savebuf, 0, sizeof(savebuf)); NULLEXPECT(sd->rdfd, StatusOutput, 50); NULLEXPECT(sd->rdfd, CRNL, 50); 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 %16s %10s %3s", &outlet, name, locked, on) > 0)) { nm = (char *) STRDUP (name); if (!nm) { goto out_of_memory; } 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) { goto out_of_memory; } else { memcpy(ret, NameList, (numnames+1)*sizeof(char*)); } return (ret); } return(ret); out_of_memory: LOG(PIL_CRIT, "out of memory"); for (i=0; irdfd, exp, 50); } LOG(PIL_DEBUG, "Power to host %s turned %s", unitid, onoff); FREE(outletstr); return (S_OK); } static int cyclades_reset(struct pluginDevice *sd, int *outlet, int numoutlet, const char *unitid) { 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) { LOG(PIL_CRIT, "out of memory"); return (S_OOPS); } snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr); LOG(PIL_INFO, "Host %s being rebooted.", unitid); if (CYC_robust_cmd(sd, cmd) != S_OK) { LOG(PIL_CRIT, "can't run cycle command"); FREE(outletstr); return(S_OOPS); } 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(sd->rdfd, exp, 50); } 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(sd->rdfd, exp, 50); } FREE(outletstr); return (S_OK); } /* * Reset the given host on this Stonith device. */ static int cyclades_reset_req(StonithPlugin * s, int request, const char * host) { struct pluginDevice *sd; int rc = 0; int numoutlet, outlets[MAX_OUTLETS]; ERRIFNOTCONFIGED(s,S_OOPS); sd = (struct pluginDevice*) s; numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS); if (!numoutlet) { LOG(PIL_CRIT, "Unknown host %s to Cyclades PM", 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; } static const char * const * cyclades_get_confignames(StonithPlugin * s) { static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_SERIALPORT, NULL}; return ret; } /* * Parse the config information in the given string, and stash it away... */ static int cyclades_set_config(StonithPlugin* s, StonithNVpair* list) { struct pluginDevice* sd = (struct pluginDevice *)s; int rc; StonithNamesToGet namestocopy[] = { {ST_IPADDR, NULL} , {ST_LOGIN, NULL} , {ST_SERIALPORT, 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->serial_port = atoi(namestocopy[2].s_value); FREE(namestocopy[2].s_value); return(S_OK); } static const char * cyclades_get_info(StonithPlugin * s, int reqtype) { struct pluginDevice * sd; const char * ret; ERRIFWRONGDEV(s, NULL); sd = (struct pluginDevice*) s; switch (reqtype) { case ST_DEVICEID: /* What type of device? */ /* FIXME: could inform the exact PM model */ ret = sd->idinfo; break; case ST_DEVICENAME: /* What particular device? */ ret = sd->device; break; case ST_DEVICEDESCR: /* Description of dev type */ ret = "Cyclades AlterPath PM " "series power switches (via TS/ACS/KVM)."; break; case ST_DEVICEURL: /* Manufacturer's web site */ ret = "http://www.cyclades.com/"; break; case ST_CONF_XML: /* XML metadata */ ret = cycladesXML; break; default: ret = NULL; break; } return ret; } /* * Cyclades Stonith destructor... */ static void cyclades_destroy(StonithPlugin *s) { struct pluginDevice* sd; VOIDERRIFWRONGDEV(s); sd = (struct pluginDevice*) s; sd->pluginid = NOTpluginID; Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); 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 StonithPlugin * cyclades_new(const char *plugin) { struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); if (sd == NULL) { LOG(PIL_CRIT, "out of memory"); return(NULL); } memset(sd, 0, sizeof(*sd)); sd->pluginid = pluginid; sd->pid = -1; sd->rdfd = -1; sd->wrfd = -1; sd->idinfo = DEVICE; sd->sp.s_ops = &cycladesOps; return &(sd->sp); /* same as sd */ }