/* $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 * * 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 #include #include #include #include #include #include #include #include #include #include #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 /* * 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; iconfig) 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); }