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.
heartbeat/lib/plugins/stonith/external.c

794 lines
17 KiB

/*
* Stonith module for EXTERNAL Stonith device
*
* Copyright (c) 2001 SuSE Linux AG
* Portions Copyright (c) 2004, tummy.com, ltd.
*
* Based on ssh.c, Authors: Joachim Gleissner <jg@suse.de>,
* Lars Marowsky-Bree <lmb@suse.de>
* Modified for external.c: Scott Kleihege <scott@tummy.com>
* Reviewed, tested, and config parsing: Sean Reifschneider <jafo@tummy.com>
* And overhauled by Lars Marowsky-Bree <lmb@suse.de>, so the circle
* closes...
* Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005
* Changed to allow full-featured external plugins by Dave Blaschke
* <debltc@us.ibm.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 <lha_internal.h>
#include <dirent.h>
#include "stonith_plugin_common.h"
#define PIL_PLUGIN external
#define PIL_PLUGIN_S "external"
#define PIL_PLUGINLICENSE LICENSE_LGPL
#define PIL_PLUGINLICENSEURL URL_LGPL
#include <pils/plugin.h>
static StonithPlugin * external_new(const char *);
static void external_destroy(StonithPlugin *);
static int external_set_config(StonithPlugin *, StonithNVpair *);
static const char** external_get_confignames(StonithPlugin *);
static const char * external_getinfo(StonithPlugin * s, int InfoType);
static int external_status(StonithPlugin * );
static int external_reset_req(StonithPlugin * s, int request, const char * host);
static char ** external_hostlist(StonithPlugin *);
static struct stonith_ops externalOps ={
external_new, /* Create new STONITH object */
external_destroy, /* Destroy STONITH object */
external_getinfo, /* Return STONITH info string */
external_get_confignames, /* Return STONITH info string */
external_set_config, /* Get configuration from NVpairs */
external_status, /* Return STONITH device status */
external_reset_req, /* Request a reset */
external_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
, &externalOps
, NULL /*close */
, &OurInterface
, (void*)&OurImports
, &interfprivate);
}
/*
* EXTERNAL STONITH device
*/
struct pluginDevice {
StonithPlugin sp;
const char * pluginid;
GHashTable * cmd_opts;
char * subplugin;
char ** confignames;
char * outputbuf;
};
static const char * pluginid = "ExternalDevice-Stonith";
static const char * NOTpluginID = "External device has been destroyed";
/* Prototypes */
/* Run the command with op and return the exit status + the output
* (NULL -> discard output) */
static int external_run_cmd(struct pluginDevice *sd, const char *op,
char **output);
/* Just free up the configuration and the memory, if any */
static void external_unconfig(struct pluginDevice *sd);
static int
external_status(StonithPlugin *s)
{
struct pluginDevice * sd;
const char * op = "status";
int rc;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
ERRIFWRONGDEV(s,S_OOPS);
sd = (struct pluginDevice*) s;
if (sd->subplugin == NULL) {
LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__);
return(S_OOPS);
}
rc = external_run_cmd(sd, op, NULL);
if (Debug) {
LOG(PIL_DEBUG, "%s: running '%s %s' returned %d",
__FUNCTION__, sd->subplugin, op, rc);
}
return rc;
}
static int
get_num_tokens(char *str)
{
int namecount = 0;
while (*str != EOS) {
str += strspn(str, WHITESPACE);
if (*str == EOS)
break;
str += strcspn(str, WHITESPACE);
namecount++;
}
return namecount;
}
static char **
external_hostlist(StonithPlugin *s)
{
struct pluginDevice* sd;
const char * op = "gethosts";
int rc, i, namecount;
char ** ret;
char * output = NULL;
char * tmp;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
ERRIFNOTCONFIGED(s,NULL);
sd = (struct pluginDevice*) s;
if (sd->subplugin == NULL) {
LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__);
return(NULL);
}
rc = external_run_cmd(sd, op, &output);
if (Debug) {
LOG(PIL_DEBUG, "%s: running '%s %s' returned %d",
__FUNCTION__, sd->subplugin, op, rc);
}
if (rc != 0) {
LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d",
__FUNCTION__, sd->subplugin, op, rc);
if (output) {
FREE(output);
}
return NULL;
}
if (!output) {
LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist",
__FUNCTION__, sd->subplugin, op);
return NULL;
}
namecount = get_num_tokens(output);
ret = MALLOC((namecount+1)*sizeof(char *));
if (!ret) {
LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
FREE(output);
return NULL;
}
memset(ret, 0, (namecount+1)*sizeof(char *));
/* White-space split the output here */
i = 0;
tmp = strtok(output, WHITESPACE);
while (tmp != NULL) {
if (Debug) {
LOG(PIL_DEBUG, "%s: %s host %s",
__FUNCTION__, sd->subplugin, tmp);
}
ret[i] = STRDUP(tmp);
if (!ret[i]) {
LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
FREE(output);
stonith_free_hostlist(ret);
return NULL;
}
i++;
tmp = strtok(NULL, WHITESPACE);
}
FREE(output);
if (i == 0) {
LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist",
__FUNCTION__, sd->subplugin, op);
stonith_free_hostlist(ret);
ret = NULL;
}
return(ret);
}
static int
external_reset_req(StonithPlugin * s, int request, const char * host)
{
struct pluginDevice * sd;
const char * op;
int rc;
char * args1and2;
int argslen;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
ERRIFNOTCONFIGED(s,S_OOPS);
if (Debug) {
LOG(PIL_DEBUG, "Host external-reset initiating on %s", host);
}
sd = (struct pluginDevice*) s;
if (sd->subplugin == NULL) {
LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__);
return(S_OOPS);
}
switch (request) {
case ST_GENERIC_RESET:
op = "reset";
break;
case ST_POWEROFF:
op = "off";
break;
case ST_POWERON:
op = "on";
break;
default:
LOG(PIL_CRIT, "%s: Unknown stonith request %d",
__FUNCTION__, request);
return S_OOPS;
break;
}
argslen = strlen(op) + strlen(host) + 2 /* 1 for blank, 1 for EOS */;
args1and2 = (char *)MALLOC(argslen);
if (args1and2 == NULL) {
LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__);
return S_OOPS;
}
rc = snprintf(args1and2, argslen, "%s %s", op, host);
if (rc <= 0 || rc >= argslen) {
FREE(args1and2);
return S_OOPS;
}
rc = external_run_cmd(sd, args1and2, NULL);
if (Debug) {
LOG(PIL_DEBUG, "%s: running '%s %s' returned %d",
__FUNCTION__, sd->subplugin, op, rc);
}
FREE(args1and2);
if (rc != 0) {
LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d",
__FUNCTION__, sd->subplugin, op, host, rc);
return(S_RESETFAIL);
}
return S_OK;
}
static int
external_parse_config_info(struct pluginDevice* sd, StonithNVpair * info)
{
char * key;
char * value;
StonithNVpair * nv;
sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal);
/* TODO: Maybe treat "" as delimeters too so
* whitespace can be passed to the plugins... */
for (nv = info; nv->s_name; nv++) {
key = STRDUP(nv->s_name);
if (!key) {
goto err_mem;
}
value = STRDUP(nv->s_value);
if (!value) {
FREE(key);
goto err_mem;
}
g_hash_table_insert(sd->cmd_opts, key, value);
}
return(S_OK);
err_mem:
LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__);
external_unconfig(sd);
return(S_OOPS);
}
static gboolean
let_remove_eachitem(gpointer key, gpointer value, gpointer user_data)
{
if (key) {
FREE(key);
}
if (value) {
FREE(value);
}
return TRUE;
}
static void
external_unconfig(struct pluginDevice *sd) {
if (sd->cmd_opts) {
g_hash_table_foreach_remove(sd->cmd_opts,
let_remove_eachitem, NULL);
g_hash_table_destroy(sd->cmd_opts);
sd->cmd_opts = NULL;
}
}
/*
* Parse the information in the given string
* and stash it away...
*/
static int
external_set_config(StonithPlugin* s, StonithNVpair *list)
{
struct pluginDevice * sd;
char ** p;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
ERRIFWRONGDEV(s,S_OOPS);
/* make sure that command has not already been set */
if (s->isconfigured) {
return(S_OOPS);
}
sd = (struct pluginDevice*) s;
if (sd->subplugin == NULL) {
LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__);
return(S_OOPS);
}
if (sd->confignames == NULL) {
/* specified by name=value pairs, check required parms */
if (external_get_confignames(s) == NULL) {
return(S_OOPS);
}
for (p = sd->confignames; *p; p++) {
if (OurImports->GetValue(list, *p) == NULL) {
LOG(PIL_INFO, "Cannot get parameter %s from "
"StonithNVpair", *p);
}
}
}
return external_parse_config_info(sd, list);
}
/* Only interested in regular files that are also executable */
static int
exec_select(const struct dirent *dire)
{
struct stat statf;
char filename[FILENAME_MAX];
int rc;
rc = snprintf(filename, FILENAME_MAX, "%s/%s",
STONITH_EXT_PLUGINDIR, dire->d_name);
if (rc <= 0 || rc >= FILENAME_MAX) {
return 0;
}
if ((stat(filename, &statf) == 0) &&
(S_ISREG(statf.st_mode)) &&
(statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) {
if (statf.st_mode & (S_IWGRP|S_IWOTH)) {
LOG(PIL_WARN, "Executable file %s ignored "
"(writable by group/others)", filename);
return 0;
}else{
return 1;
}
}
return 0;
}
/*
* Return STONITH config vars
*/
static const char**
external_get_confignames(StonithPlugin* p)
{
struct pluginDevice * sd;
const char * op = "getconfignames";
int i, rc;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
sd = (struct pluginDevice *)p;
if (sd->subplugin != NULL) {
/* return list of subplugin's required parameters */
char *output = NULL, *pch;
int namecount;
rc = external_run_cmd(sd, op, &output);
if (Debug) {
LOG(PIL_DEBUG, "%s: '%s %s' returned %d",
__FUNCTION__, sd->subplugin, op, rc);
}
if (rc != 0) {
LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d",
__FUNCTION__, sd->subplugin, op, rc);
if (output) { FREE(output); }
return NULL;
}
namecount = get_num_tokens(output);
sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *));
if (sd->confignames == NULL) {
LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
if (output) { FREE(output); }
return NULL;
}
/* now copy over confignames */
pch = strtok(output, WHITESPACE);
for (i = 0; i < namecount; i++) {
if (Debug) {
LOG(PIL_DEBUG, "%s: %s configname %s",
__FUNCTION__, sd->subplugin, pch);
}
sd->confignames[i] = STRDUP(pch);
pch = strtok(NULL, WHITESPACE);
}
FREE(output);
sd->confignames[namecount] = NULL;
}else{
/* return list of subplugins in external directory */
struct dirent ** files = NULL;
int dircount;
/* get the external plugin's confignames (list of subplugins) */
dircount = scandir(STONITH_EXT_PLUGINDIR, &files,
SCANSEL_CAST exec_select, NULL);
if (dircount < 0) {
return NULL;
}
sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *));
if (!sd->confignames) {
LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
return NULL;
}
for (i = 0; i < dircount; i++) {
sd->confignames[i] = STRDUP(files[i]->d_name);
free(files[i]);
files[i] = NULL;
}
free(files);
sd->confignames[dircount] = NULL;
}
return (const char **)sd->confignames;
}
/*
* Return STONITH info string
*/
static const char *
external_getinfo(StonithPlugin * s, int reqtype)
{
struct pluginDevice* sd;
char * ret = NULL;
const char * op;
int rc;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
ERRIFWRONGDEV(s,NULL);
sd = (struct pluginDevice *)s;
if (sd->subplugin == NULL) {
LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__);
return(NULL);
}
switch (reqtype) {
case ST_DEVICEID:
op = "getinfo-devid";
break;
case ST_DEVICENAME:
op = "getinfo-devname";
break;
case ST_DEVICEDESCR:
op = "getinfo-devdescr";
break;
case ST_DEVICEURL:
op = "getinfo-devurl";
break;
case ST_CONF_XML:
op = "getinfo-xml";
break;
default:
return NULL;
}
rc = external_run_cmd(sd, op, &ret);
if (Debug) {
LOG(PIL_DEBUG, "%s: '%s %s' returned %d",
__FUNCTION__, sd->subplugin, op, rc);
}
if (rc == 0) {
if (sd->outputbuf == NULL) {
FREE(sd->outputbuf);
}
sd->outputbuf = ret;
return(ret);
}
return(NULL);
}
/*
* EXTERNAL Stonith destructor...
*/
static void
external_destroy(StonithPlugin *s)
{
struct pluginDevice * sd;
char ** p;
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
VOIDERRIFWRONGDEV(s);
sd = (struct pluginDevice *)s;
sd->pluginid = NOTpluginID;
external_unconfig(sd);
if (sd->confignames != NULL) {
for (p = sd->confignames; *p; p++) {
FREE(*p);
}
FREE(sd->confignames);
sd->confignames = NULL;
}
if (sd->subplugin != NULL) {
FREE(sd->subplugin);
sd->subplugin = NULL;
}
if (sd->outputbuf != NULL) {
FREE(sd->outputbuf);
sd->outputbuf = NULL;
}
FREE(sd);
}
/* Create a new external Stonith device */
static StonithPlugin *
external_new(const char *subplugin)
{
struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice);
if (Debug) {
LOG(PIL_DEBUG, "%s: called.", __FUNCTION__);
}
if (sd == NULL) {
LOG(PIL_CRIT, "out of memory");
return(NULL);
}
memset(sd, 0, sizeof(*sd));
sd->pluginid = pluginid;
if (subplugin != NULL) {
sd->subplugin = STRDUP(subplugin);
if (sd->subplugin == NULL) {
FREE(sd);
return(NULL);
}
}
sd->sp.s_ops = &externalOps;
return &(sd->sp);
}
static void
ext_add_to_env(gpointer key, gpointer value, gpointer user_data)
{
if (setenv((char *)key, (char *)value, 1) != 0) {
LOG(PIL_CRIT, "%s: setenv failed.", __FUNCTION__);
}
}
static void
ext_del_from_env(gpointer key, gpointer value, gpointer user_data)
{
unsetenv((char *)key);
}
/* Run the command with op as command line argument(s) and return the exit
* status + the output */
static int
external_run_cmd(struct pluginDevice *sd, const char *op, char **output)
{
const int BUFF_LEN=4096;
char buff[BUFF_LEN];
int read_len = 0;
int rc;
char * data = NULL;
FILE * file;
char cmd[FILENAME_MAX+64];
struct stat buf;
int slen;
rc = snprintf(cmd, FILENAME_MAX, "%s/%s",
STONITH_EXT_PLUGINDIR, sd->subplugin);
if (rc <= 0 || rc >= FILENAME_MAX) {
LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__);
return -1;
}
if (stat(cmd, &buf) != 0) {
LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s",
__FUNCTION__, cmd, strerror(errno));
return -1;
}
if (!S_ISREG(buf.st_mode)
|| (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) {
LOG(PIL_CRIT, "%s: %s found NOT to be executable.",
__FUNCTION__, cmd);
return -1;
}
if (buf.st_mode & (S_IWGRP|S_IWOTH)) {
LOG(PIL_CRIT, "%s: %s found to be writable by group/others, "
"NOT executing for security purposes.",
__FUNCTION__, cmd);
return -1;
}
strcat(cmd, " ");
strcat(cmd, op);
/* We only have a global environment to use here. So we add our
* options to it, and then later remove them again. */
if (sd->cmd_opts) {
g_hash_table_foreach(sd->cmd_opts, ext_add_to_env, NULL);
}
if (Debug) {
LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd );
}
file = popen(cmd, "r");
if (NULL==file) {
LOG(PIL_CRIT, "%s: Calling '%s' failed",
__FUNCTION__, cmd);
goto err;
}
data = NULL;
slen=0;
data = MALLOC(1);
while(data && !feof(file)) {
data[slen]=EOS;
read_len = fread(buff, 1, BUFF_LEN, file);
if (read_len > 0) {
data=REALLOC(data, slen+read_len+1);
if (data == NULL) {
break;
}
memcpy(data+slen, buff, read_len);
slen += read_len;
data[slen] = EOS;
}else{
sleep(1);
}
}
if (!data) {
LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
goto err;
}
rc = pclose(file);
if (rc != 0) {
LOG(PIL_INFO, "%s: Calling '%s' returned %d", __FUNCTION__, cmd, rc);
}
if (Debug && data) {
LOG(PIL_INFO, "%s: '%s' output: %s", __FUNCTION__, cmd, data);
}
if (output) {
*output = data;
} else {
FREE(data);
}
if (sd->cmd_opts) {
g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL);
}
return(rc);
err:
if (sd->cmd_opts) {
g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL);
}
if (data) {
FREE(data);
}
if (output) {
*output = NULL;
}
return(-1);
}