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/stonith/expect.c

540 lines
12 KiB

/*
* Simple expect module for the STONITH library
*
* Copyright (c) 2000 Alan Robertson <alanr@unix.sh>
*
* 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 <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stonith/st_ttylock.h>
#include <clplumbing/longclock.h>
#define ENABLE_PIL_DEFS_PRIVATE
#include <pils/plugin.h>
#ifdef _POSIX_PRIORITY_SCHEDULING
# include <sched.h>
#endif
#include <stonith/stonith.h>
#include <stonith/stonith_plugin.h>
extern PILPluginUniv* StonithPIsys;
#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args)
#define DEBUG(args...) LOG(PIL_DEBUG, args)
#undef DEBUG
#define DEBUG(args...) PILCallLog(StonithPIsys->imports->log, PIL_DEBUG, args)
#define MALLOC StonithPIsys->imports->alloc
#define REALLOC StonithPIsys->imports->mrealloc
#define STRDUP StonithPIsys->imports->mstrdup
#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;}
#ifdef TIMES_ALLOWS_NULL_PARAM
# define TIMES_PARAM NULL
#else
static struct tms dummy_longclock_tms_struct;
# define TIMES_PARAM &dummy_longclock_tms_struct
#endif
static unsigned long
our_times(void) /* Make times(2) behave rationally on Linux */
{
clock_t ret;
#ifndef DISABLE_TIMES_KLUDGE
int save_errno = errno;
/*
* This code copied from clplumbing/longclock.c to avoid
* making STONITH depend on clplumbing. See it for an explanation
*/
errno = 0;
#endif /* DISABLE_TIMES_KLUDGE */
ret = times(TIMES_PARAM);
#ifndef DISABLE_TIMES_KLUDGE
if (errno != 0) {
ret = (clock_t) (-errno);
}
errno = save_errno;
#endif /* DISABLE_TIMES_KLUDGE */
return (unsigned long)ret;
}
/*
* Look for ('expect') any of a series of tokens in the input
* Return the token type for the given token or -1 on error.
*/
static int
ExpectToken(int fd, struct Etoken * toklist, int to_secs, char * savebuf
, int maxline, int Debug)
{
unsigned long starttime;
unsigned long endtime;
int wraparound=0;
unsigned Hertz = sysconf(_SC_CLK_TCK);
int tickstousec = (1000000/Hertz);
unsigned long now;
unsigned long ticks;
int nchars = 1; /* reserve space for an EOS */
struct timeval tv;
char * buf = savebuf;
struct Etoken * this;
/* Figure out when to give up. Handle lbolt wraparound */
starttime = our_times();
ticks = (to_secs*Hertz);
endtime = starttime + ticks;
if (endtime < starttime) {
wraparound = 1;
}
if (buf) {
*buf = EOS;
}
for (this=toklist; this->string; ++this) {
this->matchto = 0;
}
while (now = our_times(),
(wraparound && (now > starttime || now <= endtime))
|| (!wraparound && now <= endtime)) {
fd_set infds;
char ch;
unsigned long timeleft;
int retval;
timeleft = endtime - now;
tv.tv_sec = timeleft / Hertz;
tv.tv_usec = (timeleft % Hertz) * tickstousec;
if (tv.tv_sec == 0 && tv.tv_usec < tickstousec) {
/* Give 'em a little chance */
tv.tv_usec = tickstousec;
}
/* Watch our FD to see when it has input. */
FD_ZERO(&infds);
FD_SET(fd, &infds);
retval = select(fd+1, &infds, NULL, NULL, &tv);
if (retval <= 0) {
errno = ETIMEDOUT;
return(-1);
}
/* Whew! All that work just to read one character! */
if (read(fd, &ch, sizeof(ch)) <= 0) {
return(-1);
}
/* Save the text, if we can */
if (buf && nchars < maxline-1) {
*buf = ch;
++buf;
*buf = EOS;
++nchars;
}
if (Debug > 1) {
DEBUG("Got '%c'", ch);
}
/* See how this character matches our expect strings */
for (this=toklist; this->string; ++this) {
if (ch == this->string[this->matchto]) {
/* It matches the current token */
++this->matchto;
if (this->string[this->matchto] == EOS){
/* Hallelujah! We matched */
if (Debug) {
DEBUG("Matched [%s] [%d]"
, this->string
, this->toktype);
if (savebuf) {
DEBUG("Saved [%s]"
, savebuf);
}
}
return(this->toktype);
}
}else{
/* It doesn't appear to match this token */
int curlen;
int nomatch=1;
/*
* If we already had a match (matchto is
* greater than zero), we look for a match
* of the tail of the pattern matched so far
* (with the current character) against the
* head of the pattern.
*/
/*
* This is to make the string "aab" match
* the pattern "ab" correctly
* Painful, but nice to do it right.
*/
for (curlen = (this->matchto)
; nomatch && curlen >= 0
; --curlen) {
const char * tail;
tail=(this->string)
+ this->matchto
- curlen;
if (strncmp(this->string, tail
, curlen) == 0
&& this->string[curlen] == ch) {
if (this->string[curlen+1]==EOS){
/* We matched! */
/* (can't happen?) */
return(this->toktype);
}
this->matchto = curlen+1;
nomatch=0;
}
}
if (nomatch) {
this->matchto = 0;
}
}
}
}
errno = ETIMEDOUT;
return(-1);
}
/*
* Start a process with its stdin and stdout redirected to pipes
* so the parent process can talk to it.
*/
static int
StartProcess(const char * cmd, int * readfd, int * writefd)
{
pid_t pid;
int wrpipe[2]; /* The pipe the parent process writes to */
/* (which the child process reads from) */
int rdpipe[2]; /* The pipe the parent process reads from */
/* (which the child process writes to) */
if (pipe(wrpipe) < 0) {
perror("cannot create pipe\n");
return(-1);
}
if (pipe(rdpipe) < 0) {
perror("cannot create pipe\n");
close(wrpipe[0]);
close(wrpipe[1]);
return(-1);
}
switch(pid=fork()) {
case -1: perror("cannot StartProcess cmd");
close(rdpipe[0]);
close(wrpipe[1]);
close(wrpipe[0]);
close(rdpipe[1]);
return(-1);
case 0: /* We are the child */
/* Redirect stdin */
close(0);
dup2(wrpipe[0], 0);
close(wrpipe[0]);
close(wrpipe[1]);
/* Redirect stdout */
close(1);
dup2(rdpipe[1], 1);
close(rdpipe[0]);
close(rdpipe[1]);
#if defined(SCHED_OTHER) && !defined(ON_DARWIN)
{
/*
* Try and (re)set our scheduling to "normal"
* Sometimes our callers run in soft
* real-time mode. The program we exec might
* not be very well behaved - this is bad for
* operation in high-priority (soft real-time)
* mode. In particular, telnet is prone to
* going into infinite loops when killed.
*/
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &sp);
}
#endif
execlp("/bin/sh", "sh", "-c", cmd, (const char *)NULL);
perror("cannot exec shell!");
exit(1);
default: /* We are the parent */
*readfd = rdpipe[0];
close(rdpipe[1]);
*writefd = wrpipe[1];
close(wrpipe[0]);
return(pid);
}
/*NOTREACHED*/
return(-1);
}
static char **
stonith_copy_hostlist(const char** hostlist)
{
int hlleng = 1;
const char ** here = hostlist;
char ** hret;
char ** ret;
for (here = hostlist; *here; ++here) {
++hlleng;
}
ret = (char**)MALLOC(hlleng * sizeof(char *));
if (ret == NULL) {
return ret;
}
hret = ret;
for (here = hostlist; *here; ++here,++hret) {
*hret = STRDUP(*here);
if (*hret == NULL) {
stonith_free_hostlist(ret);
return NULL;
}
}
*hret = NULL;
return ret;
}
static char **
StringToHostList(const char * s)
{
const char * here;
int hlleng = 0;
char ** ret;
char ** hret;
const char * delims = " \t\n\f\r,";
/* Count the number of strings (words) in the result */
here = s;
while (*here != EOS) {
/* skip delimiters */
here += strspn(here, delims);
if (*here == EOS) {
break;
}
/* skip over substring proper... */
here += strcspn(here, delims);
++hlleng;
}
/* Malloc space for the result string pointers */
ret = (char**)MALLOC((hlleng+1) * sizeof(char *));
if (ret == NULL) {
return NULL;
}
hret = ret;
here = s;
/* Copy each substring into a separate string */
while (*here != EOS) {
int slen; /* substring length */
/* skip delimiters */
here += strspn(here, delims);
if (*here == EOS) {
break;
}
/* Compute substring length */
slen = strcspn(here, delims);
*hret = MALLOC((slen+1) * sizeof(char));
if (*hret == NULL) {
stonith_free_hostlist(hret);
return NULL;
}
/* Copy string (w/o EOS) */
memcpy(*hret, here, slen);
/* Add EOS to result string */
(*hret)[slen] = EOS;
g_strdown(*hret);
here += slen;
++hret;
}
*hret = NULL;
return ret;
}
static const char *
GetValue(StonithNVpair* parameters, const char * name)
{
while (parameters->s_name) {
if (strcmp(name, parameters->s_name) == 0) {
return parameters->s_value;
}
++parameters;
}
return NULL;
}
static int
CopyAllValues(StonithNamesToGet* output, StonithNVpair * input)
{
int j;
int rc;
for (j=0; output[j].s_name; ++j) {
const char * value = GetValue(input, output[j].s_name);
if (value == NULL) {
rc = S_INVAL;
output[j].s_value = NULL;
goto fail;
}
if ((output[j].s_value = STRDUP(value)) == NULL) {
rc = S_OOPS;
goto fail;
}
}
return S_OK;
fail:
for (j=0; output[j].s_value; ++j) {
FREE(output[j].s_value);
}
return rc;
}
static int
OpenStreamSocket(const char * host, int port, const char * service)
{
union s_un {
struct sockaddr_in si4;
struct sockaddr_in6 si6;
}sockun;
int sock;
int addrlen = -1;
memset(&sockun, 0, sizeof(sockun));
if (inet_pton(AF_INET, host, (void*)&sockun.si4.sin_addr) < 0) {
sockun.si4.sin_family = AF_INET;
}else if (inet_pton(AF_INET6, host, (void*)&sockun.si6.sin6_addr)<0){
sockun.si6.sin6_family = AF_INET6;
}else{
struct hostent* hostp = gethostbyname(host);
if (hostp == NULL) {
errno = EINVAL;
return -1;
}
sockun.si4.sin_family = hostp->h_addrtype;
memcpy(&sockun.si4.sin_addr, hostp->h_addr, hostp->h_length);
}
if ((sock = socket(sockun.si4.sin_family, SOCK_STREAM, 0)) < 0) {
return -1;
}
if (service != NULL) {
struct servent* se = getservbyname(service, "tcp");
if (se != NULL) {
/* We convert it back later... */
port = ntohs(se->s_port);
}
}
if (port <= 0) {
errno = EINVAL;
return -1;
}
port = htons(port);
if (sockun.si6.sin6_family == AF_INET6) {
sockun.si6.sin6_port = port;
addrlen = sizeof(sockun.si6);
}else if (sockun.si4.sin_family == AF_INET) {
sockun.si4.sin_port = port;
addrlen = sizeof(sockun.si4);
}else{
errno = EINVAL;
return -1;
}
if (connect(sock, (struct sockaddr*)(&sockun), addrlen)< 0){
int save = errno;
perror("connect() failed");
close(sock);
errno = save;
return -1;
}
return sock;
}
StonithImports stonithimports = {
ExpectToken,
StartProcess,
OpenStreamSocket,
GetValue,
CopyAllValues,
StringToHostList,
stonith_copy_hostlist,
stonith_free_hostlist,
st_ttylock,
st_ttyunlock
};