mirror of https://github.com/sipwise/heartbeat.git
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.
540 lines
12 KiB
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
|
|
};
|