Logo Search packages:      
Sourcecode: heartbeat-2 version File versions  Download package

external.c

/*
 * 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 <portability.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) {
            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 S_OOPS;
                  }
            }
      }

      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++) {
                  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]);
            }
            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 = 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];
      GString *         g_str_tmp = NULL;
      struct stat       buf;

      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: stating %s failed.", __FUNCTION__, cmd);
                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;
      }

      g_str_tmp = g_string_new("");
      while(!feof(file)) {
            memset(buff, 0, BUFF_LEN);
            read_len = fread(buff, 1, BUFF_LEN, file);
            if (0<read_len) {
                  g_string_append(g_str_tmp, buff);
            }
            else {
                  sleep(1);
            }
      }
      data = (char*)MALLOC(g_str_tmp->len+1);
      if (!data) {
            LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
            goto err;
      }
      
      data[0] = data[g_str_tmp->len] = 0;
      strncpy(data, g_str_tmp->str, g_str_tmp->len);
      g_string_free(g_str_tmp, TRUE);

      rc = pclose(file);
      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);

}

Generated by  Doxygen 1.6.0   Back to index