diff options
Diffstat (limited to 'lib')
132 files changed, 54150 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..5047e1d --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = pils clplumbing lrm stonith plugins diff --git a/lib/clplumbing/GSource.c b/lib/clplumbing/GSource.c new file mode 100644 index 0000000..48bb198 --- /dev/null +++ b/lib/clplumbing/GSource.c @@ -0,0 +1,1864 @@ +/* + * Copyright (c) 2002 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <string.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/GSource_internal.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/timers.h> + +#ifdef events +# undef events +#endif +#ifdef revents +# undef revents +#endif + +#define DEFAULT_MAXDISPATCH 0 +#define DEFAULT_MAXDELAY 0 +#define OTHER_MAXDELAY 100 + +/* + * On architectures with alignment constraints, our casting between + * "(GSource*)" and "(GFDSource_s*)" etc. causes trouble, because of + * the massive alignment requirements of "longclock_t". + * + * Use the following to store and fetch. + */ +static +void +lc_store(char *destptr, longclock_t value) { + longclock_t _ltt; + _ltt = value; + memcpy((destptr), &_ltt, sizeof(longclock_t)); +} + +static +longclock_t +lc_fetch(char *ptr) { + longclock_t _ltt; + memcpy(&_ltt, (ptr), sizeof(longclock_t)); + return _ltt; +} + +#define ERR_EVENTS (G_IO_ERR|G_IO_NVAL) +#define INPUT_EVENTS (G_IO_IN|G_IO_PRI|G_IO_HUP) +#define OUTPUT_EVENTS (G_IO_OUT) +#define DEF_EVENTS (INPUT_EVENTS|ERR_EVENTS) + +#define WARN_DELAY(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s was delayed" \ + " %lu ms (> %lu ms) before being called (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define EXPLAINDELAY(started, detected) cl_log(LOG_INFO \ + , "%s: started at %llu should have started at %llu" \ + , __FUNCTION__, (unsigned long long)started \ + , (unsigned long long)detected) + + +#define WARN_TOOLONG(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s took too long to execute" \ + ": %lu ms (> %lu ms) (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define CHECK_DISPATCH_DELAY(i) { \ + unsigned long ms; \ + longclock_t dettime; \ + dispstart = time_longclock(); \ + dettime = lc_fetch((i)->detecttime); \ + ms = longclockto_ms(sub_longclock(dispstart,dettime)); \ + if ((i)->maxdispatchdelayms > 0 \ + && ms > (i)->maxdispatchdelayms) { \ + WARN_DELAY(ms, (i)->maxdispatchdelayms, (i)); \ + EXPLAINDELAY(dispstart, dettime); \ + } \ +} + +#define CHECK_DISPATCH_TIME(i) { \ + unsigned long ms; \ + longclock_t dispend = time_longclock(); \ + ms = longclockto_ms(sub_longclock(dispend, dispstart)); \ + if ((i)->maxdispatchms > 0 && ms > (i)->maxdispatchms) { \ + WARN_TOOLONG(ms, (i)->maxdispatchms, (i)); \ + } \ + lc_store(((i)->detecttime), zero_longclock); \ +} + +#define WARN_TOOMUCH(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: working on %s took %ld ms (> %ld ms)" \ + , __FUNCTION__, (input)->description, ms, mx); + +#define SAVESTART {funstart = time_longclock();} + +#define CHECKEND(input) { \ + longclock_t funend = time_longclock(); \ + long ms; \ + ms = longclockto_ms(sub_longclock(funend, funstart)); \ + if (ms > OTHER_MAXDELAY){ \ + WARN_TOOMUCH(ms, ((long) OTHER_MAXDELAY), input); \ + } \ +} \ + + +#ifndef _NSIG +# define _NSIG 2*NSIG +#endif + +static gboolean G_fd_prepare(GSource* source, + gint* timeout); +static gboolean G_fd_check(GSource* source); +static gboolean G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_fd_destroy(GSource* source); + +static GSourceFuncs G_fd_SourceFuncs = { + G_fd_prepare, + G_fd_check, + G_fd_dispatch, + G_fd_destroy, +}; + +GSource* +G_main_add_input(int priority, + gboolean can_recurse, + GSourceFuncs* funcs) +{ + GSource * input_source = g_source_new(funcs, sizeof(GSource)); + if (input_source == NULL){ + cl_log(LOG_ERR, "create glib source for input failed!"); + }else { + g_source_set_priority(input_source, priority); + g_source_set_can_recurse(input_source, can_recurse); + if(g_source_attach(input_source, NULL) == 0){ + cl_log(LOG_ERR, "attaching input_source to main context" + " failed!! "); + } + } + + return input_source; +} + + +/* + * Add the given file descriptor to the gmainloop world. + */ + + +GFDSource* +G_main_add_fd(int priority, int fd, gboolean can_recurse +, gboolean (*dispatch)(int fd, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GSource* source = g_source_new(&G_fd_SourceFuncs, + sizeof(GFDSource)); + GFDSource* ret = (GFDSource*)source; + + ret->magno = MAG_GFDSOURCE; + ret->maxdispatchdelayms = DEFAULT_MAXDELAY; + ret->maxdispatchms = DEFAULT_MAXDISPATCH; + ret->udata = userdata; + ret->dispatch = dispatch; + ret->gpfd.fd = fd; + ret->gpfd.events = DEF_EVENTS; + ret->gpfd.revents = 0; + ret->dnotify = notify; + lc_store((ret->detecttime), zero_longclock); + + g_source_add_poll(source, &ret->gpfd); + + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + ret->gsourceid = g_source_attach(source, NULL); + ret->description = "file descriptor"; + + if (ret->gsourceid == 0) { + g_source_remove_poll(source, &ret->gpfd); + memset(ret, 0, sizeof(GFDSource)); + g_source_unref(source); + source = NULL; + ret = NULL; + } + return ret; +} + +gboolean +G_main_del_fd(GFDSource* fdp) +{ + GSource * source = (GSource*) fdp; + + + if (fdp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove_poll(source, &fdp->gpfd); + g_source_remove(fdp->gsourceid); + fdp->gsourceid = 0; + g_source_unref(source); + + return TRUE; + +} + +void +g_main_output_is_blocked(GFDSource* fdp) +{ + fdp->gpfd.events |= OUTPUT_EVENTS; +} + + +/* + * For pure file descriptor events, return FALSE because we + * have to poll to get events. + * + * Note that we don't modify 'timeout' either. + */ +static gboolean +G_fd_prepare(GSource* source, + gint* timeout) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_fd_check(GSource* source) + +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + if (fdp->gpfd.revents) { + lc_store((fdp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + + GFDSource* fdp = (GFDSource*)source; + longclock_t dispstart; + g_assert(IS_FDSOURCE(fdp)); + CHECK_DISPATCH_DELAY(fdp); + + + /* + * Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (fdp->gpfd.revents & OUTPUT_EVENTS) { + fdp->gpfd.events &= ~OUTPUT_EVENTS; + } + + if(fdp->dispatch) { + if(!(fdp->dispatch(fdp->gpfd.fd, fdp->udata))){ + g_source_remove_poll(source,&fdp->gpfd); + g_source_unref(source); + CHECK_DISPATCH_TIME(fdp); + return FALSE; + } + CHECK_DISPATCH_TIME(fdp); + } + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_fd_destroy(GSource* source) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + fdp->gsourceid = 0; + if (fdp->dnotify) { + fdp->dnotify(fdp->udata); + } +} + + +/************************************************************ + * Functions for IPC_Channels + ***********************************************************/ +gboolean G_CH_prepare_int(GSource* source, + gint* timeout); +gboolean G_CH_check_int(GSource* source); + +gboolean G_CH_dispatch_int(GSource* source, + GSourceFunc callback, + gpointer user_data); +void G_CH_destroy_int(GSource* source); + + +static GSourceFuncs G_CH_SourceFuncs = { + G_CH_prepare_int, + G_CH_check_int, + G_CH_dispatch_int, + G_CH_destroy_int, +}; + + + + +void +set_IPC_Channel_dnotify(GCHSource* chp, + GDestroyNotify notify){ + chp->dnotify = notify; +} + +/* + * Add an IPC_channel to the gmainloop world... + */ +GCHSource* +G_main_IPC_Channel_constructor(GSource* source, IPC_Channel* ch + , gpointer userdata + , GDestroyNotify notify) +{ + int rfd, wfd; + GCHSource* chp; + + if( !source ) { + cl_log(LOG_WARNING, "%s:%d: got null source", __FUNCTION__,__LINE__); + return NULL; + } + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + chp = (GCHSource*)source; + + chp->magno = MAG_GCHSOURCE; + chp->maxdispatchdelayms = DEFAULT_MAXDELAY; + chp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((chp->detecttime), zero_longclock); + ch->refcount++; + chp->ch = ch; + chp->udata=userdata; + chp->dnotify = notify; + chp->dontread = FALSE; + + rfd = ch->ops->get_recv_select_fd(ch); + wfd = ch->ops->get_send_select_fd(ch); + + chp->fd_fdx = (rfd == wfd); + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d,%d)",__FUNCTION__, rfd,wfd); + } + chp->infd.fd = rfd; + chp->infd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->infd); + if (!chp->fd_fdx) { + chp->outfd.fd = wfd; + chp->outfd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->outfd); + } + chp->dispatch = NULL; + chp->description = "IPC channel(base)"; + chp->gsourceid = 0; + return chp; +} + +GCHSource* +G_main_add_IPC_Channel(int priority, IPC_Channel* ch + , gboolean can_recurse + , gboolean (*dispatch)(IPC_Channel* source_data, + gpointer user_data) + , gpointer userdata + , GDestroyNotify notify) +{ + GCHSource *chp; + GSource *source; + + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + source = g_source_new(&G_CH_SourceFuncs, + sizeof(GCHSource)); + G_main_IPC_Channel_constructor(source,ch,userdata,notify); + + chp = (GCHSource*)source; + chp->dispatch = dispatch; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, can_recurse); + + chp->gsourceid = g_source_attach(source, NULL); + chp->description = "IPC channel"; + + + if (chp->gsourceid == 0) { + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + g_source_unref(source); + source = NULL; + chp = NULL; + } + return chp; +} + + +void /* Suspend reading from far end writer (flow control) */ +G_main_IPC_Channel_pause(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = TRUE; + return; +} + + +void /* Resume reading from far end writer (un-flow-control) */ +G_main_IPC_Channel_resume(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = FALSE; + return; + +} + +/* + * Delete an IPC_channel from the gmainloop world... + */ +gboolean +G_main_del_IPC_Channel(GCHSource* chp) +{ + GSource* source = (GSource*) chp; + + if (chp == NULL || chp->gsourceid <= 0) { + return FALSE; + } + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d)",__FUNCTION__, chp->infd.fd); + } + g_source_remove(chp->gsourceid); + chp->gsourceid = 0; + /* chp should (may) now be undefined */ + g_source_unref(source); + + return TRUE; +} + +/* + * For IPC_CHANNEL events, enable output checking when needed + * and note when unread input is already queued. + * + * Note that we don't modify 'timeout' either. + */ +gboolean +G_CH_prepare_int(GSource* source, + gint* timeout) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t funstart; + gboolean ret; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->ch->ops->is_sending_blocked(chp->ch)) { + if (chp->fd_fdx) { + chp->infd.events |= OUTPUT_EVENTS; + }else{ + chp->outfd.events |= OUTPUT_EVENTS; + } + } + + if (chp->ch->recv_queue->current_qlen < chp->ch->recv_queue->max_qlen) { + chp->infd.events |= INPUT_EVENTS; + }else{ + /* + * This also disables EOF events - until we + * read some of the packets we've already gotten + * This prevents a tight loop in poll(2). + */ + chp->infd.events &= ~INPUT_EVENTS; + } + + if (chp->dontread){ + return FALSE; + } + ret = chp->ch->ops->is_message_pending(chp->ch); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Did we notice any I/O events? + */ + +gboolean +G_CH_check_int(GSource* source) +{ + + GCHSource* chp = (GCHSource*)source; + gboolean ret; + longclock_t funstart; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->dontread){ + /* Make sure output gets unblocked */ + chp->ch->ops->resume_io(chp->ch); + return FALSE; + } + + ret = (chp->infd.revents != 0 + || (!chp->fd_fdx && chp->outfd.revents != 0) + || chp->ch->ops->is_message_pending(chp->ch)); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Some kind of event occurred - notify the user. + */ +gboolean +G_CH_dispatch_int(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t dispstart; + longclock_t resume_start = zero_longclock; + + g_assert(IS_CHSOURCE(chp)); + CHECK_DISPATCH_DELAY(chp); + + + if (chp->dontread){ + return TRUE; + } + + /* Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (chp->fd_fdx) { + if (chp->infd.revents & OUTPUT_EVENTS) { + chp->infd.events &= ~OUTPUT_EVENTS; + } + }else if (chp->outfd.revents & OUTPUT_EVENTS) { + chp->outfd.events &= ~OUTPUT_EVENTS; + } + + if (ANYDEBUG) { + resume_start = time_longclock(); + } + + chp->ch->ops->resume_io(chp->ch); + + if (ANYDEBUG) { + longclock_t resume_end = time_longclock(); + unsigned long ms; + ms = longclockto_ms(sub_longclock(resume_end + , resume_start)); + if (ms > 10) { + cl_log(LOG_WARNING + , "%s: resume_io() for %s took %lu ms" + , __FUNCTION__ + , chp->description, ms); + } + } + + + if(chp->dispatch && chp->ch->ops->is_message_pending(chp->ch)) { + if(!(chp->dispatch(chp->ch, chp->udata))){ + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + CHECK_DISPATCH_TIME(chp); + g_source_unref(source); + return FALSE; + } + } + CHECK_DISPATCH_TIME(chp); + + if (chp->ch->ch_status == IPC_DISCONNECT){ + return FALSE; + } + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +void +G_CH_destroy_int(GSource* source) +{ + GCHSource* chp = (GCHSource*)source; + + g_assert(IS_CHSOURCE(chp)); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(chp=0x%lx, sock=%d) {", __FUNCTION__ + , (unsigned long)chp, chp->infd.fd); + } + + if (chp->dnotify) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling dnotify(sock=%d, arg=0x%lx) function" + , __FUNCTION__, chp->infd.fd, (unsigned long)chp->udata); + } + chp->dnotify(chp->udata); + }else{ + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: NOT calling dnotify(sock=%d) function" + , __FUNCTION__, chp->infd.fd); + } + } + if (chp->ch) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: calling IPC destroy (chp->ch=0x%lx, sock=%d)" + , __FUNCTION__ , (unsigned long)chp->ch, chp->infd.fd); + } + chp->ch->ops->destroy(chp->ch); + chp->ch = NULL; + } + /*chp->gsourceid = 0; ?*/ + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d)*/", __FUNCTION__, chp->infd.fd); + } +} + + +/************************************************************ + * Functions for IPC_WaitConnections + ***********************************************************/ +static gboolean G_WC_prepare(GSource * source, + gint* timeout); +static gboolean G_WC_check(GSource* source); +static gboolean G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_WC_destroy(GSource* source); + +static GSourceFuncs G_WC_SourceFuncs = { + G_WC_prepare, + G_WC_check, + G_WC_dispatch, + G_WC_destroy, +}; + + +/* + * Add an IPC_WaitConnection to the gmainloop world... + */ +GWCSource* +G_main_add_IPC_WaitConnection(int priority +, IPC_WaitConnection* wch +, IPC_Auth* auth_info +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* wch +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GWCSource* wcp; + GSource * source = g_source_new(&G_WC_SourceFuncs, + sizeof(GWCSource)); + + wcp = (GWCSource*)source; + + wcp->magno = MAG_GWCSOURCE; + wcp->maxdispatchdelayms = DEFAULT_MAXDELAY; + wcp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((wcp->detecttime), zero_longclock); + wcp->udata = userdata; + wcp->gpfd.fd = wch->ops->get_select_fd(wch); + wcp->gpfd.events = DEF_EVENTS; + wcp->gpfd.revents = 0; + wcp->wch = wch; + wcp->dnotify = notify; + wcp->auth_info = auth_info; + wcp->dispatch = dispatch; + + g_source_add_poll(source, &wcp->gpfd); + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + wcp->gsourceid = g_source_attach(source, NULL); + wcp->description = "IPC wait for connection"; + + if (wcp->gsourceid == 0) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + source = NULL; + wcp = NULL; + } + return wcp; +} + + +/* Delete the given IPC_WaitConnection from the gmainloop world */ +gboolean +G_main_del_IPC_WaitConnection(GWCSource* wcp) +{ + + GSource* source = (GSource*) wcp; + + + if (wcp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove(wcp->gsourceid); + wcp->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + + + +/* + * For IPC_WaitConnection events, return FALSE because we + * have to poll to get events. + * + * We don't modify 'timeout' either. + */ +static gboolean +G_WC_prepare(GSource* source, + gint* timeout) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + return FALSE; +} + +/* + * Did we notice any I/O (connection pending) events? + */ + +static gboolean +G_WC_check(GSource * source) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + + if (wcp->gpfd.revents != 0) { + lc_store((wcp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Someone is trying to connect. + * Try to accept the connection and notify the user. + */ +static gboolean +G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + GWCSource* wcp = (GWCSource*)source; + IPC_Channel* ch; + gboolean rc = TRUE; + int count = 0; + longclock_t dispstart; + + g_assert(IS_WCSOURCE(wcp)); + CHECK_DISPATCH_DELAY(wcp); + + while(1) { + ch = wcp->wch->ops->accept_connection(wcp->wch, wcp->auth_info); + if (ch == NULL) { + if (errno == EBADF) { + cl_perror("%s: Stopping accepting connections(socket=%d)!!" + , __FUNCTION__, wcp->gpfd.fd); + rc = FALSE; + } + break; + } + ++count; + + if(!wcp->dispatch) { + continue; + } + + rc = wcp->dispatch(ch, wcp->udata); + if(!rc) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + break; + } + } + CHECK_DISPATCH_TIME(wcp); + return rc; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_WC_destroy(GSource* source) +{ + + GWCSource* wcp = (GWCSource*)source; + wcp->gsourceid = 0; + g_assert(IS_WCSOURCE(wcp)); + wcp->wch->ops->destroy(wcp->wch); + if (wcp->dnotify) { + wcp->dnotify(wcp->udata); + } +} + + +/************************************************************ + * Functions for Signals + ***********************************************************/ +static gboolean G_SIG_prepare(GSource* source, + gint* timeout); +static gboolean G_SIG_check(GSource* source); + +static gboolean G_SIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_SIG_destroy(GSource* source); + +static void G_main_signal_handler(int nsig); + +static GSourceFuncs G_SIG_SourceFuncs = { + G_SIG_prepare, + G_SIG_check, + G_SIG_dispatch, + G_SIG_destroy, +}; + +static GSIGSource *G_main_signal_list[_NSIG]; + +void +set_SignalHandler_dnotify(GSIGSource* sig_src, GDestroyNotify notify) +{ + sig_src->dnotify = notify; +} + +/* + * Add an Signal to the gmainloop world... + */ +GSIGSource* +G_main_add_SignalHandler(int priority, int signal, + gboolean (*dispatch)(int nsig, gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GSIGSource* sig_src; + GSource * source = g_source_new(&G_SIG_SourceFuncs, sizeof(GSIGSource)); + gboolean failed = FALSE; + + sig_src = (GSIGSource*)source; + + sig_src->magno = MAG_GSIGSOURCE; + sig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + sig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + sig_src->signal = signal; + sig_src->dispatch = dispatch; + sig_src->udata = userdata; + sig_src->dnotify = notify; + + sig_src->signal_triggered = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(G_main_signal_list[signal] != NULL) { + cl_log(LOG_ERR + , "%s: Handler already present for signal %d" + , __FUNCTION__, signal); + failed = TRUE; + } + if(!failed) { + sig_src->gsourceid = g_source_attach(source, NULL); + sig_src->description = "signal"; + if (sig_src->gsourceid < 1) { + cl_log(LOG_ERR + , "%s: Could not attach source for signal %d (%d)" + , __FUNCTION__ + , signal, sig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR + , "%s: Signal handler for signal %d NOT added" + , __FUNCTION__, signal); + g_source_remove(sig_src->gsourceid); + g_source_unref(source); + source = NULL; + sig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Added signal handler for signal %d" + , __FUNCTION__, signal); + } + G_main_signal_list[signal] = sig_src; + CL_SIGNAL(signal, G_main_signal_handler); + /* + * If we don't set this on, then the mainloop poll(2) call + * will never be interrupted by this signal - which sort of + * defeats the whole purpose of a signal handler in a + * mainloop program + */ + cl_signal_set_interrupt(signal, TRUE); + } + return sig_src; +} + + +/* + * Delete a Signal from the gmainloop world... + */ +gboolean +G_main_del_SignalHandler(GSIGSource* sig_src) +{ + GSource* source = (GSource*) sig_src; + + if (sig_src->gsourceid <= 0) { + return FALSE; + } + if(_NSIG <= sig_src->signal) { + g_assert(_NSIG > sig_src->signal); + return FALSE; + } + + CL_SIGNAL(sig_src->signal, NULL); + + sig_src->signal_triggered = FALSE; + g_source_remove(sig_src->gsourceid); + G_main_signal_list[sig_src->signal] = NULL; + sig_src->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_SIG_prepare(GSource* source, gint* timeoutms) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + /* Don't let a timing window keep us in poll() forever + * + * The timing window in question looks like this: + * No signal has occurred up to the point of prepare being called. + * Signal comes in _after_ prepare was called, but _before_ poll. + * signal_detected gets set, but no one checks it before going into poll + * We wait in poll forever... It's not a pretty sight :-(. + */ + *timeoutms = 1000; /* Sigh... */ + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + + /* detecttime is reset in the dispatch function */ + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0) { + cl_log(LOG_ERR, "%s: detecttime already set?", __FUNCTION__); + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; /* How long since signal occurred? */ + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_SIG_check(GSource* source) +{ + + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0){ + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_SIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GSIGSource* sig_src = (GSIGSource*)source; + longclock_t dispstart; + + g_assert(IS_SIGSOURCE(sig_src)); + CHECK_DISPATCH_DELAY(sig_src); + + sig_src->sh_detecttime = 0UL; + sig_src->signal_triggered = FALSE; + + if(sig_src->dispatch) { + if(!(sig_src->dispatch(sig_src->signal, sig_src->udata))){ + G_main_del_SignalHandler(sig_src); + CHECK_DISPATCH_TIME(sig_src); + return FALSE; + } + } + CHECK_DISPATCH_TIME(sig_src); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_SIG_destroy(GSource* source) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + sig_src->gsourceid = 0; + + if (sig_src->dnotify) { + sig_src->dnotify(sig_src->udata); + } +} + +/* Find and set the correct mainloop input */ + +static void +G_main_signal_handler(int nsig) +{ + GSIGSource* sig_src = NULL; + + if(G_main_signal_list == NULL) { + g_assert(G_main_signal_list != NULL); + return; + } + if(_NSIG <= nsig) { + g_assert(_NSIG > nsig); + return; + } + + sig_src = G_main_signal_list[nsig]; + + if(sig_src == NULL) { + /* cl_log(LOG_CRIT, "No handler for signal -%d", nsig); */ + return; + } + + g_assert(IS_SIGSOURCE(sig_src)); + /* Time from first occurance of signal */ + if (!sig_src->signal_triggered) { + /* Avoid calling longclock_time() on a signal */ + sig_src->sh_detecttime=cl_times(); + } + sig_src->signal_triggered = TRUE; +} + +/* + * Functions to handle child process + */ + +#define WAITALARM 5000L /* milliseconds */ + +static int alarm_count = 0; +static void +G_main_alarm_helper(int nsig) +{ + ++alarm_count; +} + +static gboolean +child_death_dispatch(int sig, gpointer notused) +{ + int status; + pid_t pid; + const int waitflags = WNOHANG; + struct sigaction saveaction; + int childcount = 0; + + /* + * wait3(WNOHANG) isn't _supposed_ to hang + * Unfortunately, it seems to do just that on some OSes. + * + * The workaround is to set an alarm. I don't think for this purpose + * that it matters if siginterrupt(SIGALRM) is set TRUE or FALSE since + * the tiniest little excuse seems to cause the wait3() to finish. + */ + + memset(&saveaction, 0, sizeof(saveaction)); + cl_signal_set_simple_handler(SIGALRM, G_main_alarm_helper, &saveaction); + + alarm_count = 0; + cl_signal_set_interrupt(SIGALRM, TRUE); + setmsrepeattimer(WAITALARM); /* Might as well be persistent ;-) */ + while((pid=wait3(&status, waitflags, NULL)) > 0 + || (pid < 0 && errno == EINTR)) { + cancelmstimer(); + if (pid > 0) { + ++childcount; + ReportProcHasDied(pid, status); + } + setmsrepeattimer(WAITALARM); /* Let's be persistent ;-) */ + } + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + + if (pid < 0 && errno != ECHILD) { + cl_perror("%s: wait3() failed" + , __FUNCTION__); + } +#if defined(DEBUG) + if (childcount < 1) { + /* + * This happens when we receive a SIGCHLD after we clear + * 'sig_src->signal_triggered' in G_SIG_dispatch() but + * before the last wait3() call returns no child above. + */ + cl_log(LOG_DEBUG, "NOTE: %s called without children to wait on" + , __FUNCTION__); + } +#endif + if (alarm_count) { + cl_log(LOG_ERR + , "%s: wait3() call hung %d times. childcount = %d" + , __FUNCTION__, alarm_count, childcount); + alarm_count = 0; + } + return TRUE; +} + +void +set_sigchld_proctrack(int priority, unsigned long maxdisptime) +{ + GSIGSource* src = G_main_add_SignalHandler(priority, SIGCHLD + , child_death_dispatch, NULL, NULL); + + G_main_setmaxdispatchdelay((GSource*) src, 100); + G_main_setmaxdispatchtime((GSource*) src, maxdisptime); + G_main_setdescription((GSource*)src, "SIGCHLD"); + return; +} + + +/************************************************************ + * Functions for Trigger inputs + ***********************************************************/ +static gboolean G_TRIG_prepare(GSource* source, + gint* timeout); +static gboolean G_TRIG_check(GSource* source); + +static gboolean G_TRIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_TRIG_destroy(GSource* source); + +static GSourceFuncs G_TRIG_SourceFuncs = { + G_TRIG_prepare, + G_TRIG_check, + G_TRIG_dispatch, + G_TRIG_destroy +}; + +void +set_TriggerHandler_dnotify(GTRIGSource* trig_src, GDestroyNotify notify) +{ + trig_src->dnotify = notify; +} + +/* + * Add an Trigger to the gmainloop world... + */ +GTRIGSource* +G_main_add_TriggerHandler(int priority, + gboolean (*dispatch)(gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GTRIGSource* trig_src = NULL; + GSource * source = g_source_new(&G_TRIG_SourceFuncs, sizeof(GTRIGSource)); + gboolean failed = FALSE; + + trig_src = (GTRIGSource*)source; + + trig_src->magno = MAG_GTRIGSOURCE; + trig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + trig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + trig_src->dispatch = dispatch; + trig_src->udata = userdata; + trig_src->dnotify = notify; + lc_store((trig_src->detecttime), zero_longclock); + + trig_src->manual_trigger = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(!failed) { + trig_src->gsourceid = g_source_attach(source, NULL); + trig_src->description = "trigger"; + if (trig_src->gsourceid < 1) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Could not attach new source (%d)", + trig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Trigger handler NOT added"); + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + source = NULL; + trig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "G_main_add_TriggerHandler: Added signal manual handler"); + } + } + + return trig_src; +} + +void +G_main_set_trigger(GTRIGSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + trig_src->manual_trigger = TRUE; + lc_store((trig_src->detecttime), time_longclock()); +} + + +/* + * Delete a Trigger from the gmainloop world... + */ +gboolean +G_main_del_TriggerHandler(GTRIGSource* trig_src) +{ + GSource* source = (GSource*) trig_src; + + if (trig_src->gsourceid <= 0) { + return FALSE; + } + trig_src->gsourceid = 0; + trig_src->manual_trigger = FALSE; + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_TRIG_prepare(GSource* source, gint* timeout) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_TRIG_check(GSource* source) +{ + + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_TRIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + longclock_t dispstart; + + g_assert(IS_TRIGSOURCE(trig_src)); + CHECK_DISPATCH_DELAY(trig_src); + + trig_src->manual_trigger = FALSE; + + if(trig_src->dispatch) { + if(!(trig_src->dispatch(trig_src->udata))){ + G_main_del_TriggerHandler(trig_src); + CHECK_DISPATCH_TIME(trig_src); + return FALSE; + } + CHECK_DISPATCH_TIME(trig_src); + } + lc_store((trig_src->detecttime), zero_longclock); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_TRIG_destroy(GSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + trig_src->gsourceid = 0; + + if (trig_src->dnotify) { + trig_src->dnotify(trig_src->udata); + } +} +/* + * Glib mainloop timeout handling code. + * + * These functions work correctly even if someone resets the + * time-of-day clock. The g_main_timeout_add() function does not have + * this property, since it relies on gettimeofday(). + * + * Our functions have the same semantics - except they always work ;-) + * + * This is because we use longclock_t for our time values. + * + */ + + +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout); + +static gboolean +Gmain_timeout_check(GSource* src); + +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data); + +static GSourceFuncs Gmain_timeout_funcs = { + Gmain_timeout_prepare, + Gmain_timeout_check, + Gmain_timeout_dispatch, +}; + + +struct GTimeoutAppend { + COMMON_STRUCTSTART; + longclock_t nexttime; + guint interval; +}; + +#define GTIMEOUT(GS) ((struct GTimeoutAppend*)((void*)(GS))) + +guint +Gmain_timeout_add(guint interval +, GSourceFunc function +, gpointer data) +{ + return Gmain_timeout_add_full(G_PRIORITY_DEFAULT + , interval, function, data, NULL); +} + +guint +Gmain_timeout_add_full(gint priority +, guint interval +, GSourceFunc function +, gpointer data +, GDestroyNotify notify) +{ + + struct GTimeoutAppend* append; + + GSource* source = g_source_new( &Gmain_timeout_funcs, + sizeof(struct GTimeoutAppend)); + + append = GTIMEOUT(source); + append->magno = MAG_GTIMEOUTSRC; + append->maxdispatchms = DEFAULT_MAXDISPATCH; + append->maxdispatchdelayms = DEFAULT_MAXDELAY; + append->description = "(timeout)"; + lc_store((append->detecttime), zero_longclock); + append->udata = NULL; + + append->nexttime = add_longclock(time_longclock() + , msto_longclock(interval)); + append->interval = interval; + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, FALSE); + + g_source_set_callback(source, function, data, notify); + + append->gsourceid = g_source_attach(source, NULL); + g_source_unref(source); + return append->gsourceid; + +} + +void +Gmain_timeout_remove(guint tag) +{ + GSource* source = g_main_context_find_source_by_id(NULL,tag); + struct GTimeoutAppend* append = GTIMEOUT(source); + + if (source == NULL){ + cl_log(LOG_ERR, "Attempt to remove timeout (%u)" + " with NULL source", tag); + }else{ + g_assert(IS_TIMEOUTSRC(append)); + g_source_remove(tag); + } + + return; +} + +/* g_main_loop-style prepare function */ +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout) +{ + + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + longclock_t remain; + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + *timeout = 0L; + return TRUE; + } + /* This is safe - we will always have a positive result */ + remain = sub_longclock(append->nexttime, lnow); + /* This is also safe - we started out in 'ms' */ + *timeout = longclockto_ms(remain); + return ((*timeout) == 0); +} + +/* g_main_loop-style check function */ +static gboolean +Gmain_timeout_check (GSource* src) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + return TRUE; + } + return FALSE; +} + +/* g_main_loop-style dispatch function */ +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t dispstart; + gboolean ret; + + g_assert(IS_TIMEOUTSRC(append)); + lc_store(append->detecttime, append->nexttime); + CHECK_DISPATCH_DELAY(append); + + + /* Schedule our next dispatch */ + append->nexttime = add_longclock(time_longclock() + , msto_longclock(append->interval)); + + /* Then call the user function */ + ret = func(user_data); + + CHECK_DISPATCH_TIME(append); + return ret; +} +void +G_main_setmaxdispatchdelay(GSource* s, unsigned long delayms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch delay on wrong object"); + return; + } + fdp->maxdispatchdelayms = delayms; +} +void +G_main_setmaxdispatchtime(GSource* s, unsigned long dispatchms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->maxdispatchms = dispatchms; +} +void +G_main_setdescription(GSource* s, const char * description) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->description = description; +} +void +G_main_setmaxdispatchdelay_id(guint id, unsigned long delayms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchdelay(source, delayms); + } +} +void +G_main_setmaxdispatchtime_id(guint id, unsigned long dispatchms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchtime(source, dispatchms); + } +} +void +G_main_setdescription_id(guint id, const char * description) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setdescription(source, description); + } +} +void +G_main_setall_id(guint id, const char * description, unsigned long delay +, unsigned long elapsed) +{ + G_main_setdescription_id(id, description); + G_main_setmaxdispatchdelay_id(id, delay); + G_main_setmaxdispatchtime_id(id, elapsed); +} + +static void TempProcessRegistered(ProcTrack* p); +static void TempProcessDied(ProcTrack* p, int status, int signo +, int exitcode, int waslogged); +static const char* TempProcessName(ProcTrack* p); + +/*********************************************************************** + * Track our temporary child processes... + * + * We run no more than one of each type at once. + * If we need to run some and one is still running we run another one + * when this one exits. + * + * Requests to run a child process don't add up. So, 3 requests to run + * a child while one is running only cause it to be run once more, not + * three times. + * + * The only guarantee is that a new child process will run after a request + * was made. + * + * To create the possibility of running a particular type of child process + * call G_main_add_tempproc_trigger(). + * + * To cause it to be run, call G_main_set_trigger(). + * + ***********************************************************************/ + +static ProcTrack_ops TempProcessTrackOps = { + TempProcessDied, + TempProcessRegistered, + TempProcessName +}; + +/* + * Information for tracking our generic temporary child processes. + */ +struct tempproc_track { + const char * procname; /* name of the process*/ + GTRIGSource* trigger; /* Trigger for this event */ + int (*fun)(gpointer userdata); /* Function to call + * in child process */ + void (*prefork)(gpointer userdata);/* Call before fork */ + void (*postfork)(gpointer userdata);/* Call after fork */ + void (*complete)(gpointer userdata, int status, int signo, int exitcode);/* Call after complete */ + gpointer userdata; /* Info to pass 'fun' */ + gboolean isrunning; /* TRUE if child is running */ + gboolean runagain; /* TRUE if we need to run + * again after child process + * finishes. + */ +}; +static void +TempProcessRegistered(ProcTrack* p) +{ + return; /* Don't need to do much here... */ +} + +static void +TempProcessDied(ProcTrack* p, int status, int signo, int exitcode +, int waslogged) +{ + struct tempproc_track * pt = p->privatedata; + + if (pt->complete) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling 'complete' for temp process %s" + , __FUNCTION__, pt->procname); + } + pt->complete(pt->userdata, status, signo, exitcode); + } + + pt->isrunning=FALSE; + if (pt->runagain) { + pt->runagain=FALSE; + + /* Do it again, Sam! */ + G_main_set_trigger(pt->trigger); + + /* Note that we set the trigger for this, we don't + * fork or call the function now. + * + * This allows the mainloop scheduler to decide + * when the fork should happen according to the priority + * of this trigger event - NOT according to the priority + * of general SIGCHLD handling. + */ + } + p->privatedata = NULL; /* Don't free until trigger is destroyed */ + return; +} + +static const char * +TempProcessName(ProcTrack* p) +{ + struct tempproc_track * pt = p->privatedata; + return pt->procname; +} +/* + * Make sure only one copy is running at a time... + */ +static gboolean +TempProcessTrigger(gpointer ginfo) +{ + struct tempproc_track* info = ginfo; + int pid; + + /* Make sure only one copy is running at a time. */ + /* This avoids concurrency problems. */ + if (info->isrunning) { + info->runagain = TRUE; + return TRUE; + } + info->isrunning = TRUE; + + if (info->prefork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling prefork for temp process %s" + , __FUNCTION__, info->procname); + } + info->prefork(info->userdata); + } + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "Forking temp process %s", info->procname); + } + switch ((pid=fork())) { + int rc; + case -1: cl_perror("%s: Can't fork temporary child" + " process [%s]!", __FUNCTION__ + , info->procname); + info->isrunning = FALSE; + break; + + case 0: /* Child */ + if ((rc=info->fun(info->userdata)) == HA_OK) { + exit(0); + } + cl_log(LOG_WARNING + , "%s: %s returns %d", __FUNCTION__ + , info->procname, rc); + exit(1); + break; + default: + /* Fall out */; + + } + if (pid > 0) { + NewTrackedProc(pid, 0, (ANYDEBUG? PT_LOGVERBOSE : PT_LOGNORMAL) + , ginfo, &TempProcessTrackOps); + if (info->postfork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling postfork for temp process %s" + , __FUNCTION__, info->procname); + } + info->postfork(info->userdata); + } + } + return TRUE; +} + +static void +tempproc_destroy_notify(gpointer userdata) +{ + if (userdata != NULL) { + free(userdata); + userdata = NULL; + } +} + +GTRIGSource* +G_main_add_tempproc_trigger(int priority +, int (*triggerfun) (gpointer p) +, const char * procname +, gpointer userdata +, void (*prefork)(gpointer p) +, void (*postfork)(gpointer p) +, void (*complete)(gpointer userdata, int status, int signo, int exitcode)) +{ + + struct tempproc_track* p; + GTRIGSource* ret; + + p = (struct tempproc_track *) malloc(sizeof(struct tempproc_track)); + if (p == NULL) { + return NULL; + } + + memset(p, 0, sizeof(*p)); + p->procname = procname; + p->fun = triggerfun; + p->userdata = userdata; + p->prefork = prefork; + p->postfork = postfork; + p->complete = complete; + + ret = G_main_add_TriggerHandler(priority + , TempProcessTrigger, p, tempproc_destroy_notify); + + if (ret == NULL) { + free(p); + p = NULL; + }else{ + p->trigger = ret; + } + return ret; +} diff --git a/lib/clplumbing/Makefile.am b/lib/clplumbing/Makefile.am new file mode 100644 index 0000000..1b504fc --- /dev/null +++ b/lib/clplumbing/Makefile.am @@ -0,0 +1,99 @@ +# +# plumbing: OCF general plumbing libraries +# +# Copyright (C) 2002 Alan Robertson, International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + + +halibdir = $(libdir)/@HB_PKG@ + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## libraries + +lib_LTLIBRARIES = libplumb.la libplumbgpl.la + +libplumb_la_SOURCES = \ + base64.c \ + cl_compress.c \ + cl_log.c \ + cl_misc.c \ + cl_msg.c \ + cl_msg_types.c \ + cl_netstring.c \ + cl_pidfile.c \ + cl_poll.c \ + cl_random.c \ + cl_signal.c \ + cl_syslog.c \ + cl_uuid.c \ + cl_plugin.c \ + cl_reboot.c \ + coredumps.c \ + cpulimits.c \ + GSource.c \ + ipcsocket.c \ + longclock.c \ + md5.c \ + mkstemp_mode.c \ + ocf_ipc.c \ + proctrack.c \ + realtime.c \ + replytrack.c \ + timers.c \ + uids.c + +libplumb_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumb_la_LDFLAGS = -version-info 3:0:1 + +libplumbgpl_la_SOURCES = setproctitle.c +libplumbgpl_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumbgpl_la_LDFLAGS = -version-info 2:0:0 + +testdir = $(libdir)/@HB_PKG@ +test_PROGRAMS = ipctest ipctransientclient ipctransientserver base64_md5_test +test_SCRIPTS = transient-test.sh + +ipctest_SOURCES = ipctest.c +ipctest_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +noinst_HEADERS = ipctransient.h + +#ipctransient_SOURCES = ipctransient.c +#ipctransient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(top_builddir)/heartbeat/libhbclient.la $(GLIBLIB) + +ipctransientclient_SOURCES = ipctransientclient.c ipctransientlib.c +ipctransientclient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +ipctransientserver_SOURCES = ipctransientserver.c ipctransientlib.c +ipctransientserver_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +#netstring_test_SOURCES = netstring_test.c +#netstring_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la libhbclient.la $(gliblib) + +base64_md5_test_SOURCES = base64_md5_test.c +base64_md5_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +EXTRA_DIST = $(test_SCRIPTS) diff --git a/lib/clplumbing/base64.c b/lib/clplumbing/base64.c new file mode 100644 index 0000000..c8ad325 --- /dev/null +++ b/lib/clplumbing/base64.c @@ -0,0 +1,422 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <syslog.h> +#include <string.h> +#include <stdlib.h> +#include "clplumbing/base64.h" + +/* + * + * Base64 conversion functions. + * They convert from a binary array into a single string + * in base 64. This is almost (but not quite) like section 5.2 of RFC 1341 + * The only difference is that we don't care about line lengths. + * We do use their encoding algorithm. + * + */ + + +static char b64chars[] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define EQUALS '=' +#define MASK6 (077) +#define MASK24 (077777777) + + +/* Convert from binary to a base64 string (~ according to RFC1341) */ +int +binary_to_base64(const void * data, int nbytes, char * output, int outlen) +{ + int requiredlen = B64_stringlen(nbytes)+1; /* EOS */ + char * outptr; + const unsigned char * inmax; + const unsigned char * inlast; + const unsigned char * inptr; + int bytesleft; + + if (outlen < requiredlen) { + syslog(LOG_ERR, "binary_to_base64: output area too small."); + return -1; + } + + inptr = data; + /* Location of last whole 3-byte chunk */ + inmax = inptr + ((nbytes / B64inunit)*B64inunit); + inlast = inptr + nbytes; + outptr = output; + + + /* Convert whole 3-byte chunks */ + for (;inptr < inmax; inptr += B64inunit) { + unsigned long chunk; + unsigned int sixbits; + + chunk = ((*inptr) << 16 + | ((*(inptr+1)) << 8) + | (*(inptr+2))) & MASK24; + + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk & MASK6); + *outptr = b64chars[sixbits]; ++outptr; + } + + /* Do we have anything left over? */ + + bytesleft = inlast - inptr; + if (bytesleft > 0) { + /* bytesleft can only be 1 or 2 */ + + unsigned long chunk; + unsigned int sixbits; + + + /* Grab first byte */ + chunk = (*inptr) << 16; + + if (bytesleft == 2) { + /* Grab second byte */ + chunk |= ((*(inptr+1)) << 8); + } + chunk &= MASK24; + + /* OK, now we have our chunk... */ + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + if (bytesleft == 2) { + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; + }else{ + *outptr = EQUALS; + } + ++outptr; + + *outptr = EQUALS; ++outptr; + } + *outptr = EOS; /* Don't increment */ + return (outptr - output); +} + + +/* This macro is only usable by the base64_to_binary() function. + * + * There are faster ways of doing this, but I didn't spend the time + * to implement one of them. If we have an array of six bit values, + * sized by 256 or so, then we could look it up. + * FIXME: This is how it really ought to be done... + */ + +static unsigned char b64values [256]; +#define BADVALUE 0xff + +static void +init_b64_values(void) +{ + int j; + memset(b64values, BADVALUE, sizeof(b64values)); + + for (j=0; b64chars[j] != EOS; ++j) { + b64values[(int)b64chars[j]] = (unsigned char)j; + } +} + + +#define Char2SixBits(in, out) { \ + unsigned char ch; \ + ch = b64values[(unsigned int)in]; \ + if (ch == BADVALUE) { \ + syslog(LOG_ERR \ + , "base64_to_binary: invalid input [%c]!" \ + , in); \ + return -1; \ + } \ + out = ch; \ + } \ + + +/* Convert from a base64 string (~ according to RFC1341) to binary */ +int +base64_to_binary(const char * in, int inlen, void * output, int outlen) +{ + int maxbinlen = B64_maxbytelen(inlen); /* Worst case size */ + const char * input = in; + const char * lastinput = in + inlen - B64outunit; + int equalcount = 0; + unsigned char * startout; + unsigned char * out; + unsigned sixbits1; + unsigned sixbits2; + unsigned sixbits3; + unsigned sixbits4; + unsigned long chunk; + static int inityet = 0; + + if (!inityet) { + inityet=1; + init_b64_values(); + } + + /* Make sure we have enough room */ + if (outlen < maxbinlen) { + int residue = maxbinlen - outlen; + + if (residue > 2 + || input[inlen-1] != EQUALS + || (residue == 2 && input[inlen-2] != EQUALS)) { + syslog(LOG_ERR + , "base64_to_binary: output area too small."); + return -1; + } + } + if ((inlen % 4) != 0) { + syslog(LOG_ERR + , "base64_to_binary: input length invalid."); + return -1; + } + + if (inlen == 0) { + return 0; + } + + /* We have enough space. We are happy :-) */ + + startout = out = (unsigned char *)output; + + while (input < lastinput) { + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + Char2SixBits(*input, sixbits3); ++input; + Char2SixBits(*input, sixbits4); ++input; + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + *out = ((chunk >> 16) & 0xff); ++out; + *out = ((chunk >> 8) & 0xff); ++out; + *out = (chunk & 0xff); ++out; + } + + /* Process last 4 chars of input (1 to 3 bytes of output) */ + + /* The first two input chars must be normal chars */ + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + + /* We should find one of: (char,char) (char,=) or (=,=) */ + /* We then output: (3 bytes) (2 bytes) (1 byte) */ + + if (*input == EQUALS) { + /* The (=,=): 1 byte case */ + equalcount=2; + sixbits3 = 0; + sixbits4 = 0; + /* We assume the 2nd char is an = sign :-) */ + }else{ + /* We have either the (char,char) or (char,=) case */ + Char2SixBits(*input, sixbits3); ++input; + if (*input == EQUALS) { + /* The (char, =): 2 bytes case */ + equalcount=1; + sixbits4 = 0; + }else{ + /* The (char, char): 3 bytes case */ + Char2SixBits(*input, sixbits4); ++input; + equalcount=0; + } + } + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + /* We always have one more char to output... */ + *out = ((chunk >> 16) & 0xff); ++out; + + if (equalcount < 2) { + /* Zero or one equal signs: total of 2 or 3 bytes output */ + *out = ((chunk >> 8) & 0xff); ++out; + + if (equalcount < 1) { + /* No equal signs: total of 3 bytes output */ + *out = (chunk & 0xff); ++out; + } + } + + return out - startout; +} + +#if 0 +#define RAND(upb) (rand()%(upb)) + +void dumpbin(void * Bin, int length); +void randbin(void * Bin, int length); + +void +dumpbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + + int j; + + for (j=0; j < length; ++j) { + fprintf(stderr, "%02x ", bin[j]); + if ((j % 32) == 31) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\n"); +} + +void +randbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + int j; + + for (j=0; j < length; ++j) { + bin[j] = (unsigned char)RAND(256); + } + +} + +#define MAXLEN 320 +#define MAXSTRING B64_stringlen(MAXLEN)+1 +#define MAXITER 300000 +int +main(int argc, char ** argv) +{ + int errcount = 0; + char origbin[MAXLEN+1]; + char sourcebin[MAXLEN+1]; + char destbin[MAXLEN+1]; + char deststr[MAXSTRING]; + int maxiter = MAXITER; + int j; + + for (j=0; j < maxiter; ++j) { + int iterlen = RAND(MAXLEN+1); + int slen; + int blen; + + if ((j%100) == 99) { + fprintf(stderr, "+"); + } + + memset(origbin, 0, MAXLEN+1); + memset(sourcebin, 0, MAXLEN+1); + memset(destbin, 0, MAXLEN+1); + randbin(origbin, iterlen); + origbin[iterlen] = 0x1; + memcpy(sourcebin, origbin, iterlen); + sourcebin[iterlen] = 0x2; + slen = binary_to_base64(sourcebin, iterlen, deststr, MAXSTRING); + if (slen < 0) { + fprintf(stderr + , "binary_to_base64 failure: length %d\n" + , iterlen); + ++errcount; + continue; + } + if (strlen(deststr) != slen) { + fprintf(stderr + , "binary_to_base64 failure: length was %d (strlen) vs %d (ret value)\n" + , strlen(deststr), slen); + fprintf(stderr, "binlen: %d, deststr: [%s]\n" + , iterlen, deststr); + continue; + ++errcount; + } + destbin[iterlen] = 0x3; + blen = base64_to_binary(deststr, slen, destbin, iterlen); + + if (blen != iterlen) { + fprintf(stderr + , "base64_to_binary failure: length was %d vs %d\n" + , blen, iterlen); + dumpbin(origbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + continue; + } + if (memcmp(destbin, origbin, iterlen) != 0) { + fprintf(stderr + , "base64_to_binary mismatch. Orig:\n"); + dumpbin(origbin, iterlen); + fprintf(stderr, "Dest:\n"); + dumpbin(destbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + } + if (destbin[iterlen] != 0x3) { + fprintf(stderr + , "base64_to_binary corruption. dest byte: 0x%02x\n" + , destbin[iterlen]); + ++errcount; + } + + if (sourcebin[iterlen] != 0x2) { + fprintf(stderr + , "base64_to_binary corruption. source byte: 0x%02x\n" + , sourcebin[iterlen]); + ++errcount; + } + sourcebin[iterlen] = 0x0; + origbin[iterlen] = 0x0; + if (memcmp(sourcebin, origbin, MAXLEN+1) != 0) { + fprintf(stderr + , "base64_to_binary corruption. origbin:\n"); + dumpbin(origbin, MAXLEN+1); + fprintf(stderr, "sourcebin:\n"); + dumpbin(sourcebin, MAXLEN+1); + ++errcount; + } + + } + + fprintf(stderr, "\n%d iterations, %d errors.\n" + , maxiter, errcount); + + return errcount; +} +/* HA-logging function */ +void +ha_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + +} +#endif diff --git a/lib/clplumbing/base64_md5_test.c b/lib/clplumbing/base64_md5_test.c new file mode 100644 index 0000000..d536776 --- /dev/null +++ b/lib/clplumbing/base64_md5_test.c @@ -0,0 +1,113 @@ +/* File: base64_md5_test.c + * Description: base64 and md5 algorithm tests + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2005 International Business Machines + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 <stdio.h> +#include <string.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/base64.h> +#include <clplumbing/md5.h> + +#define MD5LEN 16 /* md5 buffer */ +#define BASE64_BUF_LEN 32 + +/* gcc -o base64_md5_test base64_md5_test.c -lplumb */ +int main(void) +{ + int error_count = 0; + + const char base64_encode[] = "YWJjZGVmZ2g="; + const char raw_data[] = "abcdefgh"; + + /* A test case from + * RFC 1321 - The MD5 Message-Digest Algorithm + */ + const char * data1 = "message digest"; + const char digest_rfc1321[(MD5LEN+1)*2+1] + = "f96b697d7cb7938d525a2f31aaf161d0"; + + /* A test case from + * RFC 2104 - HMAC: Keyed-Hashing for Message Authentication + */ + const char *key = "Jefe"; + const char *data2 = "what do ya want for nothing?"; + const char digest_rfc2104[(MD5LEN+1)*2+1] + = "750c783e6ab0b503eaa86e310a5db738"; + + char buffer_tmp[BASE64_BUF_LEN]; + + char md[(MD5LEN+1)*2+1]; + unsigned char digest[MD5LEN]; + char * md_tmp; + int rc,i; + + /* base64 encode test */ + binary_to_base64(raw_data, strlen(raw_data), buffer_tmp + , BASE64_BUF_LEN); + /* printf("base64_encode = %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, base64_encode, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "binary_to_base64 works bad."); + error_count++; + } + + /* base64 decode test */ + memset(buffer_tmp, 0, BASE64_BUF_LEN); + base64_to_binary(base64_encode, strlen(base64_encode) + , buffer_tmp, BASE64_BUF_LEN); + /* printf("decoded data= %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, raw_data, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "base64_to_binary works bad."); + error_count++; + } + + rc = MD5((const unsigned char *)data1, strlen(data1), digest); + + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + snprintf(md_tmp, sizeof(md), "%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d MD5=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc1321, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc1321 algorithm works bad."); + error_count++; + } + + rc = HMAC((const unsigned char *)key, strlen(key) + , (const unsigned char *)data2, strlen(data2), digest); + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + sprintf(md_tmp,"%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d HMAC=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc2104, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc2104 algorithm works bad."); + error_count++; + } + + (void) rc; /* Suppress -Werror=unused-but-set-variable */ + return error_count; +} diff --git a/lib/clplumbing/cl_compress.c b/lib/clplumbing/cl_compress.c new file mode 100644 index 0000000..6b56ad6 --- /dev/null +++ b/lib/clplumbing/cl_compress.c @@ -0,0 +1,500 @@ + +/* + * compress.c: Compression functions for Linux-HA + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Compression is designed to handle big messages, right now with 4 nodes + * cib message can go up to 64 KB or more. I expect much larger messages + * when the number of node increase. This makes message compression necessary. + * + * + * Compression is handled in field level. One can add a struct field using + * ha_msg_addstruct() -- the field will not get compressed, or using + * ha_msg_addstruct_compress(), and the field will get compressed when + * the message is converted to wire format, i.e. when msg2wirefmt() is called. + * The compressed field will stay compressed until it reached the desination. + * It will finally decompressed when the user start to get the field value. + * It is designed this way so that the compression/decompression only happens + * in end users so that heartbeat itself can save cpu cycle and memory. + * (more info about compression can be found in cl_msg_types.c about FT_COMPRESS + * FT_UNCOMPRESS types) + * + * compression has another legacy mode, which is there so it can be compatible + * to old ways of compression. In the old way, no field is compressed individually + * and the messages is compressed before it is sent out, and it will be decompressed + * in the receiver side immediately. So in each IPC channel, the message is compressed + * and decompressed once. This way will cost a lot of cpu time and memory and it is + * discouraged. + * + * If use_traditional_compression is true, then it is using the legacy mode, otherwise + * it is using the new compression. For back compatibility, the default is legacy mode. + * + * The real compression work is done by compression plugins. There are two plugins right + * now: zlib and bz2, they are in lib/plugins/compress + * + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <unistd.h> +#include <assert.h> +#include <glib.h> +#include <compress.h> +#include <ha_msg.h> +#include <clplumbing/netstring.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +#define COMPRESSED_FIELD "_compressed_payload" +#define COMPRESS_NAME "_compression_algorithm" +#define HACOMPRESSNAME "HA_COMPRESSION" +#define DFLT_COMPRESS_PLUGIN "bz2" + +static struct hb_compress_fns* msg_compress_fns = NULL; +static char* compress_name = NULL; +GHashTable* CompressFuncs = NULL; + +static PILGenericIfMgmtRqst Reqs[] = + { + {"compress", &CompressFuncs, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static PILPluginUniv* CompressPIsys = NULL; + +static int +init_pluginsys(void){ + + if (CompressPIsys) { + return TRUE; + } + + CompressPIsys = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (CompressPIsys) { + if (PILLoadPlugin(CompressPIsys, PI_IFMANAGER, "generic", Reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(CompressPIsys); + CompressPIsys = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return CompressPIsys != NULL; + +} + +int +cl_compress_remove_plugin(const char* pluginname) +{ + return HA_OK; +} + +int +cl_compress_load_plugin(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (!init_pluginsys()){ + return HA_FAIL; + } + + if ((funcs = g_hash_table_lookup(CompressFuncs, pluginname)) + == NULL){ + if (PILPluginExists(CompressPIsys, HB_COMPRESS_TYPE_S, + pluginname) == PIL_OK){ + PIL_rc rc; + if ((rc = PILLoadPlugin(CompressPIsys, + HB_COMPRESS_TYPE_S, + pluginname, + NULL))!= PIL_OK){ + cl_log(LOG_ERR, + "Cannot load compress plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return HA_FAIL; + } + funcs = g_hash_table_lookup(CompressFuncs, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "Compression module(%s) not found", pluginname); + return HA_FAIL; + } + + /* set the environment variable so that later programs can + * load the appropriate plugin + */ + setenv(HACOMPRESSNAME,pluginname,1); + msg_compress_fns = funcs; + + return HA_OK; +} + +int +cl_set_compress_fns(const char* pluginname) +{ + /* this function was unnecessary duplication of the + * code in cl_compress_load_plugin + */ + return cl_compress_load_plugin(pluginname); +} + +struct hb_compress_fns* +cl_get_compress_fns(void) +{ + static int try_dflt = 1; + + if (try_dflt && !msg_compress_fns) { + try_dflt = 0; + cl_log(LOG_INFO, "%s: user didn't set compression type, " + "loading %s plugin", + __FUNCTION__, DFLT_COMPRESS_PLUGIN); + cl_compress_load_plugin(DFLT_COMPRESS_PLUGIN); + } + return msg_compress_fns; +} + +static struct hb_compress_fns* +get_compress_fns(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (cl_compress_load_plugin(pluginname) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module" + "(%s) failed", + __FUNCTION__, pluginname); + return NULL; + } + + funcs = g_hash_table_lookup(CompressFuncs, pluginname); + return funcs; +} + +void cl_realtime_malloc_check(void); + +char* +cl_compressmsg(struct ha_msg* m, size_t* len) +{ + char* src; + char* dest; + size_t destlen; + int rc; + char* ret = NULL; + struct ha_msg* tmpmsg; + size_t datalen; + + destlen = MAXMSG; + + dest = malloc(destlen); + if (!dest) { + cl_log(LOG_ERR, "%s: failed to allocate destination buffer", + __FUNCTION__); + return NULL; + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + goto out; + } + if ( get_netstringlen(m) > MAXUNCOMPRESSED + || get_stringlen(m) > MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg too big(stringlen=%d," + "netstringlen=%d)", + __FUNCTION__, + get_stringlen(m), + get_netstringlen(m)); + goto out; + } + + + if ((src = msg2wirefmt_noac(m, &datalen)) == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + goto out; + } + + rc = msg_compress_fns->compress(dest, &destlen, + src, datalen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + goto out; + } + + free(src); + + tmpmsg =ha_msg_new(0); + rc = ha_msg_addbin(tmpmsg, COMPRESSED_FIELD, dest, destlen)/*discouraged function*/; + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding binary to msg failed", + __FUNCTION__); + goto out; + } + + rc = ha_msg_add(tmpmsg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + goto out; + } + + + ret = msg2netstring(tmpmsg, len); + ha_msg_del(tmpmsg); + +#if 0 + cl_log(LOG_INFO, "------original stringlen=%d, netstringlen=%d," + "compressed_datalen=%d,current len=%d", + get_stringlen(m), get_netstringlen(m),(int)destlen, (int)*len); + +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +gboolean +is_compressed_msg(struct ha_msg* m) +{ + if( cl_get_binary(m, COMPRESSED_FIELD, NULL) /*discouraged function*/ + != NULL){ + return TRUE; + } + + return FALSE; + +} + +/* the decompressmsg function is not exactly the reverse + * operation of compressmsg, it starts when the prorgram + * detects there is compressed_field in a msg + */ + +struct ha_msg* +cl_decompressmsg(struct ha_msg* m) +{ + const char* src; + size_t srclen; + char *dest = NULL; + size_t destlen = MAXUNCOMPRESSED; + int rc; + struct ha_msg* ret = NULL; + const char* decompress_name; + struct hb_compress_fns* funcs = NULL; + + dest = malloc(destlen); + + if (!dest) { + cl_log(LOG_ERR, "%s: Failed to allocate buffer.", __FUNCTION__); + return NULL; + } + + if (m == NULL){ + cl_log(LOG_ERR, "%s: NULL message", __FUNCTION__); + goto out; + } + src = cl_get_binary(m, COMPRESSED_FIELD, &srclen)/*discouraged function*/; + if (src == NULL){ + cl_log(LOG_ERR, "%s: compressed-field is NULL", + __FUNCTION__); + goto out; + } + + if (srclen > MAXMSG){ + cl_log(LOG_ERR, "%s: field too long(%d)", + __FUNCTION__, (int)srclen); + goto out; + } + + decompress_name = ha_msg_value(m, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + goto out; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + goto out; + } + + rc = funcs->decompress(dest, &destlen, src, srclen); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + goto out; + } + + ret = wirefmt2msg(dest, destlen, 0); + +#if 0 + cl_log(LOG_INFO, "%s: srclen =%d, destlen=%d", + __FUNCTION__, + srclen, destlen); +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +int +cl_decompress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* value; + int vallen; + int rc; + const char* decompress_name; + struct hb_compress_fns* funcs; + + if ( msg == NULL|| index >= msg->nfields){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + value = msg->values[index]; + vallen = msg->vlens[index]; + + decompress_name = ha_msg_value(msg, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + return HA_FAIL; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + return HA_FAIL; + } + + rc = funcs->decompress(buf, buflen, value, vallen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + return HA_OK; +} + + +int +cl_compress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* src; + size_t srclen; + int rc; + + if ( msg == NULL|| index >= msg->nfields + || msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + if (msg_compress_fns == NULL){ + if (compress_name == NULL){ + compress_name = getenv(HACOMPRESSNAME); + } + + if (compress_name == NULL){ + cl_log(LOG_ERR, "%s: no compression module name found", + __FUNCTION__); + return HA_FAIL; + } + + if(cl_set_compress_fns(compress_name) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module failed", + __FUNCTION__); + return HA_FAIL; + } + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + return HA_FAIL; + } + + src = msg2wirefmt_noac(msg->values[index], &srclen); + if (src == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + return HA_FAIL; + } + + rc = msg_compress_fns->compress(buf, buflen, + src, srclen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + + rc = ha_msg_mod(msg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + return HA_FAIL;; + } + + free(src); + src = NULL; + + return HA_OK; + +} diff --git a/lib/clplumbing/cl_log.c b/lib/clplumbing/cl_log.c new file mode 100644 index 0000000..213e760 --- /dev/null +++ b/lib/clplumbing/cl_log.c @@ -0,0 +1,1261 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> +#include <time.h> +#include <sys/utsname.h> +#include <clplumbing/ipc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/loggingdaemon.h> +#include <clplumbing/longclock.h> +#include <clplumbing/uids.h> +#include <glib.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <clplumbing/GSource.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_syslog.h> +#include <ha_msg.h> + +#ifndef MAXLINE +# define MAXLINE 512 +#endif +/* + * <syslog.h> might not contain LOG_PRI... + * So, we define it ourselves, or error out if we can't... + */ + +#ifndef LOG_PRI +# ifdef LOG_PRIMASK + /* David Lee <T.D.Lee@durham.ac.uk> reports this works on Solaris */ +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +# else +# error "Syslog.h does not define either LOG_PRI or LOG_PRIMASK." +# endif +#endif + +#define DFLT_ENTITY "cluster" +#define DFLT_PREFIX "" +#define NULLTIME 0 +#define QUEUE_SATURATION_FUZZ 10 + +static IPC_Channel* logging_daemon_chan = NULL; +static gboolean syslogformatfile = TRUE; +/* + * If true, then output messages more or less like this... + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); + +static int LogToLoggingDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +static IPC_Message* ChildLogIPCMessage(int priority, const char *buf, int bstrlen, + gboolean use_priority_str, IPC_Channel* ch); +static void FreeChildLogIPCMessage(IPC_Message* msg); +static gboolean send_dropped_message(gboolean use_pri_str, IPC_Channel *chan); +static int cl_set_logging_wqueue_maxlen(int qlen); + +static int use_logging_daemon = FALSE; +static int conn_logd_time = 0; +static char cl_log_entity[MAXENTITY]= DFLT_ENTITY; +static char cl_log_syslogprefix[MAXENTITY] = DFLT_PREFIX; +static char common_log_entity[MAXENTITY]= DFLT_ENTITY; +static int cl_log_facility = LOG_USER; +static int use_buffered_io = 0; + +static void cl_opensyslog(void); +static int syslog_enabled = 0; +static int stderr_enabled = 0; +static int stdout_enabled = 0; +static const char* logfile_name = NULL; +static const char* debugfile_name = NULL; +static int cl_process_pid = -1; +int debug_level = 0; +static GDestroyNotify destroy_logging_channel_callback; +static void (*create_logging_channel_callback)(IPC_Channel* chan); +static gboolean logging_chan_in_main_loop = FALSE; + +/*********************** + *debug use only, do not use this function in your program + */ +IPC_Channel * get_log_chan(void); + +IPC_Channel* get_log_chan(void){ + return logging_daemon_chan; +} +/*************************/ + +/************************** + * check if the fd is in use for logging + **************************/ +int +cl_log_is_logd_fd(int fd) +{ + return logging_daemon_chan && ( + fd == logging_daemon_chan->ops->get_send_select_fd(logging_daemon_chan) + || + fd == logging_daemon_chan->ops->get_recv_select_fd(logging_daemon_chan) + ); +} + +void +cl_log_enable_stderr(int truefalse) +{ + stderr_enabled = truefalse; +} + +void +cl_log_enable_stdout(int truefalse) +{ + stdout_enabled = truefalse; +} + +void +cl_log_set_uselogd(int truefalse) +{ + use_logging_daemon = truefalse; +} +void +cl_log_enable_syslog_filefmt(int truefalse) +{ + syslogformatfile = (gboolean)truefalse; +} + +gboolean +cl_log_get_uselogd(void) +{ + return use_logging_daemon; +} + + +int +cl_log_get_logdtime(void) +{ + return conn_logd_time; + +} + +void +cl_log_set_logdtime(int logdtime) +{ + conn_logd_time = logdtime; + return; +} + +void +cl_log_use_buffered_io(int truefalse) +{ + use_buffered_io = truefalse; + cl_log_close_log_files(); +} + +#define ENVPRE "HA_" + +#define ENV_HADEBUGVAL "HA_debug" +#define ENV_LOGFENV "HA_logfile" /* well-formed log file :-) */ +#define ENV_DEBUGFENV "HA_debugfile" /* Debug log file */ +#define ENV_LOGFACILITY "HA_logfacility"/* Facility to use for logger */ +#define ENV_SYSLOGFMT "HA_syslogmsgfmt"/* TRUE if we should use syslog message formatting */ +#define ENV_LOGDAEMON "HA_use_logd" +#define ENV_CONNINTVAL "HA_conn_logd_time" +#define TRADITIONAL_COMPRESSION "HA_traditional_compression" +#define COMPRESSION "HA_compression" + +static void +inherit_compress(void) +{ + char* inherit_env = NULL; + + inherit_env = getenv(TRADITIONAL_COMPRESSION); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean value; + + if (cl_str_to_boolean(inherit_env, &value)!= HA_OK){ + cl_log(LOG_ERR, "inherit traditional_compression failed"); + }else{ + cl_set_traditional_compression(value); + } + } + +} + +void +cl_inherit_logging_environment(int logqueuemax) +{ + char * inherit_env = NULL; + + /* Donnot need to free the return pointer from getenv */ + inherit_env = getenv(ENV_HADEBUGVAL); + if (inherit_env != NULL && atoi(inherit_env) != 0 ) { + debug_level = atoi(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_logfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_DEBUGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_debugfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFACILITY); + if (inherit_env != NULL && *inherit_env != EOS) { + int facility = -1; + facility = cl_syslogfac_str2int(inherit_env); + if ( facility >= 0 ) { + cl_log_set_facility(facility); + } + inherit_env = NULL; + } + + inherit_env = getenv(ENV_SYSLOGFMT); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean truefalse; + if (cl_str_to_boolean(inherit_env, &truefalse) == HA_OK) { + cl_log_enable_syslog_filefmt(truefalse); + } + } + + inherit_env = getenv(ENV_LOGDAEMON); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean uselogd; + cl_str_to_boolean(inherit_env, &uselogd); + cl_log_set_uselogd(uselogd); + if (uselogd) { + if (logqueuemax > 0) { + cl_set_logging_wqueue_maxlen(logqueuemax); + } + } + } + + inherit_env = getenv(ENV_CONNINTVAL); + if (inherit_env != NULL && *inherit_env != EOS) { + int logdtime; + logdtime = cl_get_msec(inherit_env); + cl_log_set_logdtime(logdtime); + } + + inherit_compress(); + return; +} + + +static void +add_logging_channel_mainloop(IPC_Channel* chan) +{ + GCHSource* chp= + G_main_add_IPC_Channel( G_PRIORITY_DEFAULT, + chan, + FALSE, + NULL, + NULL, + destroy_logging_channel_callback); + + if (chp == NULL){ + cl_log(LOG_INFO, "adding logging channel to mainloop failed"); + } + + logging_chan_in_main_loop = TRUE; + + + return; +} + +static void +remove_logging_channel_mainloop(gpointer userdata) +{ + logging_chan_in_main_loop = FALSE; + + return; +} + + +static IPC_Channel* +create_logging_channel(void) +{ + GHashTable* attrs; + char path[] = IPC_PATH_ATTR; + char sockpath[] = HA_LOGDAEMON_IPC; + IPC_Channel* chan; + static gboolean complained_yet = FALSE; + + attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(attrs, path, sockpath); + + chan =ipc_channel_constructor(IPC_ANYTYPE, attrs); + + g_hash_table_destroy(attrs); + + if (chan == NULL) { + cl_log(LOG_ERR, "create_logging_channel:" + "contructing ipc channel failed"); + return NULL; + } + + if (chan->ops->initiate_connection(chan) != IPC_OK) { + if (!complained_yet) { + complained_yet = TRUE; + cl_log(LOG_WARNING, "Initializing connection" + " to logging daemon failed." + " Logging daemon may not be running"); + } + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + + return NULL; + } + complained_yet = FALSE; + + if (create_logging_channel_callback){ + create_logging_channel_callback(chan); + } + + + return chan; + +} + +gboolean +cl_log_test_logd(void) +{ + IPC_Channel* chan = logging_daemon_chan; + + if (chan && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + return TRUE; + } + if (chan ){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + } + + logging_daemon_chan = chan = create_logging_channel(); + + if (chan == NULL){ + return FALSE; + } + + if(chan->ops->get_chan_status(chan) != IPC_CONNECT){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + return FALSE; + } + + return TRUE; + +} + +/* FIXME: This is way too ugly to bear */ + +void +cl_log_set_facility(int facility) +{ + if (syslog_enabled && facility == cl_log_facility) { + return; + } + cl_log_facility = facility; + closelog(); + syslog_enabled = 0; + if (facility > 0) { + cl_opensyslog(); + } +} + +void +cl_log_set_entity(const char * entity) +{ + if (entity == NULL) { + entity = DFLT_ENTITY; + } + strncpy(cl_log_entity, entity, MAXENTITY); + cl_log_entity[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_syslogprefix(const char *prefix) +{ + if (prefix == NULL) { + prefix = DFLT_PREFIX; + } + strncpy(cl_log_syslogprefix, prefix, MAXENTITY); + cl_log_syslogprefix[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_logfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + logfile_name = path; + cl_log_close_log_files(); +} +void +cl_log_set_debugfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + debugfile_name = path; + cl_log_close_log_files(); +} + + +/* + * This function sets two callback functions. + * One for creating a channel and + * the other for destroying a channel* + */ +int +cl_log_set_logd_channel_source( void (*create_callback)(IPC_Channel* chan), + GDestroyNotify destroy_callback) +{ + IPC_Channel* chan = logging_daemon_chan ; + + if (destroy_callback == NULL){ + destroy_logging_channel_callback = remove_logging_channel_mainloop; + }else{ + destroy_logging_channel_callback = destroy_callback; + } + + if (create_callback == NULL){ + create_logging_channel_callback = add_logging_channel_mainloop; + }else{ + create_logging_channel_callback = create_callback; + } + + if (chan != NULL + && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + add_logging_channel_mainloop(chan); + } + + return 0; +} + +const char * +prio2str(int priority) +{ + static const char *log_prio[8] = { + "EMERG", + "ALERT", + "CRIT", + "ERROR", + "WARN", + "notice", + "info", + "debug" + }; + int logpri; + + logpri = LOG_PRI(priority); + + return (logpri < 0 || logpri >= DIMOF(log_prio)) ? + "(undef)" : log_prio[logpri]; +} + +/* print log line to a FILE *f */ +#define print_logline(fp,entity,entity_pid,ts,pristr,buf) { \ + fprintf(fp, "%s[%d]: %s ",entity,entity_pid,ha_timestamp(ts)); \ + if (pristr) \ + fprintf(fp,"%s: %s\n",pristr,buf); \ + else \ + fprintf(fp,"%s\n",buf); \ + } + +static char * syslog_timestamp(TIME_T t); +static void cl_limit_log_update(struct msg_ctrl *ml, time_t ts); + +static void +append_log(FILE * fp, const char * entity, int entity_pid +, TIME_T timestamp, const char * pristr, const char * msg) +{ + static int got_uname = FALSE; + static struct utsname un; + + if (!syslogformatfile) { + print_logline(fp, entity, entity_pid, timestamp, pristr, msg); + return; + } + if (!got_uname) { + uname(&un); + } + /* + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + fprintf(fp, "%s %s %s: [%d]: %s%s%s\n" + , syslog_timestamp(timestamp) + , un.nodename, entity, entity_pid + , (pristr ? pristr : "") + , (pristr ? ": " : "") + , msg); +} + +/* As performance optimization we try to keep the file descriptor + * open all the time, but as logrotation needs to work, the calling + * program actually needs a signal handler. + * + * To be able to keep files open even without signal handler, + * we remember the stat info, and close/reopen if the inode changed. + * We keep the number of stat() calls to one per file per minute. + * logrotate should be configured for delayed compression, if any. + */ + +struct log_file_context { + FILE *fp; + struct stat stat_buf; +}; + +static struct log_file_context log_file, debug_file; + +static void close_log_file(struct log_file_context *lfc) +{ + /* ignore errors, we cannot do anything about them anyways */ + fflush(lfc->fp); + fsync(fileno(lfc->fp)); + fclose(lfc->fp); + lfc->fp = NULL; +} + +void cl_log_close_log_files(void) +{ + if (log_file.fp) + close_log_file(&log_file); + if (debug_file.fp) + close_log_file(&debug_file); +} + +static void maybe_close_log_file(const char *fname, struct log_file_context *lfc) +{ + struct stat buf; + if (!lfc->fp) + return; + if (stat(fname, &buf) || buf.st_ino != lfc->stat_buf.st_ino) { + close_log_file(lfc); + cl_log(LOG_INFO, "log-rotate detected on logfile %s", fname); + } +} + +/* Default to unbuffered IO. logd or others can use cl_log_use_buffered_io(1) + * to enable fully buffered mode, and then use fflush appropriately. + */ +static void open_log_file(const char *fname, struct log_file_context *lfc) +{ + lfc->fp = fopen(fname ,"a"); + if (!lfc->fp) { + syslog(LOG_ERR, "Failed to open log file %s: %s\n" , + fname, strerror(errno)); + } else { + setvbuf(lfc->fp, NULL, + use_buffered_io ? _IOFBF : _IONBF, + BUFSIZ); + fstat(fileno(lfc->fp), &lfc->stat_buf); + } +} + +static void maybe_reopen_log_files(const char *log_fname, const char *debug_fname) +{ + static TIME_T last_stat_time; + + if (log_file.fp || debug_file.fp) { + TIME_T now = time(NULL); + if (now - last_stat_time > 59) { + /* Don't use an exact minute, have it jitter around a + * bit against cron or others. Note that, if there + * is no new log message, it can take much longer + * than this to notice logrotation and actually close + * our file handle on the possibly already rotated, + * or even deleted. + * + * As long as at least one minute pases between + * renaming the log file, and further processing, + * no message will be lost, so this should do fine: + * (mv ha-log ha-log.1; sleep 60; gzip ha-log.1) + */ + maybe_close_log_file(log_fname, &log_file); + maybe_close_log_file(debug_fname, &debug_file); + last_stat_time = now; + } + } + + if (log_fname && !log_file.fp) + open_log_file(log_fname, &log_file); + + if (debug_fname && !debug_file.fp) + open_log_file(debug_fname, &debug_file); +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +/* Cluster logging function */ +void +cl_direct_log(int priority, const char* buf, gboolean use_priority_str, + const char* entity, int entity_pid, TIME_T ts) +{ + const char * pristr; + int needprivs = !cl_have_full_privs(); + + pristr = use_priority_str ? prio2str(priority) : NULL; + + if (!entity) + entity = *cl_log_entity ? cl_log_entity : DFLT_ENTITY; + + if (needprivs) { + return_to_orig_privs(); + } + + if (syslog_enabled) { + snprintf(common_log_entity, MAXENTITY, "%s", + *cl_log_syslogprefix ? cl_log_syslogprefix : entity); + + /* The extra trailing '\0' is supposed to work around some + * "known syslog bug that ends up concatinating entries". + * Knowledge about which syslog package, version, platform and + * what exactly the bug was has been lost, but leaving it in + * won't do any harm either. */ + syslog(priority, "%s[%d]: %s%s%s%c", + *cl_log_syslogprefix ? entity : "", + entity_pid, + pristr ?: "", pristr ? ": " : "", + buf, 0); + } + + maybe_reopen_log_files(logfile_name, debugfile_name); + + if (debug_file.fp) + append_log(debug_file.fp, entity, entity_pid, ts, pristr, buf); + + if (priority != LOG_DEBUG && log_file.fp) + append_log(log_file.fp, entity, entity_pid, ts, pristr, buf); + + if (needprivs) { + return_to_dropped_privs(); + } + return; +} + +void cl_log_do_fflush(int do_fsync) +{ + if (log_file.fp) { + fflush(log_file.fp); + if (do_fsync) + fsync(fileno(log_file.fp)); + } + if (debug_file.fp) { + fflush(debug_file.fp); + if (do_fsync) + fsync(fileno(debug_file.fp)); + } +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +static int cl_log_depth = 0; + +/* Cluster logging function */ +void +cl_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + ssize_t nbytes; + + cl_process_pid = (int)getpid(); + + cl_log_depth++; + + buf[MAXLINE-1] = EOS; + va_start(ap, fmt); + nbytes=vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (nbytes >= (ssize_t)sizeof(buf)){ + nbytes = sizeof(buf) -1 ; + } + + if (stderr_enabled) { + append_log(stderr, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (stdout_enabled) { + append_log(stdout, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (use_logging_daemon && cl_log_depth <= 1) { + LogToLoggingDaemon(priority, buf, nbytes, TRUE); + }else{ + /* this may cause blocking... maybe should make it optional? */ + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + } + + cl_log_depth--; + return; +} + +/* + * Log a message only if there were not too many messages of this + * kind recently. This is too prevent log spamming in case a + * condition persists over a long period of time. The maximum + * number of messages for the timeframe and other details are + * provided in struct logspam (see cl_log.h). + * + * Implementation details: + * - max number of time_t slots is allocated; slots keep time + * stamps of previous max number of messages + * - we check if the difference between now (i.e. new message just + * arrived) and the oldest message is _less_ than the window + * timeframe + * - it's up to the user to do cl_limit_log_new and afterwards + * cl_limit_log_destroy, though the latter is usually not + * necessary; the memory allocated with cl_limit_log_new stays + * constant during the lifetime of the process + * + * NB on Thu Aug 4 15:26:49 CEST 2011: + * This interface is very new, use with caution and report bugs. + */ + +struct msg_ctrl * +cl_limit_log_new(struct logspam *lspam) +{ + struct msg_ctrl *ml; + + ml = (struct msg_ctrl *)malloc(sizeof(struct msg_ctrl)); + if (!ml) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->msg_slots = (time_t *)calloc(lspam->max, sizeof(time_t)); + if (!ml->msg_slots) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->lspam = lspam; + cl_limit_log_reset(ml); + return ml; /* to be passed later to cl_limit_log() */ +} + +void +cl_limit_log_destroy(struct msg_ctrl *ml) +{ + if (!ml) + return; + g_free(ml->msg_slots); + g_free(ml); +} + +void +cl_limit_log_reset(struct msg_ctrl *ml) +{ + ml->last = -1; + ml->cnt = 0; + ml->suppress_t = (time_t)0; + memset(ml->msg_slots, 0, ml->lspam->max * sizeof(time_t)); +} + +static void +cl_limit_log_update(struct msg_ctrl *ml, time_t ts) +{ + ml->last = (ml->last + 1) % ml->lspam->max; + *(ml->msg_slots + ml->last) = ts; + if (ml->cnt < ml->lspam->max) + ml->cnt++; +} + +void +cl_limit_log(struct msg_ctrl *ml, int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + time_t last_ts, now = time(NULL); + + if (!ml) + goto log_msg; + if (ml->suppress_t) { + if ((now - ml->suppress_t) < ml->lspam->reset_time) + return; + /* message blocking expired */ + cl_limit_log_reset(ml); + } + last_ts = ml->last != -1 ? *(ml->msg_slots + ml->last) : (time_t)0; + if ( + ml->cnt < ml->lspam->max || /* not so many messages logged */ + (now - last_ts) > ml->lspam->window /* messages far apart */ + ) { + cl_limit_log_update(ml, now); + goto log_msg; + } else { + cl_log(LOG_INFO + , "'%s' messages logged too often, " + "suppressing messages of this kind for %ld seconds" + , ml->lspam->id, ml->lspam->reset_time); + cl_log(priority, "%s", ml->lspam->advice); + ml->suppress_t = now; + return; + } + +log_msg: + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + cl_log(priority, "%s", buf); +} + +void +cl_perror(const char * fmt, ...) +{ + const char * err; + + va_list ap; + char buf[MAXLINE]; + + err = strerror(errno); + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + cl_log(LOG_ERR, "%s: %s", buf, err); + +} +void +cl_glib_msg_handler(const gchar *log_domain, GLogLevelFlags log_level +, const gchar *message, gpointer user_data) +{ + GLogLevelFlags level = (log_level & G_LOG_LEVEL_MASK); + int ha_level; + + switch(level) { + case G_LOG_LEVEL_ERROR: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_CRITICAL: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_WARNING: ha_level = LOG_WARNING; break; + case G_LOG_LEVEL_MESSAGE: ha_level = LOG_NOTICE; break; + case G_LOG_LEVEL_INFO: ha_level = LOG_INFO; break; + case G_LOG_LEVEL_DEBUG: ha_level = LOG_DEBUG; break; + + default: ha_level = LOG_WARNING; break; + } + + + cl_log(ha_level, "glib: %s", message); +} +static char * +syslog_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + static const char* monthstr [12] = { + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" + }; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%3s %02d %02d:%02d:%02d" + , monthstr[ttm->tm_mon], ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + + +char * +ha_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%04d/%02d/%02d_%02d:%02d:%02d" + , ttm->tm_year+1900, ttm->tm_mon+1, ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + +static int +cl_set_logging_wqueue_maxlen(int qlen) +{ + int sendrc; + IPC_Channel* chan = logging_daemon_chan; + + if (chan == NULL){ + chan = logging_daemon_chan = create_logging_channel(); + } + + if (chan == NULL){ + return HA_FAIL; + } + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, "cl_set_logging_wqueue_maxle:" + "channel is not connected"); + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + return HA_FAIL; + } + + sendrc = chan->ops->set_send_qlen(logging_daemon_chan, qlen); + + if (sendrc == IPC_OK) { + return HA_OK; + }else { + return HA_FAIL; + } +} + + + +int +LogToDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + int rc; + + cl_log_depth++; + + rc= LogToLoggingDaemon(priority, buf, bufstrlen, use_pri_str); + + cl_log_depth--; + + return rc; +} + +static int drop_msg_num = 0; + +void +cl_flush_logs(void) +{ + if(logging_daemon_chan == NULL) { + return; + } + logging_daemon_chan->ops->waitout(logging_daemon_chan); +} + +static int +LogToLoggingDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + IPC_Channel* chan = logging_daemon_chan; + static longclock_t nexttime = 0; + IPC_Message* msg; + int sendrc = IPC_FAIL; + int intval = conn_logd_time; + + /* make sure we don't hold file descriptors open + * we don't intend to use again */ + cl_log_close_log_files(); + + if (chan == NULL) { + longclock_t lnow = time_longclock(); + + if (cmp_longclock(lnow, nexttime) >= 0){ + nexttime = add_longclock( + lnow, msto_longclock(intval)); + + logging_daemon_chan = chan = create_logging_channel(); + } + } + + if (chan == NULL){ + cl_direct_log( + priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + return HA_FAIL; + } + + msg = ChildLogIPCMessage(priority, buf, bufstrlen, use_pri_str, chan); + if (msg == NULL) { + drop_msg_num++; + return HA_FAIL; + } + + if (chan->ch_status == IPC_CONNECT){ + + if (chan->ops->is_sending_blocked(chan)) { + chan->ops->resume_io(chan); + } + /* Make sure there is room for the drop message _and_ the + * one we wish to log. Otherwise there is no point. + * + * Try to avoid bouncing on the limit by additionally + * waiting until there is room for QUEUE_SATURATION_FUZZ + * messages. + */ + if (drop_msg_num > 0 + && chan->send_queue->current_qlen + < (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ)) + { + /* have to send it this way so the order is correct */ + send_dropped_message(use_pri_str, chan); + } + + /* Don't log a debug message if we're + * approaching the queue limit and already + * dropped a message + */ + if (drop_msg_num == 0 + || chan->send_queue->current_qlen < + (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ) + || priority != LOG_DEBUG ) + { + sendrc = chan->ops->send(chan, msg); + } + } + + if (sendrc == IPC_OK) { + return HA_OK; + + } else { + + if (chan->ops->get_chan_status(chan) != IPC_CONNECT) { + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + + if (drop_msg_num > 0){ + /* Direct logging here is ok since we're + * switching to that for everything + * "for a while" + */ + cl_log(LOG_ERR, + "cl_log: %d messages were dropped" + " : channel destroyed", drop_msg_num); + } + + drop_msg_num=0; + FreeChildLogIPCMessage(msg); + return HA_FAIL; + } + + drop_msg_num++; + + } + + FreeChildLogIPCMessage(msg); + return HA_FAIL; +} + + +static gboolean +send_dropped_message(gboolean use_pri_str, IPC_Channel *chan) +{ + int sendrc; + char buf[64]; + int buf_len = 0; + IPC_Message *drop_msg = NULL; + + memset(buf, 0, 64); + snprintf(buf, 64, "cl_log: %d messages were dropped", drop_msg_num); + buf_len = strlen(buf)+1; + drop_msg = ChildLogIPCMessage(LOG_ERR, buf, buf_len, use_pri_str, chan); + + if(drop_msg == NULL || drop_msg->msg_len == 0) { + return FALSE; + } + + sendrc = chan->ops->send(chan, drop_msg); + + if(sendrc == IPC_OK) { + drop_msg_num = 0; + }else{ + FreeChildLogIPCMessage(drop_msg); + } + return sendrc == IPC_OK; +} + + +static IPC_Message* +ChildLogIPCMessage(int priority, const char *buf, int bufstrlen, + gboolean use_prio_str, IPC_Channel* ch) +{ + IPC_Message* ret; + LogDaemonMsgHdr logbuf; + int msglen; + char* bodybuf; + + + if (ch->msgpad > MAX_MSGPAD){ + cl_log(LOG_ERR, "ChildLogIPCMessage: invalid msgpad(%d)", + ch->msgpad); + return NULL; + } + + + ret = (IPC_Message*)malloc(sizeof(IPC_Message)); + + if (ret == NULL) { + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + /* Compute msg len: including room for the EOS byte */ + msglen = sizeof(LogDaemonMsgHdr)+bufstrlen + 1; + bodybuf = malloc(msglen + ch->msgpad); + if (bodybuf == NULL) { + free(ret); + return NULL; + } + + memset(bodybuf, 0, msglen + ch->msgpad); + memset(&logbuf, 0, sizeof(logbuf)); + logbuf.msgtype = LD_LOGIT; + logbuf.facility = cl_log_facility; + logbuf.priority = priority; + logbuf.use_pri_str = use_prio_str; + logbuf.entity_pid = getpid(); + logbuf.timestamp = time(NULL); + if (*cl_log_entity){ + strncpy(logbuf.entity,cl_log_entity,MAXENTITY); + }else { + strncpy(logbuf.entity,DFLT_ENTITY,MAXENTITY); + } + + logbuf.msglen = bufstrlen + 1; + memcpy(bodybuf + ch->msgpad, &logbuf, sizeof(logbuf)); + memcpy(bodybuf + ch->msgpad + sizeof(logbuf), + buf, + bufstrlen); + + ret->msg_len = msglen; + ret->msg_buf = bodybuf; + ret->msg_body = bodybuf + ch->msgpad; + ret->msg_done = FreeChildLogIPCMessage; + ret->msg_ch = ch; + + return ret; +} + + +static void +FreeChildLogIPCMessage(IPC_Message* msg) +{ + if (msg == NULL) { + return; + } + memset(msg->msg_body, 0, msg->msg_len); + free(msg->msg_buf); + + memset(msg, 0, sizeof (*msg)); + free(msg); + + return; + +} + + + +static void +cl_opensyslog(void) +{ + if (*cl_log_entity == '\0' || cl_log_facility < 0) { + return; + } + syslog_enabled = 1; + strncpy(common_log_entity, cl_log_entity, MAXENTITY); + openlog(common_log_entity, LOG_CONS, cl_log_facility); +} + + +void +cl_log_args(int argc, char **argv) +{ + int lpc = 0; + int len = 0; + int existing_len = 0; + char *arg_string = NULL; + + if(argc == 0 || argv == NULL) { + return; + } + + for(;lpc < argc; lpc++) { + if(argv[lpc] == NULL) { + break; + } + + len = 2 + strlen(argv[lpc]); /* +1 space, +1 EOS */ + if(arg_string) { + existing_len = strlen(arg_string); + } + + arg_string = realloc(arg_string, len + existing_len); + sprintf(arg_string + existing_len, "%s ", argv[lpc]); + } + cl_log(LOG_INFO, "Invoked: %s", arg_string); + free(arg_string); +} diff --git a/lib/clplumbing/cl_malloc.c b/lib/clplumbing/cl_malloc.c new file mode 100644 index 0000000..ca6dc0b --- /dev/null +++ b/lib/clplumbing/cl_malloc.c @@ -0,0 +1,1044 @@ +/* + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define HA_MALLOC_ORIGINAL +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif /* HAVE_STDINT_H */ +#include <string.h> +#include <errno.h> +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include <malloc.h> +#endif +#endif +#include <clplumbing/cl_malloc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/longclock.h> + +#include <ltdl.h> + +#ifndef _CLPLUMBING_CLMALLOC_NATIVE_H +static cl_mem_stats_t default_memstats; +static volatile cl_mem_stats_t * memstats = &default_memstats; + +/* + * Compile time malloc debugging switches: + * + * MARK_PRISTINE - puts known byte pattern in freed memory + * Good at finding "use after free" cases + * Cheap in memory, but expensive in CPU + * + * MAKE_GUARD - puts a known pattern *after* allocated memory + * Good at finding overrun problems after the fact + * Cheap in CPU, adds a few bytes to each malloc item + * + */ + +#define MARK_PRISTINE 1 /* Expensive in CPU time */ +#undef MARK_PRISTINE +#define MAKE_GUARD 1 /* Adds 'n' bytes memory - cheap in CPU*/ +#define USE_ASSERTS 1 +#define DUMPONERR 1 +#define RETURN_TO_MALLOC 1 +#undef RETURN_TO_MALLOC + +#ifndef DUMPONERR +# define DUMPIFASKED() /* nothing */ +#else +# define DUMPIFASKED() {abort();} +#endif + + +/* + * + * Malloc wrapper functions + * + * I wrote these so we can better track memory leaks, etc. and verify + * that the system is stable in terms of memory usage. + * + * For our purposes, these functions are a somewhat faster than using + * malloc directly (although they use a bit more memory) + * + * The general strategy is loosely related to the buddy system, + * except very simple, well-suited to our continuous running + * nature, and the constancy of the requests and messages. + * + * We keep an array of linked lists, each for a different size + * buffer. If we need a buffer larger than the largest one provided + * by the list, we go directly to malloc. + * + * Otherwise, we keep return them to the appropriate linked list + * when we're done with them, and reuse them from the list. + * + * We never coalesce buffers on our lists, and we never free them. + * + * It's very simple. We get usage stats. It makes me happy. + */ + +#define HA_MALLOC_MAGIC 0xFEEDBEEFUL +#define HA_FREE_MAGIC 0xDEADBEEFUL + + +/* + * We put a struct cl_mhdr in front of every malloc item. + * This means each malloc item is at least 12 bytes bigger than it theoretically + * needs to be. But, it allows this code to be fast and recognize + * multiple free attempts, and memory corruption *before* the object + * + * It's probably possible to combine these fields a bit, + * since bucket and reqsize are only needed for allocated items, + * both are bounded in value, and fairly strong integrity checks apply + * to them. But then we wouldn't be able to tell *quite* as reliably + * if someone gave us an item to free that we didn't allocate... + * + * Could even make the bucket and reqsize objects into 16-bit ints... + * + * The idea of getting it all down into 32-bits of overhead is + * an interesting thought... + * + * But some architectures have alignment constraints. For instance, sparc + * requires that double-word accesses be aligned on double-word boundaries. + * Thus if the requested space is bigger than a double-word, then cl_mhdr + * should, for safety, be a double-word multiple (minimum 8bytes, 64bits). + +*/ + +#ifdef HA_MALLOC_TRACK +# define HA_MALLOC_OWNER 64 +struct cl_bucket; +#endif + +struct cl_mhdr { +# ifdef HA_MALLOC_MAGIC + unsigned long magic; /* Must match HA_*_MAGIC */ +#endif +# ifdef HA_MALLOC_TRACK + char owner[HA_MALLOC_OWNER]; + struct cl_bucket * left; + struct cl_bucket * right; + int dumped; + longclock_t mtime; +#endif + size_t reqsize; + int bucket; +}; + +struct cl_bucket { + struct cl_mhdr hdr; + struct cl_bucket * next; +}; + +#define NUMBUCKS 12 +#define NOBUCKET (NUMBUCKS) + +static struct cl_bucket* cl_malloc_buckets[NUMBUCKS]; +static size_t cl_bucket_sizes[NUMBUCKS]; +static size_t buckminpow2 = 0L; + +static int cl_malloc_inityet = 0; +static size_t cl_malloc_hdr_offset = sizeof(struct cl_mhdr); + +static void* cl_new_mem(size_t size, int numbuck); +static void cl_malloc_init(void); +static void cl_dump_item(const struct cl_bucket*b); + +#ifdef MARK_PRISTINE +# define PRISTVALUE 0xff + static int cl_check_is_pristine(const void* v, unsigned size); + static void cl_mark_pristine(void* v, unsigned size); + static int pristoff; +#endif + +#define BHDR(p) ((struct cl_bucket*)(void*)(((char*)p)-cl_malloc_hdr_offset)) +#define CBHDR(p) ((const struct cl_bucket*)(const void*)(((const char*)p)-cl_malloc_hdr_offset)) +#define MEMORYSIZE(p)(CBHDR(p)->hdr.reqsize) + +#define MALLOCSIZE(allocsize) ((allocsize) + cl_malloc_hdr_offset + GUARDSIZE) +#define MAXMALLOC (SIZE_MAX-(MALLOCSIZE(0)+1)) + +#ifdef MAKE_GUARD +# define GUARDLEN 4 + static const unsigned char cl_malloc_guard[] = +#if GUARDLEN == 1 + {0xA5}; +#endif +#if GUARDLEN == 2 + {0x5A, 0xA5}; +#endif +#if GUARDLEN == 4 + {0x5A, 0xA5, 0x5A, 0xA5}; +#endif +# define GUARDSIZE sizeof(cl_malloc_guard) +# define ADD_GUARD(cp) (memcpy((((char*)cp)+MEMORYSIZE(cp)), cl_malloc_guard, sizeof(cl_malloc_guard))) +# define GUARD_IS_OK(cp) (memcmp((((const char*)cp)+MEMORYSIZE(cp)), \ + cl_malloc_guard, sizeof(cl_malloc_guard)) == 0) +# define CHECK_GUARD_BYTES(cp, msg) { \ + if (!GUARD_IS_OK(cp)) { \ + cl_log(LOG_ERR, "%s: guard corrupted at 0x%lx", msg \ + , (unsigned long)cp); \ + cl_dump_item(CBHDR(cp)); \ + DUMPIFASKED(); \ + } \ + } +#else +# define GUARDSIZE 0 +# define ADD_GUARD(cp) /* */ +# define GUARD_IS_OK(cp) (1) +# define CHECK_GUARD_BYTES(cp, msg) /* */ +#endif + +#define MALLOCROUND 4096 /* Round big mallocs up to a multiple of this size */ + + +#ifdef HA_MALLOC_TRACK + +static struct cl_bucket * cl_malloc_track_root = NULL; + +static void +cl_ptr_tag(void *ptr, const char *file, const char *function, const int line) +{ + struct cl_bucket* bhdr = BHDR(ptr); + snprintf(bhdr->hdr.owner, HA_MALLOC_OWNER, "%s:%s:%d", + file, function, line); +} + +static void +cl_ptr_track(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.left == NULL); + g_assert(bhdr->hdr.right == NULL); + g_assert((cl_malloc_track_root == NULL) || (cl_malloc_track_root->hdr.left == NULL)); +#endif + + bhdr->hdr.dumped = 0; + bhdr->hdr.mtime = time_longclock(); + + if (cl_malloc_track_root == NULL) { + cl_malloc_track_root = bhdr; + } else { + bhdr->hdr.right = cl_malloc_track_root; + cl_malloc_track_root->hdr.left = bhdr; + cl_malloc_track_root = bhdr; + } +} + +static void +cl_ptr_release(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +/* cl_log(LOG_DEBUG, "cl_free: Freeing memory belonging to %s" + , bhdr->hdr.owner); */ + +#if defined(USE_ASSERTS) + g_assert(cl_malloc_track_root != NULL); + g_assert(cl_malloc_track_root->hdr.left == NULL); +#endif + + if (bhdr->hdr.left != NULL) { + bhdr->hdr.left->hdr.right=bhdr->hdr.right; + } + + if (bhdr->hdr.right != NULL) { + bhdr->hdr.right->hdr.left=bhdr->hdr.left; + } + + if (cl_malloc_track_root == bhdr) { + cl_malloc_track_root=bhdr->hdr.right; + } + + bhdr->hdr.left = NULL; + bhdr->hdr.right = NULL; +} + +static void +cl_ptr_init(void) +{ + cl_malloc_track_root = NULL; +} + +int +cl_malloc_dump_allocated(int log_level, gboolean filter) +{ + int lpc = 0; + struct cl_bucket* cursor = cl_malloc_track_root; + longclock_t time_diff; + + cl_log(LOG_INFO, "Dumping allocated memory buffers:"); + + while (cursor != NULL) { + if(filter && cursor->hdr.dumped) { + + } else if(log_level > LOG_DEBUG) { + } else if(filter) { + lpc++; + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize); + } else { + lpc++; + time_diff = sub_longclock(time_longclock(), cursor->hdr.mtime); + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d, dumped %d, age %lu ms" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize + , cursor->hdr.dumped + , longclockto_long(time_diff)); + } + cursor->hdr.dumped = 1; + cursor = cursor->hdr.right; + } + + cl_log(LOG_INFO, "End dump."); + return lpc; +} +#endif +static const int LogTable256[] = +{ + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; +#define POW2BYTE(b) (LogTable256[b]) +#define BYTE3(i) (((i)&0xFF000000)>>24) +#define BYTE2(i) (((i)&0x00FF0000)>>16) +#define BYTE1(i) (((i)&0x0000FF00)>>8) +#define BYTE0(i) ((i)&0x000000FF) + +/* Works for malloc bucket sizes up to 2^8 */ +#define POW21BYTE(i) (POW2BYTE(BYTE0(i))) + +/* Works for malloc bucket sizes up to 2^16 */ +#define POW22BYTE(i) ((BYTE1(i) != 0x00)? (POW2BYTE(BYTE1(i))+8) \ + : (POW21BYTE(i))) + +/* Works for malloc bucket sizes up to 2^24 */ +#define POW23BYTE(i) ((BYTE2(i) != 0x00)? (POW2BYTE(BYTE2(i))+16) \ + : POW22BYTE(i)) + +/* Works for malloc bucket sizes up to 2^32 */ +#define POW24BYTE(i) ((BYTE3(i) != 0x00)? (POW2BYTE(BYTE3(i))+24) \ + : POW23BYTE(i)) + +/* #define INT2POW2(i) POW24BYTE(i) / * This would allow 2G in our largest malloc chain */ + /* which I don't think we need */ +#define INT2POW2(i) POW23BYTE(i) /* This allows up to about 16 Mbytes in our largest malloc chain */ + /* and it's a little faster than the one above */ + + +/* + * cl_malloc: malloc clone + */ + +void * +cl_malloc(size_t size) +{ +#if 0 + int j; +#endif + int numbuck = NOBUCKET; + struct cl_bucket* buckptr = NULL; + void* ret; + + if(!size) { + cl_log(LOG_ERR + , "%s: refusing to allocate zero sized block" + , __FUNCTION__ + ); + return NULL; + } + if (size > MAXMALLOC) { + return NULL; + } + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + +#if 1 + /* + * NOTE: This restricts bucket sizes to be powers of two + * - which is OK with me - and how the code has always worked :-D + */ + numbuck = INT2POW2(size-1)-buckminpow2; + numbuck = MAX(0, numbuck); + if (numbuck < NUMBUCKS) { + if (size <= cl_bucket_sizes[numbuck] + || (numbuck > 0 && size <= (cl_bucket_sizes[numbuck]/2))) { + buckptr = cl_malloc_buckets[numbuck]; + }else{ + cl_log(LOG_ERR + , "%s: bucket size bug: %lu bytes in %lu byte bucket #%d" + , __FUNCTION__ + , (unsigned long)size + , (unsigned long)cl_bucket_sizes[numbuck] + , numbuck); + + } + } +#else + /* + * Find which bucket would have buffers of the requested size + */ + for (j=0; j < NUMBUCKS; ++j) { + if (size <= cl_bucket_sizes[j]) { + numbuck = j; + buckptr = cl_malloc_buckets[numbuck]; + break; + } + } +#endif + + /* + * Pull it out of the linked list of free buffers if we can... + */ + + if (buckptr == NULL) { + ret = cl_new_mem(size, numbuck); + }else{ + cl_malloc_buckets[numbuck] = buckptr->next; + buckptr->hdr.reqsize = size; + ret = (((char*)buckptr)+cl_malloc_hdr_offset); + +#ifdef MARK_PRISTINE + { + int bucksize = cl_bucket_sizes[numbuck]; + if (!cl_check_is_pristine(ret, bucksize)) { + cl_log(LOG_ERR + , "attempt to allocate memory" + " which is not pristine."); + cl_dump_item(buckptr); + DUMPIFASKED(); + } + } +#endif + +#ifdef HA_MALLOC_MAGIC + switch (buckptr->hdr.magic) { + + case HA_FREE_MAGIC: + break; + + case HA_MALLOC_MAGIC: + cl_log(LOG_ERR + , "attempt to allocate memory" + " already allocated at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + + default: + cl_log(LOG_ERR + , "corrupt malloc buffer at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + } + buckptr->hdr.magic = HA_MALLOC_MAGIC; +#endif /* HA_MALLOC_MAGIC */ + if (memstats) { + memstats->nbytes_req += size; + memstats->nbytes_alloc + += MALLOCSIZE(cl_bucket_sizes[numbuck]); + } + + } + + if (ret && memstats) { +#if 0 && defined(HAVE_MALLINFO) + /* mallinfo is too expensive to use :-( */ + struct mallinfo i = mallinfo(); + memstats->arena = i.arena; +#endif + memstats->numalloc++; + } + if (ret) { +#ifdef HA_MALLOC_TRACK + /* If we were _always_ called via the wrapper functions, + * this wouldn't be necessary, but we aren't, some use + * function pointers directly to cl_malloc() */ + cl_ptr_track(ret); + cl_ptr_tag(ret, "cl_malloc.c", "cl_malloc", 0); +#endif + ADD_GUARD(ret); + } + return(ret); +} + +int +cl_is_allocated(const void *ptr) +{ +#ifdef HA_MALLOC_MAGIC + if (NULL == ptr || CBHDR(ptr)->hdr.magic != HA_MALLOC_MAGIC) { + return FALSE; + }else if (GUARD_IS_OK(ptr)) { + return TRUE; + } + cl_log(LOG_ERR + , "cl_is_allocated: supplied storage is guard-corrupted at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(CBHDR(ptr)); + DUMPIFASKED(); + return FALSE; +#else + return (ptr != NULL); +#endif +} + +/* + * cl_free: "free" clone + */ + +void +cl_free(void *ptr) +{ + int bucket; + struct cl_bucket* bhdr; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (ptr == NULL) { + cl_log(LOG_ERR, "attempt to free NULL pointer in cl_free()"); + DUMPIFASKED(); + return; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_free: attempt to free already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + default: + cl_log(LOG_ERR, "cl_free: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + } +#endif + if (!GUARD_IS_OK(ptr)) { + cl_log(LOG_ERR + , "cl_free: attempt to free guard-corrupted" + " object at 0x%lx", (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bucket = bhdr->hdr.bucket; +#ifdef HA_MALLOC_MAGIC + bhdr->hdr.magic = HA_FREE_MAGIC; +#endif + + /* + * Return it to the appropriate bucket (linked list), or just free + * it if it didn't come from one of our lists... + */ + +#ifndef RETURN_TO_MALLOC + if (bucket >= NUMBUCKS) { +#endif +#ifdef MARK_PRISTINE + /* Is this size right? */ + cl_mark_pristine(ptr, bhdr->hdr.reqsize); +#endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + } + free(bhdr); +#ifndef RETURN_TO_MALLOC + }else{ + int bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= cl_bucket_sizes[bucket]); +# endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bucksize); + } + bhdr->next = cl_malloc_buckets[bucket]; + cl_malloc_buckets[bucket] = bhdr; +#ifdef MARK_PRISTINE + cl_mark_pristine(ptr, bucksize); +# endif + } +#endif /* RETURN_TO_MALLOC */ + if (memstats) { + memstats->numfree++; + } +} + +void* +cl_realloc(void *ptr, size_t newsize) +{ + struct cl_bucket* bhdr; + int bucket; + size_t bucksize; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (memstats) { + memstats->numrealloc++; + } + if (ptr == NULL) { + /* NULL is a legal 'ptr' value for realloc... */ + return cl_malloc(newsize); + } + if (newsize == 0) { + /* realloc() is the most redundant interface ever */ + cl_free(ptr); + return NULL; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_realloc: attempt to realloc already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + default: + cl_log(LOG_ERR, "cl_realloc: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + } +#endif + CHECK_GUARD_BYTES(ptr, "cl_realloc"); + + bucket = bhdr->hdr.bucket; + + /* + * Figure out which bucket it came from... If any... + */ + + if (bucket >= NUMBUCKS) { + /* Not from our bucket-area... Call realloc... */ + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->nbytes_req += newsize; + memstats->nbytes_alloc += MALLOCSIZE(newsize); + memstats->mallocbytes += MALLOCSIZE(newsize); + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bhdr = realloc(bhdr, newsize + cl_malloc_hdr_offset + GUARDSIZE); + if (!bhdr) { + return NULL; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_track(ptr); + cl_ptr_tag(ptr, "cl_malloc.c", "realloc", 0); +#endif + bhdr->hdr.reqsize = newsize; + ptr = (((char*)bhdr)+cl_malloc_hdr_offset); + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - real realloc return value"); + /* Not really a memory leak... BEAM thinks so though... */ + return ptr; /*memory leak*/ + } + bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= bucksize); +#endif + if (newsize > bucksize) { + /* Need to allocate new space for it */ + void* newret = cl_malloc(newsize); + if (newret != NULL) { + memcpy(newret, ptr, bhdr->hdr.reqsize); + CHECK_GUARD_BYTES(newret, "cl_realloc - cl_malloc case"); + } + cl_free(ptr); + return newret; + } + + /* Amazing! It fits into the space previously allocated for it! */ + bhdr->hdr.reqsize = newsize; + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_req += newsize; + } + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - fits in existing space"); + return ptr; +} + +/* + * cl_new_mem: use the real malloc to allocate some new memory + */ + +static void* +cl_new_mem(size_t size, int numbuck) +{ + struct cl_bucket* hdrret; + size_t allocsize; + size_t mallocsize; + + if (numbuck < NUMBUCKS) { + allocsize = cl_bucket_sizes[numbuck]; + }else{ + allocsize = size; + } + + mallocsize = MALLOCSIZE(allocsize); + if (numbuck == NOBUCKET) { + mallocsize = (((mallocsize + (MALLOCROUND-1))/MALLOCROUND)*MALLOCROUND); + } + + if ((hdrret = malloc(mallocsize)) == NULL) { + return NULL; + } + + hdrret->hdr.reqsize = size; + hdrret->hdr.bucket = numbuck; +#ifdef HA_MALLOC_MAGIC + hdrret->hdr.magic = HA_MALLOC_MAGIC; +#endif +#ifdef HA_MALLOC_TRACK + hdrret->hdr.left = NULL; + hdrret->hdr.right = NULL; + hdrret->hdr.owner[0] = '\0'; + hdrret->hdr.dumped = 0; +#endif + + if (memstats) { + memstats->nbytes_alloc += mallocsize; + memstats->nbytes_req += size; + memstats->mallocbytes += mallocsize; + } + /* BEAM BUG -- this is NOT a leak */ + return(((char*)hdrret)+cl_malloc_hdr_offset); /*memory leak*/ +} + + +/* + * cl_calloc: calloc clone + */ + +void * +cl_calloc(size_t nmemb, size_t size) +{ + void * ret = cl_malloc(nmemb*size); + + if (ret != NULL) { + memset(ret, 0, nmemb*size); +#ifdef HA_MALLOC_TRACK + cl_ptr_tag(ret, "cl_malloc.c", "cl_calloc", 0); +#endif + } + + return(ret); +} + +#ifdef HA_MALLOC_TRACK +void * +cl_calloc_track(size_t nmemb, size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_calloc(nmemb, size); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void* +cl_realloc_track(void *ptr, size_t newsize, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_realloc(ptr, newsize); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void * +cl_malloc_track(size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_malloc(size); + if (ret) { + /* Retag with the proper owner. */ + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +#endif + +/* + * cl_strdup: strdup clone + */ + +char * +cl_strdup(const char *s) +{ + void * ret; + + if (!s) { + cl_log(LOG_ERR, "cl_strdup(NULL)"); + return(NULL); + } + ret = cl_malloc((strlen(s) + 1) * sizeof(char)); + + if (ret) { + strcpy(ret, s); + } + + return(ret); +} + + +/* + * cl_malloc_init(): initialize our malloc wrapper things + */ + +static void +cl_malloc_init() +{ + int j; + size_t cursize = 32; + int llcount = 1; + + cl_malloc_inityet = 1; + + /* cl_malloc_hdr_offset should be a double-word multiple */ + while (cl_malloc_hdr_offset > (llcount * sizeof(long long))) { + llcount++; + } + cl_malloc_hdr_offset = llcount * sizeof(long long); + + + for (j=0; j < NUMBUCKS; ++j) { + cl_malloc_buckets[j] = NULL; + + cl_bucket_sizes[j] = cursize; + cursize <<= 1; + } + buckminpow2 = INT2POW2(cl_bucket_sizes[0]-1); +#ifdef MARK_PRISTINE + { + struct cl_bucket b; + pristoff = (unsigned char*)&(b.next)-(unsigned char*)&b; + pristoff += sizeof(b.next); + } +#endif +#ifdef HA_MALLOC_TRACK + cl_ptr_init(); +#endif +} + +void +cl_malloc_setstats(volatile cl_mem_stats_t *stats) +{ + if (memstats && stats) { + *stats = *memstats; + } + memstats = stats; +} + +volatile cl_mem_stats_t * +cl_malloc_getstats(void) +{ + return memstats; +} + +static void +cl_dump_item(const struct cl_bucket*b) +{ + const unsigned char * cbeg; + const unsigned char * cend; + const unsigned char * cp; + cl_log(LOG_INFO, "Dumping cl_malloc item @ 0x%lx, bucket address: 0x%lx" + , ((unsigned long)b)+cl_malloc_hdr_offset, (unsigned long)b); +#ifdef HA_MALLOC_TRACK + cl_log(LOG_INFO, "Owner: %s" + , b->hdr.owner); +#endif +#ifdef HA_MALLOC_MAGIC + cl_log(LOG_INFO, "Magic number: 0x%lx reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , b->hdr.magic + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#else + cl_log(LOG_INFO, "reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#endif + cbeg = ((const unsigned char *)b)+cl_malloc_hdr_offset; + cend = cbeg+b->hdr.reqsize+GUARDSIZE; + + for (cp=cbeg; cp < cend; cp+= sizeof(unsigned)) { + cl_log(LOG_INFO, "%02x %02x %02x %02x \"%c%c%c%c\"" + , (unsigned)cp[0], (unsigned)cp[1] + , (unsigned)cp[2], (unsigned)cp[3] + , cp[0], cp[1], cp[2], cp[3]); + } +} + +/* The only reason these functions exist is because glib uses non-standard + * types (gsize)in place of size_t. Since size_t is 64-bits on some + * machines where gsize (unsigned int) is 32-bits, this is annoying. + */ + +static gpointer +cl_malloc_glib(gsize n_bytes) +{ + return (gpointer)cl_malloc((size_t)n_bytes); +} + +static void +cl_free_glib(gpointer mem) +{ + cl_free((void*)mem); +} + +static void * +cl_realloc_glib(gpointer mem, gsize n_bytes) +{ + return cl_realloc((void*)mem, (size_t)n_bytes); +} + + +/* Call before using any glib functions(!) */ +/* See also: g_mem_set_vtable() */ +void +cl_malloc_forced_for_glib(void) +{ + static GMemVTable vt = { + cl_malloc_glib, + cl_realloc_glib, + cl_free_glib, + NULL, + NULL, + NULL, + }; + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + g_mem_set_vtable(&vt); +} + +#ifdef MARK_PRISTINE +static int +cl_check_is_pristine(const void* v, unsigned size) +{ + const unsigned char * cp; + const unsigned char * last; + cp = v; + last = cp + size; + cp += pristoff; + + for (;cp < last; ++cp) { + if (*cp != PRISTVALUE) { + return FALSE; + } + } + return TRUE; +} +static void +cl_mark_pristine(void* v, unsigned size) +{ + unsigned char * cp = v; + memset(cp+pristoff, PRISTVALUE, size-pristoff); +} +#endif + +#endif /* _CLPLUMBING_CLMALLOC_NATIVE_H */ diff --git a/lib/clplumbing/cl_misc.c b/lib/clplumbing/cl_misc.c new file mode 100644 index 0000000..be6441d --- /dev/null +++ b/lib/clplumbing/cl_misc.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <lha_internal.h> + +#include <strings.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_log.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <sys/time.h> + +int +cl_str_to_boolean(const char * s, int * ret) +{ + if(s == NULL) { + return HA_FAIL; + } + + if ( strcasecmp(s, "true") == 0 + || strcasecmp(s, "on") == 0 + || strcasecmp(s, "yes") == 0 + || strcasecmp(s, "y") == 0 + || strcasecmp(s, "1") == 0){ + *ret = TRUE; + return HA_OK; + } + if ( strcasecmp(s, "false") == 0 + || strcasecmp(s, "off") == 0 + || strcasecmp(s, "no") == 0 + || strcasecmp(s, "n") == 0 + || strcasecmp(s, "0") == 0){ + *ret = FALSE; + return HA_OK; + } + return HA_FAIL; +} + +int +cl_file_exists(const char* filename) +{ + struct stat st; + + if (filename == NULL){ + cl_log(LOG_ERR, "%s: NULL filename", + __FUNCTION__); + return FALSE; + } + + if (lstat(filename, &st) == 0){ + return S_ISREG(st.st_mode); + } + + return FALSE; +} + +char* +cl_get_env(const char* env_name) +{ + if (env_name == NULL){ + cl_log(LOG_ERR, "%s: null name", + __FUNCTION__); + return NULL; + } + + return getenv(env_name); +} + + +int +cl_binary_to_int(const char* data, int len) +{ + const char *p = data; + const char *pmax = p + len; + guint h = *p; + + if (h){ + for (p += 1; p < pmax; p++){ + h = (h << 5) - h + *p; + } + } + + return h; +} + +/* + * Convert a string into a positive, rounded number of milliseconds. + * + * Returns -1 on error. + * + * Permissible forms: + * [0-9]+ units are seconds + * [0-9]*.[0-9]+ units are seconds + * [0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]*.[0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]+ *[Uu][Ss] units are microseconds + * [0-9]*.[0-9]+ *[Uu][Ss] units are microseconds + * + * Examples: + * + * 1 = 1000 milliseconds + * 1000ms = 1000 milliseconds + * 1000000us = 1000 milliseconds + * 0.1 = 100 milliseconds + * 100ms = 100 milliseconds + * 100000us = 100 milliseconds + * 0.001 = 1 millisecond + * 1ms = 1 millisecond + * 1000us = 1 millisecond + * 499us = 0 milliseconds + * 501us = 1 millisecond + */ + +#define NUMCHARS "0123456789." +#define WHITESPACE " \t\n\r\f" +#define EOS '\0' + +long +cl_get_msec(const char * input) +{ + const char * cp = input; + const char * units; + long multiplier = 1000; + long divisor = 1; + long ret = -1; + double dret; + + cp += strspn(cp, WHITESPACE); + units = cp + strspn(cp, NUMCHARS); + units += strspn(units, WHITESPACE); + + if (strchr(NUMCHARS, *cp) == NULL) { + return ret; + } + + if (strncasecmp(units, "ms", 2) == 0 + || strncasecmp(units, "cl_get_msec", 4) == 0) { + multiplier = 1; + divisor = 1; + }else if (strncasecmp(units, "us", 2) == 0 + || strncasecmp(units, "usec", 4) == 0) { + multiplier = 1; + divisor = 1000; + }else if (*units != EOS && *units != '\n' + && *units != '\r') { + return ret; + } + dret = atof(cp); + dret *= (double)multiplier; + dret /= (double)divisor; + dret += 0.5; + ret = (long)dret; + return(ret); +} diff --git a/lib/clplumbing/cl_msg.c b/lib/clplumbing/cl_msg.c new file mode 100644 index 0000000..22f00e3 --- /dev/null +++ b/lib/clplumbing/cl_msg.c @@ -0,0 +1,2537 @@ +/* + * Heartbeat messaging object. + * + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/utsname.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/base64.h> +#include <clplumbing/netstring.h> +#include <glib.h> +#include <clplumbing/cl_uuid.h> +#include <compress.h> +#include <clplumbing/timers.h> +#include <clplumbing/cl_signal.h> + +#define MAXMSGLINE 512 +#define MINFIELDS 30 +#define NEWLINE "\n" + + +#define NEEDAUTH 1 +#define NOAUTH 0 +#define MAX_INT_LEN 64 +#define MAX_NAME_LEN 255 +#define UUID_SLEN 64 +#define MAXCHILDMSGLEN 512 + +static int compression_threshold = (128*1024); + +static enum cl_msgfmt msgfmt = MSGFMT_NVPAIR; +static gboolean use_traditional_compression = FALSE; + +const char* +FT_strings[]={ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" +}; + +#undef DOAUDITS +#define DOAUDITS + +#undef DOPARANOIDAUDITS +/* #define DOPARANOIDAUDITS */ + +#ifdef DOAUDITS +void ha_msg_audit(const struct ha_msg* msg); +# define AUDITMSG(msg) ha_msg_audit(msg) +# ifdef DOPARANOIDAUDITS +# define PARANOIDAUDITMSG(msg) ha_msg_audit(msg) +# else +# define PARANOIDAUDITMSG(msg) /*nothing*/ +# endif +#else +# define AUDITMSG(msg) /*nothing*/ +# define PARANOIDAUDITMSG(msg) /*nothing*/ +#endif + + +static volatile hb_msg_stats_t* msgstats = NULL; + +gboolean cl_msg_quiet_fmterr = FALSE; + +extern int netstring_format; + +static struct ha_msg* wirefmt2msg_ll(const char* s, size_t length, int need_auth); + +struct ha_msg* string2msg_ll(const char * s, size_t length, int need_auth, int depth); + +extern int struct_stringlen(size_t namlen, size_t vallen, const void* value); +extern int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +extern int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +static char* msg2wirefmt_ll(struct ha_msg*m, size_t* len, gboolean need_compress); +extern GHashTable* CompressFuncs; + + +void +cl_set_traditional_compression(gboolean value) +{ + use_traditional_compression = value; + if (use_traditional_compression && CompressFuncs) { + cl_log(LOG_WARNING + , "Traditional compression selected" + ". Realtime behavior will likely be impacted(!)"); + cl_log(LOG_INFO + , "See %s for more information." + , HAURL("Ha.cf#traditional_compression_-_controls_compression_mode")); + } +} + +void +cl_set_compression_threshold(size_t threadhold) +{ + compression_threshold = threadhold; + +} + +void +cl_msg_setstats(volatile hb_msg_stats_t* stats) +{ + msgstats = stats; +} + +static int msg_stats_fd = -1; + +static int +cl_msg_stats_open(const char* filename) +{ + if (filename == NULL){ + cl_log(LOG_ERR, "%s: filename is NULL", __FUNCTION__); + return -1; + } + + return open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); + +} + +static int +cl_msg_stats_close(void) +{ + if (msg_stats_fd > 0){ + close(msg_stats_fd); + } + + msg_stats_fd = -1; + + return HA_OK; +} + +#define STATSFILE "/var/log/ha_msg_stats" +int +cl_msg_stats_add(longclock_t time, int size) +{ + char buf[MAXLINE]; + int len; + + if (msg_stats_fd < 0){ + msg_stats_fd = cl_msg_stats_open(STATSFILE); + if (msg_stats_fd < 0){ + cl_log(LOG_ERR, "%s:opening file failed", + __FUNCTION__); + return HA_FAIL; + } + } + + + sprintf(buf, "%lld %d\n", (long long)time, size); + len = strnlen(buf, MAXLINE); + if (write(msg_stats_fd, buf, len) == len){ + cl_msg_stats_close(); + return HA_OK; + } + + cl_msg_stats_close(); + + return HA_FAIL;; + +} + + +/* Set default messaging format */ +void +cl_set_msg_format(enum cl_msgfmt mfmt) +{ + msgfmt = mfmt; +} + +void +cl_dump_msgstats(void) +{ + if (msgstats){ + cl_log(LOG_INFO, "dumping msg stats: " + "allocmsgs=%lu", + msgstats->allocmsgs); + } + return; +} +void +list_cleanup(GList* list) +{ + size_t i; + for (i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_WARNING, "list_cleanup:" + "element is NULL"); + continue; + } + free(element); + } + g_list_free(list); +} + + + +/* Create a new (empty) message */ +struct ha_msg * +ha_msg_new(int nfields) +{ + struct ha_msg * ret; + int nalloc; + + ret = MALLOCT(struct ha_msg); + if (ret) { + ret->nfields = 0; + + if (nfields > MINFIELDS) { + nalloc = nfields; + } else { + nalloc = MINFIELDS; + } + + ret->nalloc = nalloc; + ret->names = (char **)calloc(sizeof(char *), nalloc); + ret->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->values = (void **)calloc(sizeof(void *), nalloc); + ret->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->types = (int*)calloc(sizeof(int), nalloc); + + if (ret->names == NULL || ret->values == NULL + || ret->nlens == NULL || ret->vlens == NULL + || ret->types == NULL) { + + cl_log(LOG_ERR, "%s" + , "ha_msg_new: out of memory for ha_msg"); + /* It is safe to give this to ha_msg_del() */ + /* at this point. It's well-enough-formed */ + ha_msg_del(ret); /*violated property*/ + ret = NULL; + }else if (msgstats) { + msgstats->allocmsgs++; + msgstats->totalmsgs++; + msgstats->lastmsg = time_longclock(); + } + } + return(ret); +} + +/* Delete (destroy) a message */ +void +ha_msg_del(struct ha_msg *msg) +{ + if (msg) { + int j; + PARANOIDAUDITMSG(msg); + if (msgstats) { + msgstats->allocmsgs--; + } + if (msg->names) { + for (j=0; j < msg->nfields; ++j) { + if (msg->names[j]) { + free(msg->names[j]); + msg->names[j] = NULL; + } + } + free(msg->names); + msg->names = NULL; + } + if (msg->values) { + for (j=0; j < msg->nfields; ++j) { + + if (msg->values[j] == NULL){ + continue; + } + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + } + } + free(msg->values); + msg->values = NULL; + } + if (msg->nlens) { + free(msg->nlens); + msg->nlens = NULL; + } + if (msg->vlens) { + free(msg->vlens); + msg->vlens = NULL; + } + if (msg->types){ + free(msg->types); + msg->types = NULL; + } + msg->nfields = -1; + msg->nalloc = -1; + free(msg); + } +} +struct ha_msg* +ha_msg_copy(const struct ha_msg *msg) +{ + struct ha_msg* ret; + int j; + + + PARANOIDAUDITMSG(msg); + if (msg == NULL || (ret = ha_msg_new(msg->nalloc)) == NULL) { + return NULL; + } + + ret->nfields = msg->nfields; + + memcpy(ret->nlens, msg->nlens, sizeof(msg->nlens[0])*msg->nfields); + memcpy(ret->vlens, msg->vlens, sizeof(msg->vlens[0])*msg->nfields); + memcpy(ret->types, msg->types, sizeof(msg->types[0])*msg->nfields); + + for (j=0; j < msg->nfields; ++j) { + + if ((ret->names[j] = malloc(msg->nlens[j]+1)) == NULL) { + goto freeandleave; + } + memcpy(ret->names[j], msg->names[j], msg->nlens[j]+1); + + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + ret->values[j] = fieldtypefuncs[msg->types[j]].dup(msg->values[j], + msg->vlens[j]); + if (!ret->values[j]){ + cl_log(LOG_ERR,"duplicating the message field failed"); + goto freeandleave; + } + } + } + return ret; + +freeandleave: + /* + * ha_msg_del nicely handles partially constructed ha_msgs + * so, there's not really a memory leak here at all, but BEAM + * thinks there is. + */ + ha_msg_del(ret);/* memory leak */ ret=NULL; + return ret; +} + +#ifdef DOAUDITS +void +ha_msg_audit(const struct ha_msg* msg) +{ + int doabort = FALSE; + int j; + + if (!msg) { + return; + } + if (!msg) { + cl_log(LOG_CRIT, "Message @ %p is not allocated" + , msg); + abort(); + } + if (msg->nfields < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative fields (%d)" + , msg, msg->nfields); + doabort = TRUE; + } + if (msg->nalloc < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative nalloc (%d)" + , msg, msg->nalloc); + doabort = TRUE; + } + + if (!msg->names) { + cl_log(LOG_CRIT + , "Message names @ %p is not allocated" + , msg->names); + doabort = TRUE; + } + if (!msg->values) { + cl_log(LOG_CRIT + , "Message values @ %p is not allocated" + , msg->values); + doabort = TRUE; + } + if (!msg->nlens) { + cl_log(LOG_CRIT + , "Message nlens @ %p is not allocated" + , msg->nlens); + doabort = TRUE; + } + if (!msg->vlens) { + cl_log(LOG_CRIT + , "Message vlens @ %p is not allocated" + , msg->vlens); + doabort = TRUE; + } + if (doabort) { + cl_log_message(LOG_INFO,msg); + abort(); + } + for (j=0; j < msg->nfields; ++j) { + + if (msg->nlens[j] == 0){ + cl_log(LOG_ERR, "zero namelen found in msg"); + abort(); + } + + if (msg->types[j] == FT_STRING){ + if (msg->vlens[j] != strlen(msg->values[j])){ + cl_log(LOG_ERR, "stringlen does not match"); + cl_log_message(LOG_INFO,msg); + abort(); + } + } + + if (!msg->names[j]) { + cl_log(LOG_CRIT, "Message name[%d] @ 0x%p" + " is not allocated." , + j, msg->names[j]); + abort(); + } + if (msg->types[j] != FT_LIST && !msg->values[j]) { + cl_log(LOG_CRIT, "Message value [%d] @ 0x%p" + " is not allocated.", j, msg->values[j]); + cl_log_message(LOG_INFO, msg); + abort(); + } + } +} +#endif + + + +int +ha_msg_expand(struct ha_msg* msg ) +{ + char ** names ; + size_t *nlens ; + void ** values ; + size_t* vlens ; + int * types ; + int nalloc; + + if(!msg){ + cl_log(LOG_ERR, "ha_msg_expand:" + "input msg is null"); + return HA_FAIL; + } + + names = msg->names; + nlens = msg->nlens; + values = msg->values; + vlens = msg->vlens; + types = msg->types; + + nalloc = msg->nalloc + MINFIELDS; + msg->names = (char **)calloc(sizeof(char *), nalloc); + msg->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->values = (void **)calloc(sizeof(void *), nalloc); + msg->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->types= (int*)calloc(sizeof(int), nalloc); + + if (msg->names == NULL || msg->values == NULL + || msg->nlens == NULL || msg->vlens == NULL + || msg->types == NULL) { + + cl_log(LOG_ERR, "%s" + , " out of memory for ha_msg"); + return(HA_FAIL); + } + + memcpy(msg->names, names, msg->nalloc*sizeof(char *)); + memcpy(msg->nlens, nlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->values, values, msg->nalloc*sizeof(void *)); + memcpy(msg->vlens, vlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->types, types, msg->nalloc*sizeof(int)); + + free(names); + free(nlens); + free(values); + free(vlens); + free(types); + + msg->nalloc = nalloc; + + return HA_OK; +} + +int +cl_msg_remove_value(struct ha_msg* msg, const void* value) +{ + int j; + + if (msg == NULL || value == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %p not found", value); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); + +} + + +int +cl_msg_remove(struct ha_msg* msg, const char* name) +{ + int j; + + if (msg == NULL || name == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (strcmp(name, msg->names[j]) == 0){ + break; + } + } + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %s not found", name); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); +} + +int +cl_msg_remove_offset(struct ha_msg* msg, int offset) +{ + int j = offset; + int i; + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %d not found", j); + return HA_FAIL; + } + + free(msg->names[j]); + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + + for (i= j + 1; i < msg->nfields ; i++){ + msg->names[i -1] = msg->names[i]; + msg->nlens[i -1] = msg->nlens[i]; + msg->values[i -1] = msg->values[i]; + msg->vlens[i-1] = msg->vlens[i]; + msg->types[i-1] = msg->types[i]; + } + msg->nfields--; + + + return HA_OK; +} + + + +/* low level implementation for ha_msg_add + the caller is responsible to allocate/free memories + for @name and @value. + +*/ + +static int +ha_msg_addraw_ll(struct ha_msg * msg, char * name, size_t namelen, + void * value, size_t vallen, int type, int depth) +{ + + size_t startlen = sizeof(MSG_START)-1; + + + int (*addfield) (struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth); + + if (!msg || msg->names == NULL || (msg->values == NULL) ) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: cannot add field to ha_msg"); + return(HA_FAIL); + } + + if (msg->nfields >= msg->nalloc) { + if( ha_msg_expand(msg) != HA_OK){ + cl_log(LOG_ERR, "message expanding failed"); + return(HA_FAIL); + } + + } + + if (namelen >= startlen + && name[0] == '>' + && strncmp(name, MSG_START, startlen) == 0) { + if(!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: illegal field"); + } + return(HA_FAIL); + } + + if (name == NULL || (value == NULL) + || namelen <= 0 || vallen < 0) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: " + "cannot add name/value to ha_msg"); + return(HA_FAIL); + } + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + addfield = fieldtypefuncs[type].addfield; + if (!addfield || + addfield(msg, name, namelen, value, vallen,depth) != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw_ll: addfield failed"); + return(HA_FAIL); + } + + PARANOIDAUDITMSG(msg); + + return(HA_OK); + + +} + +static int +ha_msg_addraw(struct ha_msg * msg, const char * name, size_t namelen, + const void * value, size_t vallen, int type, int depth) +{ + + char *cpvalue = NULL; + char *cpname = NULL; + int ret; + + + if (namelen == 0){ + cl_log(LOG_ERR, "%s: Adding a field with 0 name length", __FUNCTION__); + return HA_FAIL; + } + + if ((cpname = malloc(namelen+1)) == NULL) { + cl_log(LOG_ERR, "ha_msg_addraw: no memory for string (name)"); + return(HA_FAIL); + } + strncpy(cpname, name, namelen); + cpname[namelen] = EOS; + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + if (fieldtypefuncs[type].dup){ + cpvalue = fieldtypefuncs[type].dup(value, vallen); + } + if (cpvalue == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw: copying message failed"); + free(cpname); + return(HA_FAIL); + } + + ret = ha_msg_addraw_ll(msg, cpname, namelen, cpvalue, vallen + , type, depth); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw(): ha_msg_addraw_ll failed"); + free(cpname); + fieldtypefuncs[type].memfree(cpvalue); + } + + return(ret); + +} + +/*Add a null-terminated name and binary value to a message*/ +int +ha_msg_addbin(struct ha_msg * msg, const char * name, + const void * value, size_t vallen) +{ + + return(ha_msg_addraw(msg, name, strlen(name), + value, vallen, FT_BINARY, 0)); + +} + +int +ha_msg_adduuid(struct ha_msg* msg, const char *name, const cl_uuid_t* u) +{ + return(ha_msg_addraw(msg, name, strlen(name), + u, sizeof(cl_uuid_t), FT_BINARY, 0)); +} + +/*Add a null-terminated name and struct value to a message*/ +int +ha_msg_addstruct(struct ha_msg * msg, const char * name, const void * value) +{ + const struct ha_msg* childmsg = (const struct ha_msg*) value; + + if (get_netstringlen(childmsg) > MAXCHILDMSGLEN + || get_stringlen(childmsg) > MAXCHILDMSGLEN) { + /*cl_log(LOG_WARNING, + "%s: childmsg too big (name=%s, nslen=%d, len=%d)." + " Use ha_msg_addstruct_compress() instead.", + __FUNCTION__, name, get_netstringlen(childmsg), + get_stringlen(childmsg)); + */ + } + + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); +} + +int +ha_msg_addstruct_compress(struct ha_msg * msg, const char * name, const void * value) +{ + + if (use_traditional_compression){ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); + }else{ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_UNCOMPRESS, 0); + } +} + +int +ha_msg_add_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_int(const struct ha_msg * msg, const char * name, int* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = atoi(svalue); + return HA_OK; +} + +int +ha_msg_add_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_ul(const struct ha_msg * msg, const char * name, unsigned long* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = strtoul(svalue, NULL, 10); + return HA_OK; +} + +/* + * ha_msg_value_str_list()/ha_msg_add_str_list(): + * transform a string list suitable for putting into an ha_msg is by a convention + * of naming the fields into the following format: + * listname1=foo + * listname2=bar + * listname3=stuff + * etc. + */ + +GList* +ha_msg_value_str_list(struct ha_msg * msg, const char * name) +{ + + int i = 1; + int len = 0; + const char* value; + char* element; + GList* list = NULL; + + + if( NULL==msg||NULL==name||strnlen(name, MAX_NAME_LEN)>=MAX_NAME_LEN ){ + return NULL; + } + len = cl_msg_list_length(msg,name); + for(i=0; i<len; i++) { + value = cl_msg_list_nth_data(msg,name,i); + if (NULL == value) { + break; + } + element = g_strdup(value); + list = g_list_append(list, element); + } + return list; +} + + + +static void +pair_to_msg(gpointer key, gpointer value, gpointer user_data) +{ + struct ha_msg* msg = (struct ha_msg*)user_data; + if( HA_OK != ha_msg_add(msg, key, value)) { + cl_log(LOG_ERR, "ha_msg_add in pair_to_msg failed"); + } +} + + +static struct ha_msg* +str_table_to_msg(GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + + if ( NULL == hash_table) { + return NULL; + } + + hash_msg = ha_msg_new(5); + g_hash_table_foreach(hash_table, pair_to_msg, hash_msg); + return hash_msg; +} + + +static GHashTable* +msg_to_str_table(struct ha_msg * msg) +{ + int i; + GHashTable* hash_table; + + if ( NULL == msg) { + return NULL; + } + + hash_table = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; i < msg->nfields; i++) { + if( FT_STRING != msg->types[i] ) { + continue; + } + g_hash_table_insert(hash_table, + g_strndup(msg->names[i],msg->nlens[i]), + g_strndup(msg->values[i],msg->vlens[i])); + } + return hash_table; +} + +GHashTable* +ha_msg_value_str_table(struct ha_msg * msg, const char * name) +{ + struct ha_msg* hash_msg; + GHashTable * hash_table = NULL; + + if (NULL == msg || NULL == name) { + return NULL; + } + + hash_msg = cl_get_struct(msg, name); + if (NULL == hash_msg) { + return NULL; + } + hash_table = msg_to_str_table(hash_msg); + return hash_table; +} + +int +ha_msg_add_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != ha_msg_addstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_addstruct in ha_msg_add_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +ha_msg_mod_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != cl_msg_modstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_modstruct in ha_msg_mod_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +cl_msg_list_add_string(struct ha_msg* msg, const char* name, const char* value) +{ + GList* list = NULL; + int ret; + + if(!msg || !name || !value){ + cl_log(LOG_ERR, "cl_msg_list_add_string: input invalid"); + return HA_FAIL; + } + + + list = g_list_append(list, UNCONST_CAST_POINTER(gpointer, value)); + if (!list){ + cl_log(LOG_ERR, "cl_msg_list_add_string: append element to" + "a glist failed"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + g_list_free(list); + + return ret; + +} + +/* Add a null-terminated name and value to a message */ +int +ha_msg_add(struct ha_msg * msg, const char * name, const char * value) +{ + if(name == NULL || value == NULL) { + return HA_FAIL; + } + return(ha_msg_nadd(msg, name, strlen(name), value, strlen(value))); +} + +/* Add a name/value pair to a message (with sizes for name and value) */ +int +ha_msg_nadd(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, FT_STRING, 0)); + +} + +/* Add a name/value/type to a message (with sizes for name and value) */ +int +ha_msg_nadd_type(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen, int type) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, type, 0)); + +} + + + +/* Add a "name=value" line to the name, value pairs in a message */ +static int +ha_msg_add_nv_depth(struct ha_msg* msg, const char * nvline, + const char * bufmax, int depth) +{ + int namelen; + const char * valp; + int vallen; + + if (!nvline) { + cl_log(LOG_ERR, "ha_msg_add_nv: NULL nvline"); + return(HA_FAIL); + } + /* How many characters before the '='? */ + if ((namelen = strcspn(nvline, EQUAL)) <= 0 + || nvline[namelen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "ha_msg_add_nv_depth: line doesn't contain '='"); + cl_log(LOG_INFO, "%s", nvline); + } + return(HA_FAIL); + } + valp = nvline + namelen +1; /* Point just *past* the '=' */ + if (valp >= bufmax){ + return HA_FAIL; + } + vallen = strcspn(valp, NEWLINE); + if ((valp + vallen) >= bufmax){ + return HA_FAIL; + } + + if (vallen == 0){ + valp = NULL; + } + /* Call ha_msg_nadd to actually add the name/value pair */ + return(ha_msg_addraw(msg, nvline, namelen, valp, vallen + , FT_STRING, depth)); + +} + +int +ha_msg_add_nv(struct ha_msg* msg, const char * nvline, + const char * bufmax) +{ + + return(ha_msg_add_nv_depth(msg, nvline, bufmax, 0)); + +} + + +static void * +cl_get_value(const struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument (%s)", + __FUNCTION__, name); + return(NULL); + } + + PARANOIDAUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + const char *local_name = msg->names[j]; + if (name[0] == local_name[0] + && strcmp(name, local_name) == 0) { + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + +static void * +cl_get_value_mutate(struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return(NULL); + } + + AUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + int tp = msg->types[j]; + if (fieldtypefuncs[tp].pregetaction){ + fieldtypefuncs[tp].pregetaction(msg, j); + } + + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + + +const void * +cl_get_binary(const struct ha_msg *msg, + const char * name, size_t * vallen) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, vallen, &type); + + if (ret == NULL){ + /* + cl_log(LOG_WARNING, "field %s not found", name); + cl_log_message(msg); + */ + return(NULL); + } + if ( type != FT_BINARY){ + cl_log(LOG_WARNING, "field %s is not binary", name); + cl_log_message(LOG_WARNING, msg); + return(NULL); + } + + return(ret); +} + +/* UUIDs are stored with a machine-independent byte ordering (even though it's binary) */ +int +cl_get_uuid(const struct ha_msg *msg, const char * name, cl_uuid_t* retval) +{ + const void * vret; + size_t vretsize; + + cl_uuid_clear(retval); + + if ((vret = cl_get_binary(msg, name, &vretsize)/*discouraged function*/) == NULL) { + /* But perfectly portable in this case */ + return HA_FAIL; + } + if (vretsize != sizeof(cl_uuid_t)) { + cl_log(LOG_WARNING, "Binary field %s is not a uuid.", name); + cl_log(LOG_INFO, "expecting %d bytes, got %d bytes", + (int)sizeof(cl_uuid_t), (int)vretsize); + cl_log_message(LOG_INFO, msg); + return HA_FAIL; + } + memcpy(retval, vret, sizeof(cl_uuid_t)); + return HA_OK; +} + +const char * +cl_get_string(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL || type != FT_STRING){ + return(NULL); + } + + return(ret); + +} + +int +cl_get_type(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL) { + return -1; + } + if (type < 0){ + cl_log(LOG_WARNING, "field %s not a valid type" + , name); + return(-1); + } + + return(type); + +} + +/* +struct ha_msg * +cl_get_struct(const struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type; + size_t vallen; + + ret = cl_get_value(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} +*/ + + +struct ha_msg * +cl_get_struct(struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type = -1; + size_t vallen; + + ret = cl_get_value_mutate(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_UNCOMPRESS: + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} + + +int +cl_msg_list_length(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + return -1; + } + + return g_list_length(ret); + +} + + +void* +cl_msg_list_nth_data(struct ha_msg* msg, const char* name, int n) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return g_list_nth_data(ret, n); + +} + +int +cl_msg_add_list(struct ha_msg* msg, const char* name, GList* list) +{ + int ret; + + if(msg == NULL|| name ==NULL || list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list:" + "invalid arguments"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + return ret; +} + +GList* +cl_msg_get_list(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return ret; +} + + +int +cl_msg_add_list_str(struct ha_msg* msg, const char* name, + char** buf, size_t n) +{ + GList* list = NULL; + int i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "%s:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL",__FUNCTION__); + return HA_FAIL; + } + + for ( i = 0; i < n; i++){ + if (buf[i] == NULL){ + cl_log(LOG_ERR, "%s: %dth element in buf is null", + __FUNCTION__, i); + goto free_and_out; + } + list = g_list_append(list, buf[i]); + if (list == NULL){ + cl_log(LOG_ERR, "%s:adding integer to list failed", + __FUNCTION__); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + free_and_out: + if (list){ + g_list_free(list); + list = NULL; + } + return ret; +} + +static void +list_element_free(gpointer data, gpointer userdata) +{ + if (data){ + g_free(data); + } +} + +int +cl_msg_add_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t n) +{ + + GList* list = NULL; + size_t i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + goto free_and_out; + } + + for ( i = 0; i < n; i++){ + char intstr[MAX_INT_LEN]; + sprintf(intstr,"%d", buf[i]); + list = g_list_append(list, g_strdup(intstr)); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "adding integer to list failed"); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + free_and_out: + if (list){ + g_list_foreach(list,list_element_free , NULL); + g_list_free(list); + list = NULL; + } + + return ret; +} +int +cl_msg_get_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t* n) +{ + GList* list; + size_t len; + int i; + GList* list_element; + + + if (n == NULL || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "invalid parameter(%s)", + !n?"n is NULL": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + return HA_FAIL; + } + + list = cl_msg_get_list(msg, name); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "list of integers %s not found", name); + return HA_FAIL; + } + + len = g_list_length(list); + if (len > *n){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "buffer too small: *n=%ld, required len=%ld", + (long)*n, (long)len); + *n = len; + return HA_FAIL; + } + + *n = len; + i = 0; + list_element = g_list_first(list); + while( list_element != NULL){ + char* intstr = list_element->data; + if (intstr == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + if (sscanf(intstr,"%d", &buf[i]) != 1){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + i++; + list_element = g_list_next(list_element); + } + + return HA_OK; +} + +int +cl_msg_replace_value(struct ha_msg* msg, const void *old_value, + const void* value, size_t vlen, int type) +{ + int j; + + if (msg == NULL || old_value == NULL) { + cl_log(LOG_ERR, "cl_msg_replace: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (old_value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_replace: field %p not found", old_value); + return HA_FAIL; + } + return cl_msg_replace(msg, j, value, vlen, type); +} + +/*this function is for internal use only*/ +int +cl_msg_replace(struct ha_msg* msg, int index, + const void* value, size_t vlen, int type) +{ + void * newv ; + int newlen = vlen; + int oldtype; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || value == NULL) { + cl_log(LOG_ERR, "%s: NULL input.", __FUNCTION__); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "%s:" + "invalid type(%d)",__FUNCTION__, type); + return HA_FAIL; + } + + if (index >= msg->nfields){ + cl_log(LOG_ERR, "%s: index(%d) out of range(%d)", + __FUNCTION__,index, msg->nfields); + return HA_FAIL; + } + + oldtype = msg->types[index]; + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "%s: duplicating message fields failed" + "value=%p, vlen=%d, msg->names[i]=%s", + __FUNCTION__,value, (int)vlen, msg->names[index]); + return HA_FAIL; + } + + fieldtypefuncs[oldtype].memfree(msg->values[index]); + + msg->values[index] = newv; + msg->vlens[index] = newlen; + msg->types[index] = type; + PARANOIDAUDITMSG(msg); + return(HA_OK); + +} + + +static int +cl_msg_mod(struct ha_msg * msg, const char * name, + const void* value, size_t vlen, int type) +{ + int j; + int rc; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || name == NULL || value == NULL) { + cl_log(LOG_ERR, "cl_msg_mod: NULL input."); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "cl_msg_mod:" + "invalid type(%d)", type); + return HA_FAIL; + } + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + + char * newv ; + int newlen = vlen; + + if (type != msg->types[j]){ + cl_log(LOG_ERR, "%s: type mismatch(%d %d)", + __FUNCTION__, type, msg->types[j]); + return HA_FAIL; + } + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "duplicating message fields failed" + "value=%p, vlen=%d, msg->names[j]=%s", + value, (int)vlen, msg->names[j]); + return HA_FAIL; + } + + fieldtypefuncs[type].memfree(msg->values[j]); + msg->values[j] = newv; + msg->vlens[j] = newlen; + PARANOIDAUDITMSG(msg); + return(HA_OK); + } + } + + rc = ha_msg_nadd_type(msg, name,strlen(name), value, vlen, type); + + PARANOIDAUDITMSG(msg); + return rc; +} + +int +cl_msg_modstruct(struct ha_msg * msg, const char* name, + const struct ha_msg* value) +{ + return cl_msg_mod(msg, name, value, 0, FT_STRUCT); +} + +int +cl_msg_modbin(struct ha_msg * msg, const char* name, + const void* value, size_t vlen) +{ + return cl_msg_mod(msg, name, value, vlen, FT_BINARY); + +} +int +cl_msg_moduuid(struct ha_msg * msg, const char* name, + const cl_uuid_t* uuid) +{ + return cl_msg_mod(msg, name, uuid, sizeof(cl_uuid_t), FT_BINARY); +} + + + +/* Modify the value associated with a particular name */ +int +cl_msg_modstring(struct ha_msg * msg, const char * name, const char * value) +{ + return cl_msg_mod(msg, name, value, strlen(value), FT_STRING); +} + + + +/* Return the next message found in the stream */ +struct ha_msg * +msgfromstream(FILE * f) +{ + char buf[MAXMSGLINE]; + char * getsret; + clearerr(f); + /* Skip until we find a MSG_START (hopefully we skip nothing) */ + while(1) { + getsret = fgets(buf, sizeof(buf), f); + if (!getsret) { + break; + } + if (strcmp(buf, MSG_START) == 0) { + return msgfromstream_string(f); + + } + if (strcmp(buf, MSG_START_NETSTRING) == 0){ + return msgfromstream_netstring(f); + } + + } + + return NULL; +} + +/* Return the next message found in the stream with string format */ +struct ha_msg * +msgfromstream_string(FILE * f) +{ + char buf[MAXMSGLINE]; + const char * bufmax = buf + sizeof(buf); + struct ha_msg* ret; + char * getsret; + + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR, "msgfromstream: cannot get message"); + } + return(NULL); + } + + /* Add Name=value pairs until we reach MSG_END or EOF */ + while(1) { + getsret = fgets(buf, MAXMSGLINE, f); + if (!getsret) { + break; + } + + if (strnlen(buf, MAXMSGLINE) > MAXMSGLINE - 2) { + cl_log(LOG_DEBUG + , "msgfromstream: field too long [%s]" + , buf); + } + + if (!strcmp(buf, MSG_END)) { + break; + } + + + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv(ret, buf, bufmax) != HA_OK) { + cl_log(LOG_ERR, "NV failure (msgfromsteam): [%s]" + , buf); + ha_msg_del(ret); ret=NULL; + return(NULL); + } + } + return(ret); +} + + +/* Return the next message found in the stream with netstring format*/ + +struct ha_msg * +msgfromstream_netstring(FILE * f) +{ + struct ha_msg * ret; + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR + , "msgfromstream_netstring(): cannot get message"); + } + return(NULL); + } + + while(1) { + char* nvpair; + int nvlen; + int n; + + if (fscanf(f, "%d:", &nvlen) <= 0 || nvlen <= 0){ + return(ret); + } + + nvpair = malloc(nvlen + 2); + + if ((n =fread(nvpair, 1, nvlen + 1, f)) != nvlen + 1){ + cl_log(LOG_WARNING, "msgfromstream_netstring()" + ": Can't get enough nvpair," + "expecting %d bytes long, got %d bytes", + nvlen + 1, n); + ha_msg_del(ret); + return(NULL); + } + + process_netstring_nvpair(ret, nvpair, nvlen); + + } + +} + +static gboolean ipc_timer_expired = FALSE; + +static void cl_sigalarm_handler(int signum) +{ + if (signum == SIGALRM) { + ipc_timer_expired = TRUE; + } +} + +int +cl_ipc_wait_timeout( + IPC_Channel *chan, int (*waitfun)(IPC_Channel *chan), unsigned int timeout) +{ + int rc = IPC_FAIL; + struct sigaction old_action; + + memset(&old_action, 0, sizeof(old_action)); + cl_signal_set_simple_handler(SIGALRM, cl_sigalarm_handler, &old_action); + + ipc_timer_expired = FALSE; + + alarm(timeout); + rc = waitfun(chan); + if (rc == IPC_INTR && ipc_timer_expired) { + rc = IPC_TIMEOUT; + } + + alarm(0); /* ensure it expires */ + cl_signal_set_simple_handler(SIGALRM, old_action.sa_handler, &old_action); + + + return rc; +} + +/* Return the next message found in the IPC channel */ +static struct ha_msg* +msgfromIPC_ll(IPC_Channel * ch, int flag, unsigned int timeout, int *rc_out) +{ + int rc; + IPC_Message* ipcmsg; + struct ha_msg* hmsg; + int need_auth = flag & MSG_NEEDAUTH; + int allow_intr = flag & MSG_ALLOWINTR; + + startwait: + if(timeout > 0) { + rc = cl_ipc_wait_timeout(ch, ch->ops->waitin, timeout); + } else { + rc = ch->ops->waitin(ch); + } + + if(rc_out) { + *rc_out = rc; + } + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_TIMEOUT: + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + if ( allow_intr){ + goto startwait; + }else{ + return NULL; + } + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if(rc_out) { + *rc_out = rc; + } + + if (rc != IPC_OK) { + return NULL; + } + + hmsg = wirefmt2msg_ll((char *)ipcmsg->msg_body, ipcmsg->msg_len, need_auth); + if (ipcmsg->msg_done) { + ipcmsg->msg_done(ipcmsg); + } + + AUDITMSG(hmsg); + return hmsg; +} + +/* Return the next message found in the IPC channel */ +struct ha_msg* +msgfromIPC_timeout(IPC_Channel *ch, int flag, unsigned int timeout, int *rc_out) +{ + return msgfromIPC_ll(ch, flag, timeout, rc_out); +} + +struct ha_msg* +msgfromIPC(IPC_Channel * ch, int flag) +{ + return msgfromIPC_ll(ch, flag, 0, NULL); +} + + +struct ha_msg* +msgfromIPC_noauth(IPC_Channel * ch) +{ + int flag = 0; + + flag |= MSG_ALLOWINTR; + return msgfromIPC_ll(ch, flag, 0, NULL); +} + +/* Return the next message found in the IPC channel */ +IPC_Message * +ipcmsgfromIPC(IPC_Channel * ch) +{ + int rc; + IPC_Message* ipcmsg; + + rc = ch->ops->waitin(ch); + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + return NULL; + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if (rc != IPC_OK) { + return NULL; + } + + + return(ipcmsg); +} + + +/* Writes a message into a stream - used for serial lines */ +int +msg2stream(struct ha_msg* m, FILE * f) +{ + size_t len; + char * s = msg2wirefmt(m, &len); + + if (s != NULL) { + int rc = HA_OK; + if (fputs(s, f) == EOF) { + rc = HA_FAIL; + cl_perror("msg2stream: fputs failure"); + } + if (fflush(f) == EOF) { + cl_perror("msg2stream: fflush failure"); + rc = HA_FAIL; + } + free(s); + return(rc); + }else{ + return(HA_FAIL); + } +} +static void ipcmsg_done(IPC_Message* m); + +static int clmsg_ipcmsg_allocated = 0; +static int clmsg_ipcmsg_freed = 0; + +void dump_clmsg_ipcmsg_stats(void); +void +dump_clmsg_ipcmsg_stats(void) +{ + cl_log(LOG_INFO, "clmsg ipcmsg allocated=%d, freed=%d, diff=%d", + clmsg_ipcmsg_allocated, + clmsg_ipcmsg_freed, + clmsg_ipcmsg_allocated - clmsg_ipcmsg_freed); + + return; +} + +static void +ipcmsg_done(IPC_Message* m) +{ + if (!m) { + return; + } + if (m->msg_buf) { + free(m->msg_buf); + } + free(m); + m = NULL; + clmsg_ipcmsg_freed ++; +} + + + +/* + * create an ipcmsg and copy the data + */ + +IPC_Message* +wirefmt2ipcmsg(void* p, size_t len, IPC_Channel* ch) +{ + IPC_Message* ret = NULL; + + if (p == NULL){ + return(NULL); + } + + ret = MALLOCT(IPC_Message); + if (!ret) { + return(NULL); + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, p, len); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; + +} + +IPC_Message* +hamsg2ipcmsg(struct ha_msg* m, IPC_Channel* ch) +{ + size_t len; + char * s = msg2wirefmt_ll(m, &len, MSG_NEEDCOMPRESS); + IPC_Message* ret = NULL; + + if (s == NULL) { + return ret; + } + ret = MALLOCT(IPC_Message); + if (!ret) { + free(s); + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(s); + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, s, len); + free(s); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; +} + +struct ha_msg* +ipcmsg2hamsg(IPC_Message*m) +{ + struct ha_msg* ret = NULL; + + + ret = wirefmt2msg(m->msg_body, m->msg_len,MSG_NEEDAUTH); + return ret; +} + +int +msg2ipcchan(struct ha_msg*m, IPC_Channel*ch) +{ + IPC_Message* imsg; + + if (m == NULL || ch == NULL) { + cl_log(LOG_ERR, "Invalid msg2ipcchan argument"); + errno = EINVAL; + return HA_FAIL; + } + + if ((imsg = hamsg2ipcmsg(m, ch)) == NULL) { + cl_log(LOG_ERR, "hamsg2ipcmsg() failure"); + return HA_FAIL; + } + + if (ch->ops->send(ch, imsg) != IPC_OK) { + if (ch->ch_status == IPC_CONNECT) { + snprintf(ch->failreason,MAXFAILREASON, + "send failed,farside_pid=%d, sendq length=%ld(max is %ld)", + ch->farside_pid, (long)ch->send_queue->current_qlen, + (long)ch->send_queue->max_qlen); + } + imsg->msg_done(imsg); + return HA_FAIL; + } + return HA_OK; +} + +static gboolean (*msg_authentication_method)(const struct ha_msg* ret) = NULL; + + +void +cl_set_oldmsgauthfunc(gboolean (*authfunc)(const struct ha_msg*)) +{ + msg_authentication_method = authfunc; +} + + + +/* Converts a string (perhaps received via UDP) into a message */ +struct ha_msg * +string2msg_ll(const char * s, size_t length, int depth, int need_auth) +{ + struct ha_msg* ret; + int startlen; + int endlen; + const char * sp = s; + const char * smax = s + length; + + + if ((ret = ha_msg_new(0)) == NULL) { + cl_log(LOG_ERR, "%s: creating new msg failed", __FUNCTION__); + return(NULL); + } + + startlen = sizeof(MSG_START)-1; + if (strncmp(sp, MSG_START, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "string2msg_ll: no MSG_START"); + cl_log(LOG_WARNING, "%s: s=%s", __FUNCTION__, s); + cl_log(LOG_WARNING, "depth=%d", depth); + } + ha_msg_del(ret); + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END)-1; + + /* Add Name=value pairs until we reach MSG_END or end of string */ + + while (*sp != EOS && strncmp(sp, MSG_END, endlen) != 0) { + + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* Skip over initial CR/NL things */ + sp += strspn(sp, NEWLINE); + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after NEWLINE(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* End of message marker? */ + if (strncmp(sp, MSG_END, endlen) == 0) { + break; + } + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv_depth(ret, sp, smax, depth) != HA_OK) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "NV failure (string2msg_ll):"); + cl_log(LOG_ERR, "Input string: [%s]", s); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "depth=%d", depth); + cl_log_message(LOG_ERR,ret); + } + ha_msg_del(ret); + return(NULL); + } + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after adding field(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + sp += strcspn(sp, NEWLINE); + } + + if (need_auth && msg_authentication_method + && !msg_authentication_method(ret)) { + const char* from = ha_msg_value(ret, F_ORIG); + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, + "string2msg_ll: node [%s]" + " failed authentication", from ? from : "?"); + } + ha_msg_del(ret); + ret = NULL; + } + return(ret); +} + + + +struct ha_msg * +string2msg(const char * s, size_t length) +{ + return(string2msg_ll(s, length, 0, MSG_NEEDAUTH)); +} + + + + + + +/* Converts a message into a string (for sending out UDP interface) + + used in two places: + + 1.called by msg2string as a implementation for computing string for a + message provided the buffer + + 2.called by is_authentic. In this case, there are no start/end string + and the "auth" field is not included in the string + +*/ + +#define NOROOM { \ + cl_log(LOG_ERR, "%s:%d: out of memory bound" \ + ", bp=%p, buf + len=%p, len=%ld" \ + , __FUNCTION__, __LINE__ \ + , bp, buf + len, (long)len); \ + cl_log_message(LOG_ERR, m); \ + return(HA_FAIL); \ + } + +#define CHECKROOM_CONST(c) CHECKROOM_INT(STRLEN_CONST(c)) +#define CHECKROOM_STRING(s) CHECKROOM_INT(strnlen(s, len)) +#define CHECKROOM_STRING_INT(s,i) CHECKROOM_INT(strnlen(s, len)+(i)) +#define CHECKROOM_INT(i) { \ + if ((bp + (i)) > maxp) { \ + NOROOM; \ + } \ + } + + +int +msg2string_buf(const struct ha_msg *m, char* buf, size_t len +, int depth,int needhead) +{ + + char * bp = NULL; + int j; + char* maxp = buf + len; + + buf[0]=0; + bp = buf; + + if (needhead){ + CHECKROOM_CONST(MSG_START); + strcpy(bp, MSG_START); + bp += STRLEN_CONST(MSG_START); + } + + for (j=0; j < m->nfields; ++j) { + + int truelen; + int (*tostring)(char*, char*, void*, size_t, int); + + if (needhead == NOHEAD && strcmp(m->names[j], F_AUTH) == 0) { + continue; + } + + if (m->types[j] != FT_STRING){ + CHECKROOM_STRING_INT(FT_strings[m->types[j]],2); + strcat(bp, "("); + bp++; + strcat(bp, FT_strings[m->types[j]]); + bp++; + strcat(bp,")"); + bp++; + } + + CHECKROOM_STRING_INT(m->names[j],1); + strcat(bp, m->names[j]); + bp += m->nlens[j]; + strcat(bp, "="); + bp++; + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + tostring = fieldtypefuncs[m->types[j]].tostring; + } else { + cl_log(LOG_ERR, "type(%d) unrecognized", m->types[j]); + return HA_FAIL; + } + if (!tostring || + (truelen = tostring(bp, maxp, m->values[j], m->vlens[j], depth)) + < 0){ + cl_log(LOG_ERR, "tostring failed for field %d", j); + return HA_FAIL; + } + + CHECKROOM_INT(truelen+1); + bp +=truelen; + + strcat(bp,"\n"); + bp++; + } + if (needhead){ + CHECKROOM_CONST(MSG_END); + strcat(bp, MSG_END); + bp += strlen(MSG_END); + } + + CHECKROOM_INT(1); + bp[0] = EOS; + + return(HA_OK); +} + + +char * +msg2string(const struct ha_msg *m) +{ + void *buf; + int len; + + AUDITMSG(m); + if (m->nfields <= 0) { + cl_log(LOG_ERR, "msg2string: Message with zero fields"); + return(NULL); + } + + len = get_stringlen(m); + + buf = malloc(len); + + if (buf == NULL) { + cl_log(LOG_ERR, "msg2string: no memory for string"); + return(NULL); + } + + if (msg2string_buf(m, buf, len ,0, NEEDHEAD) != HA_OK){ + cl_log(LOG_ERR, "msg2string: msg2string_buf failed"); + free(buf); + return(NULL); + } + + return(buf); +} + +gboolean +must_use_netstring(const struct ha_msg* msg) +{ + int i; + + for ( i = 0; i < msg->nfields; i++){ + if (msg->types[i] == FT_COMPRESS + || msg->types[i] == FT_UNCOMPRESS + || msg->types[i] == FT_STRUCT){ + return TRUE; + } + } + + return FALSE; + +} + +#define use_netstring(m) (msgfmt == MSGFMT_NETSTRING || must_use_netstring(m)) + +static char* +msg2wirefmt_ll(struct ha_msg*m, size_t* len, int flag) +{ + + int wirefmtlen; + int i; + int netstg = use_netstring(m); + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (use_traditional_compression + &&(flag & MSG_NEEDCOMPRESS) + && (wirefmtlen> compression_threshold) + && cl_get_compress_fns() != NULL){ + return cl_compressmsg(m, len); + } + + if (flag & MSG_NEEDCOMPRESS){ + for (i=0 ;i < m->nfields; i++){ + int type = m->types[i]; + if (fieldtypefuncs[type].prepackaction){ + fieldtypefuncs[type].prepackaction(m,i); + } + } + } + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (wirefmtlen >= MAXMSG){ + if (flag&MSG_NEEDCOMPRESS) { + if (cl_get_compress_fns() != NULL) + return cl_compressmsg(m, len); + } + cl_log(LOG_ERR, "%s: msg too big(%d)", + __FUNCTION__, wirefmtlen); + return NULL; + } + if (flag & MSG_NEEDAUTH) { + return msg2netstring(m, len); + } + return msg2wirefmt_noac(m, len); +} + +char* +msg2wirefmt(struct ha_msg*m, size_t* len){ + return msg2wirefmt_ll(m, len, MSG_NEEDAUTH|MSG_NEEDCOMPRESS); +} + +char* +msg2wirefmt_noac(struct ha_msg*m, size_t* len) +{ + if (use_netstring(m)) { + return msg2netstring_noauth(m, len); + } else { + char *tmp; + + tmp = msg2string(m); + if(tmp == NULL){ + *len = 0; + return NULL; + } + *len = strlen(tmp) + 1; + return tmp; + } +} + +static struct ha_msg* +wirefmt2msg_ll(const char* s, size_t length, int need_auth) +{ + + size_t startlen; + struct ha_msg* msg = NULL; + + + startlen = sizeof(MSG_START)-1; + + if (startlen > length){ + return NULL; + } + + if (strncmp( s, MSG_START, startlen) == 0) { + msg = string2msg_ll(s, length, 0, need_auth); + goto out; + } + + startlen = sizeof(MSG_START_NETSTRING) - 1; + + if (startlen > length){ + return NULL; + } + + if (strncmp(s, MSG_START_NETSTRING, startlen) == 0) { + msg = netstring2msg(s, length, need_auth); + goto out; + } + +out: + if (msg && is_compressed_msg(msg)){ + struct ha_msg* ret; + if ((ret = cl_decompressmsg(msg))==NULL){ + cl_log(LOG_ERR, "decompress msg failed"); + ha_msg_del(msg); + return NULL; + } + ha_msg_del(msg); + return ret; + } + return msg; + +} + + + + +struct ha_msg* +wirefmt2msg(const char* s, size_t length, int flag) +{ + return wirefmt2msg_ll(s, length, flag& MSG_NEEDAUTH); + +} + + +void +cl_log_message (int log_level, const struct ha_msg *m) +{ + int j; + + if(m == NULL) { + cl_log(log_level, "MSG: No message to dump"); + return; + } + + cl_log(log_level, "MSG: Dumping message with %d fields", m->nfields); + + for (j=0; j < m->nfields; ++j) { + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[m->types[j]].display(log_level, j, + m->names[j], + m->values[j], + m->vlens[j]); + } + } +} + + +#ifdef TESTMAIN_MSGS +int +main(int argc, char ** argv) +{ + struct ha_msg* m; + while (!feof(stdin)) { + if ((m=controlfifo2msg(stdin)) != NULL) { + fprintf(stderr, "Got message!\n"); + if (msg2stream(m, stdout) == HA_OK) { + fprintf(stderr, "Message output OK!\n"); + }else{ + fprintf(stderr, "Could not output Message!\n"); + } + }else{ + fprintf(stderr, "Could not get message!\n"); + } + } + return(0); +} +#endif diff --git a/lib/clplumbing/cl_msg_types.c b/lib/clplumbing/cl_msg_types.c new file mode 100644 index 0000000..56cf56a --- /dev/null +++ b/lib/clplumbing/cl_msg_types.c @@ -0,0 +1,1736 @@ +/* + * Heartbeat message type functions + * + * Copyright (C) 2004 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This software licensed under the GNU LGPL. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/utsname.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/base64.h> +#include <clplumbing/netstring.h> +#include <glib.h> + +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + + +extern const char* FT_strings[]; + + + +#define NL_TO_SYM 0 +#define SYM_TO_NL 1 + +static const int SPECIAL_SYMS[MAXDEPTH] = { + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 15, + 16, + 17, + 18, +}; + +#define SPECIAL_SYM 19 + +struct ha_msg* string2msg_ll(const char*, size_t, int, int); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int struct_display_print_spaces(char *buffer, int depth); +int struct_display_as_xml(int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted); +int struct_stringlen(size_t namlen, size_t vallen, const void* value); +int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +int convert_nl_sym(char* s, int len, char sym, int direction); +int bytes_for_int(int x); + +int +bytes_for_int(int x) +{ + int len = 0; + if(x < 0) { + x = 0-x; + len=1; + } + while(x > 9) { + x /= 10; + len++; + } + return len+1; +} + +int +netstring_extra(int x) +{ + return (bytes_for_int(x) + x + 2); +} + +int +get_netstringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_netstringlen:" + "asking netstringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START_NETSTRING) + + sizeof(MSG_END_NETSTRING) -2 ; + + + for (i = 0; i < m->nfields; i++){ + int len; + len = fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + total_len += netstring_extra(len); + } + + + return total_len; + + +} + + + +int +get_stringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_stringlen:" + "asking stringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START)+sizeof(MSG_END)-1; + + for (i = 0; i < m->nfields; i++){ + total_len += fieldtypefuncs[m->types[i]].stringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + } + + return total_len; +} + + + +/* + compute the total size of the resulted string + if the string list is to be converted + +*/ +size_t +string_list_pack_length(const GList* _list) +{ + size_t i; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + size_t total_length = 0; + + if (list == NULL){ + cl_log(LOG_WARNING, "string_list_pack_length():" + "list is NULL"); + + return 0; + } + for (i = 0; i < g_list_length(list) ; i++){ + + int len = 0; + char * element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack_length: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + len = strlen(element); + total_length += bytes_for_int(len) + len + 2; + /* 2 is for ":" and "," */ + } + return total_length ; +} + + + +/* + convert a string list into a single string + the format to convert is similar to netstring: + <length> ":" <the actual string> "," + + for example, a list containing two strings "abc" "defg" + will be converted into + 3:abc,4:defg, + @list: the list to be converted + @buf: the converted string should be put in the @buf + @maxp: max pointer +*/ + + +int +string_list_pack(GList* list, char* buf, char* maxp) +{ + size_t i; + char* p = buf; + + for (i = 0; i < g_list_length(list) ; i++){ + char * element = g_list_nth_data(list, i); + int element_len; + + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + element_len = strlen(element); + if (p + 2 + element_len + bytes_for_int(element_len)> maxp){ + cl_log(LOG_ERR, "%s: memory out of boundary", + __FUNCTION__); + return 0; + } + p += sprintf(p, "%d:%s,", element_len,element); + + if (p > maxp){ + cl_log(LOG_ERR, "string_list_pack: " + "buffer overflowed "); + return 0; + } + } + + + return (p - buf); +} + + + +/* + this is reverse process of pack_string_list +*/ +GList* +string_list_unpack(const char* packed_str_list, size_t length) +{ + GList* list = NULL; + const char* psl = packed_str_list; + const char * maxp= packed_str_list + length; + int len = 0; + + + while(TRUE){ + char* buf; + + if (*psl == '\0' || psl >= maxp){ + break; + } + + if (sscanf( psl, "%d:", &len) <= 0 ){ + break; + } + + if (len <=0){ + cl_log(LOG_ERR, "unpack_string_list:" + "reading len of string error"); + if (list){ + list_cleanup(list); + } + return NULL; + } + + while (*psl != ':' && *psl != '\0' ){ + psl++; + } + + if (*psl == '\0'){ + break; + } + + psl++; + + buf = malloc(len + 1); + if (buf == NULL){ + cl_log(LOG_ERR, "unpack_string_list:" + "unable to allocate buf"); + if(list){ + list_cleanup(list); + } + return NULL; + + } + memcpy(buf, psl, len); + buf[len] = '\0'; + list = g_list_append(list, buf); + psl +=len; + + if (*psl != ','){ + cl_log(LOG_ERR, "unpack_string_list:" + "wrong format, s=%s",packed_str_list); + } + psl++; + } + + return list; + +} + + +static void +string_memfree(void* value) +{ + if (value){ + free(value); + }else { + cl_log(LOG_ERR, "string_memfree: " + "value is NULL"); + } + + + return; +} + +static void +binary_memfree(void* value) +{ + string_memfree(value); +} + + +static void +struct_memfree( void* value) +{ + struct ha_msg* msg; + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + msg = (struct ha_msg*) value; + ha_msg_del(msg); + return ; +} + +static void +list_memfree(void* value) +{ + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + list_cleanup(value); + +} + + +static void* +binary_dup(const void* value, size_t len) +{ + + char* dupvalue; + + /* 0 byte binary field is allowed*/ + + if (value == NULL && len > 0){ + cl_log(LOG_ERR, "binary_dup:" + "NULL value with non-zero len=%d", + (int)len); + return NULL; + } + + dupvalue = malloc(len + 1); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "binary_dup:" + "malloc failed"); + return NULL; + } + + if (value != NULL) { + memcpy(dupvalue, value, len); + } + + dupvalue[len] =0; + + return dupvalue; +} + +static void* +string_dup(const void* value, size_t len) +{ + return binary_dup(value, len); +} + + +static void* +struct_dup(const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + + dupvalue = (void*)ha_msg_copy((const struct ha_msg*)value); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "struct_dup: " + "ha_msg_copy failed"); + return NULL; + } + + return dupvalue; +} + +static GList* +list_copy(const GList* _list) +{ + size_t i; + GList* newlist = NULL; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + + for (i = 0; i < g_list_length(list); i++){ + char* dup_element = NULL; + char* element = g_list_nth_data(list, i); + int len; + if (element == NULL){ + cl_log(LOG_WARNING, "list_copy:" + "element is NULL"); + continue; + } + + len = strlen(element); + dup_element= malloc(len + 1); + if ( dup_element == NULL){ + cl_log(LOG_ERR, "duplicate element failed"); + continue; + } + memcpy(dup_element, element,len); + dup_element[len] = 0; + + newlist = g_list_append(newlist, dup_element); + } + + return newlist; +} + +static void* +list_dup( const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + dupvalue = (void*)list_copy((const GList*)value); + + if (!dupvalue){ + cl_log(LOG_ERR, "list_dup: " + "list_copy failed"); + return NULL; + } + + return dupvalue; +} + + +static void +general_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int netslen; + int slen; + HA_MSG_ASSERT(value); + HA_MSG_ASSERT(name); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + +} +static void +string_display(int log_level, int seq, char* name, void* value, int vlen) +{ + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + cl_log(log_level, "MSG[%d] : [%s=%s]", + seq, name, (const char*)value); + return; +} + +static void +binary_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_display(log_level, seq, name, value, vlen, FT_BINARY); +} + +static void +compress_display(int log_level, int seq, char* name, void* value, int vlen){ + general_display(log_level, seq, name, value, vlen, FT_COMPRESS); +} + + +static void +general_struct_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int slen; + int netslen; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + if(cl_get_string((struct ha_msg*) value, F_XML_TAGNAME) == NULL) { + cl_log_message(log_level, (struct ha_msg*) value); + } else { + /* use a more friendly output format for nested messages */ + struct_display_as_xml(log_level, 0, value, NULL, TRUE); + } +} +static void +struct_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_STRUCT); + +} +static void +uncompress_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_UNCOMPRESS); +} + +#define update_buffer_head(buffer, len) if(len < 0) { \ + (*buffer) = EOS; return -1; \ + } else { \ + buffer += len; \ + } + +int +struct_display_print_spaces(char *buffer, int depth) +{ + int lpc = 0; + int spaces = 2*depth; + /* <= so that we always print 1 space - prevents problems with syslog */ + for(lpc = 0; lpc <= spaces; lpc++) { + if(sprintf(buffer, "%c", ' ') < 1) { + return -1; + } + buffer += 1; + } + return lpc; +} + +int +struct_display_as_xml( + int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted) +{ + int lpc = 0; + int printed = 0; + gboolean has_children = FALSE; + char print_buffer[1000]; + char *buffer = print_buffer; + const char *name = cl_get_string(data, F_XML_TAGNAME); + + if(data == NULL) { + return 0; + + } else if(name == NULL) { + cl_log(LOG_WARNING, "Struct at depth %d had no name", depth); + cl_log_message(log_level, data); + return 0; + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + + printed = sprintf(buffer, "<%s", name); + update_buffer_head(buffer, printed); + + for (lpc = 0; lpc < data->nfields; lpc++) { + const char *prop_name = data->names[lpc]; + const char *prop_value = data->values[lpc]; + if(data->types[lpc] != FT_STRING) { + continue; + } else if(prop_name == NULL) { + continue; + } else if(prop_name[0] == '_' && prop_name[1] == '_') { + continue; + } + printed = sprintf(buffer, " %s=\"%s\"", prop_name, prop_value); + update_buffer_head(buffer, printed); + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] == FT_STRUCT) { + has_children = TRUE; + break; + } + } + + printed = sprintf(buffer, "%s>", has_children==0?"/":""); + update_buffer_head(buffer, printed); + cl_log(log_level, "%s%s", prefix?prefix:"", print_buffer); + buffer = print_buffer; + + if(has_children == FALSE) { + return 0; + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] != FT_STRUCT) { + continue; + } else if(0 > struct_display_as_xml( + log_level, depth+1, data->values[lpc], + prefix, formatted)) { + return -1; + } + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + cl_log(log_level, "%s%s</%s>", prefix?prefix:"", print_buffer, name); + + return 0; +} + + + + +static int +liststring(GList* list, char* buf, int maxlen) +{ + char* p = buf; + char* maxp = buf + maxlen; + size_t i; + + for ( i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL) { + cl_log(LOG_ERR, "%luth element is NULL " + , (unsigned long)i); + return HA_FAIL; + } else{ + if (i == 0){ + p += sprintf(p,"%s",element); + }else{ + p += sprintf(p," %s",element); + } + + } + if ( p > maxp){ + cl_log(LOG_ERR, "buffer overflow"); + return HA_FAIL; + } + + } + + return HA_OK; +} + +static void +list_display(int log_level, int seq, char* name, void* value, int vlen) +{ + GList* list; + char buf[MAXLENGTH]; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + list = value; + + if (liststring(list, buf, MAXLENGTH) != HA_OK){ + cl_log(LOG_ERR, "liststring error"); + return; + } + cl_log(log_level, "MSG[%d] :[(%s)%s=%s]", + seq, FT_strings[FT_LIST], + name, buf); + + return ; + +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +int +convert_nl_sym(char* s, int len, char sym, int direction) +{ + int i; + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert_nl_sym(): direction not defined!"); + return(HA_FAIL); + } + + + for (i = 0; i < len && s[i] != EOS; i++){ + + switch(direction){ + case NL_TO_SYM : + if (s[i] == '\n'){ + s[i] = sym; + break; + } + + if (s[i] == sym){ + cl_log(LOG_ERR + , "convert_nl_sym(): special symbol \'0x%x\' (%c) found" + " in string at %d (len=%d)", s[i], s[i], i, len); + i -= 10; + if(i < 0) { + i = 0; + } + cl_log(LOG_ERR, "convert_nl_sym(): %s", s + i); + return(HA_FAIL); + } + + break; + + case SYM_TO_NL: + + if (s[i] == sym){ + s[i] = '\n'; + break; + } + break; + default: + /* nothing, never executed*/; + + } + } + + return(HA_OK); +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +static int +convert(char* s, int len, int depth, int direction) +{ + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert(): direction not defined!"); + return(HA_FAIL); + } + + + if (depth >= MAXDEPTH ){ + cl_log(LOG_ERR, "convert(): MAXDEPTH exceeded: %d", depth); + return(HA_FAIL); + } + + return convert_nl_sym(s, len, SPECIAL_SYMS[depth], direction); +} + + + + +static int +string_stringlen(size_t namlen, size_t vallen, const void* value) +{ + + HA_MSG_ASSERT(value); +/* HA_MSG_ASSERT( vallen == strlen(value)); */ + return namlen + vallen + 2; +} + +static int +binary_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int length; + + HA_MSG_ASSERT(value); + + length = 3 + namlen + 1 + vallen; + + return length; +} + + +static int +string_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + HA_MSG_ASSERT( vallen == strlen(value)); + + return binary_netstringlen(namlen, vallen, value); +} + + +static int +binary_stringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + + return namlen + B64_stringlen(vallen) + 2 + 3; + /*overhead 3 is for type*/ +} + + + + + +int +struct_stringlen(size_t namlen, size_t vallen, const void* value) +{ + const struct ha_msg* childmsg; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + return namlen +2 + 3 + get_stringlen(childmsg); + /*overhead 3 is for type*/ +} + +int +struct_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + + int ret; + const struct ha_msg* childmsg; + int len; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + len = get_netstringlen(childmsg); + + ret = 3 + namlen + 1 + len; + + return ret; + +} + + +static int +list_stringlen(size_t namlen, size_t vallen, const void* value) +{ + (void)value; + return namlen + vallen + 2 + 3; + /*overhead 3 is for type (FT_STRUCT)*/ +} + +static int +list_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int ret; + const GList* list; + + list = (const GList*)value; + + ret = 3 + namlen + 1 + string_list_pack_length(list); + + return ret; + +} + +static int +add_binary_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_BINARY; + msg->nfields++; + + return HA_OK; +} + + +static int +add_struct_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_STRUCT; + + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_list_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + int j; + GList* list = NULL; + + if ( !msg || !name || !value + || namelen <= 0 + || vallen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_list_field:" + " invalid input argument"); + return HA_FAIL; + } + + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + break; + } + } + + if ( j >= msg->nfields){ + list = (GList*)value; + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_LIST; + msg->nfields++; + + } else if( msg->types[j] == FT_LIST ){ + + GList* oldlist = (GList*) msg->values[j]; + int listlen; + size_t i; + + for ( i =0; i < g_list_length((GList*)value); i++){ + list = g_list_append(oldlist, g_list_nth_data((GList*)value, i)); + } + if (list == NULL){ + cl_log(LOG_ERR, "add_list_field:" + " g_list_append() failed"); + return HA_FAIL; + } + + listlen = string_list_pack_length(list); + + msg->values[j] = list; + msg->vlens[j] = listlen; + g_list_free((GList*)value); /*we don't free each element + because they are used in new list*/ + free(name); /* this name is no longer necessary + because msg->names[j] is reused */ + + } else { + cl_log(LOG_ERR, "field already exists " + "with differnt type=%d", msg->types[j]); + return (HA_FAIL); + } + + return HA_OK; +} + + +static int +add_compress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_COMPRESS; + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_uncompress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_UNCOMPRESS; + + msg->nfields++; + + return HA_OK; +} + + + +/*print a string to a string, + pretty simple one :) +*/ +static int +str2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + char* s = value; + char* p = buf; + (void)maxp; + (void)depth; + + if (buf + len > maxp){ + cl_log(LOG_ERR, "%s: out of boundary", + __FUNCTION__); + return -1; + } + + if ( strlen(s) != len){ + cl_log(LOG_ERR, "str2string:" + "the input len != string length"); + return -1; + } + + strcat(buf, s); + while(*p != '\0'){ + if (*p == '\n'){ + *p = SPECIAL_SYM; + } + p++; + } + + return len; + +} + +/*print a binary value to a string using base64 + library +*/ + +static int +binary2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int baselen; + int truelen = 0; + + (void)depth; + baselen = B64_stringlen(len) + 1; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "binary2string: out of bounary"); + return -1; + } + + truelen = binary_to_base64(value, len, buf, baselen); + + return truelen; +} + +/*print a struct(ha_msg) to a string + @depth denotes the number of recursion +*/ + +static int +struct2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + + struct ha_msg* msg = value; + int baselen = get_stringlen(msg); + + (void)len; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "struct2string: not enough buffer" + "for the struct to generate a string"); + return -1; + } + + if (msg2string_buf(msg, buf ,baselen,depth + 1, NEEDHEAD) + != HA_OK){ + + cl_log(LOG_ERR + , "struct2string(): msg2string_buf for" + " child message failed"); + return -1; + + } + + if (convert(buf, baselen, depth, NL_TO_SYM) != HA_OK){ + cl_log(LOG_ERR , "struct2string(): convert failed"); + return -1; + } + + return strlen(buf); +} + + + + +/* print a list to a string + */ + +static int +list2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int listlen; + GList* list = (GList*) value; + + (void)len; + (void)depth; + listlen = string_list_pack(list , buf, maxp); + if (listlen == 0){ + cl_log(LOG_ERR, "list2string():" + "string_list_pack() failed"); + return -1; + } + + return listlen; + +} + + +static int +string2str(void* value, size_t len, int depth, void** nv, size_t* nlen ) +{ + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2str:invalid input"); + return HA_FAIL; + } + + if (convert_nl_sym(value, len, SPECIAL_SYM, SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR, "string2str:convert_nl_sym" + "from symbol to new line failed"); + return HA_FAIL; + } + *nv = value; + *nlen = len; + + return HA_OK; +} + +static int +string2binary(void* value, size_t len, int depth, void** nv, size_t* nlen) +{ + char tmpbuf[MAXLINE]; + char* buf = NULL; + int buf_malloced = 0; + int ret = HA_FAIL; + if (len > MAXLINE){ + buf = malloc(len); + if (buf == NULL){ + cl_log(LOG_ERR, "%s: malloc failed", + __FUNCTION__); + goto out; + } + buf_malloced = 1; + }else { + buf = &tmpbuf[0]; + } + + if (value == NULL && len == 0){ + *nv = NULL; + *nlen = 0; + ret = HA_OK; + goto out; + } + + if ( !value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2binary:invalid input"); + ret = HA_FAIL; + goto out; + } + + memcpy(buf, value, len); + *nlen = base64_to_binary(buf, len, value, len); + + *nv = value; + ret = HA_OK; + out: + if (buf_malloced && buf){ + free(buf); + } + return ret; +} + +static int +string2struct(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + + struct ha_msg *tmpmsg; + + if (!value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + + if (convert(value, vallen, depth,SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): convert failed"); + return(HA_FAIL); + } + + tmpmsg = string2msg_ll(value, vallen,depth + 1, 0); + if (tmpmsg == NULL){ + cl_log(LOG_ERR + , "string2struct()" + ": string2msg_ll failed"); + return(HA_FAIL); + } + free(value); + *nv = tmpmsg; + *nlen = 0; + + return HA_OK; + +} + +static int +string2list(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + GList* list; + + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + list = string_list_unpack(value, vallen); + if (list == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw_ll():" + "unpack_string_list failed: %s", (char*)value); + return(HA_FAIL); + } + free(value); + + *nv = (void*)list; + *nlen = string_list_pack_length(list); + + return HA_OK; + +} + +static int +fields2netstring(char* sp, char* smax, char* name, size_t nlen, + void* value, size_t vallen, int type, size_t* comlen) +{ + size_t fieldlen; + size_t slen; + int ret = HA_OK; + char* sp_save = sp; + char* tmpsp; + + fieldlen = fieldtypefuncs[type].netstringlen(nlen, vallen, value); + /* this check seems to be superfluous because of the next one + if (fieldlen > MAXMSG){ + cl_log(LOG_INFO, "%s: field too big(%d)", __FUNCTION__, (int)fieldlen); + return HA_FAIL; + } + */ + tmpsp = sp + netstring_extra(fieldlen); + if (tmpsp > smax){ + cl_log(LOG_ERR, "%s: memory out of boundary, tmpsp=%p, smax=%p", + __FUNCTION__, tmpsp, smax); + return HA_FAIL; + } + sp += sprintf(sp , "%d:(%d)%s=", (int)fieldlen, type, name); + switch (type){ + + case FT_STRING: + case FT_BINARY: + case FT_COMPRESS: + memcpy(sp, value, vallen); + slen = vallen; + break; + + case FT_UNCOMPRESS: + case FT_STRUCT: + { + struct ha_msg* msg = (struct ha_msg*) value; + /* infinite recursion? Must say that I got lost at + * this point + */ + ret = msg2netstring_buf(msg, sp,get_netstringlen(msg), + &slen); + break; + } + case FT_LIST: + { + + char buf[MAXLENGTH]; + GList* list = NULL; + int tmplen; + + list = (GList*) value; + + tmplen = string_list_pack_length(list); + if (tmplen >= MAXLENGTH){ + cl_log(LOG_ERR, + "string list length exceeds limit"); + return(HA_FAIL); + } + + if (string_list_pack(list, buf, buf + MAXLENGTH) + != tmplen ){ + cl_log(LOG_ERR, + "packing string list return wrong length"); + return(HA_FAIL); + } + + + memcpy(sp, buf, tmplen); + slen = tmplen; + ret = HA_OK; + break; + } + + default: + ret = HA_FAIL; + cl_log(LOG_ERR, "%s: Wrong type (%d)", __FUNCTION__,type); + } + + if (ret == HA_FAIL){ + return ret; + } + + sp +=slen; + *sp++ = ','; + *comlen = sp - sp_save; + + return HA_OK; + + +} + + +static int +netstring2string(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + char* dupvalue; + + if (value == NULL && vlen == 0){ + *retvalue = NULL; + *ret_vlen = 0; + return HA_OK; + } + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2string:" + "invalid input arguments"); + return HA_FAIL; + } + + dupvalue = binary_dup(value, vlen); + if (!dupvalue){ + cl_log(LOG_ERR, "netstring2string:" + "duplicating value failed"); + return HA_FAIL; + } + + *retvalue = dupvalue; + *ret_vlen = vlen; + + return HA_OK; +} + +static int +netstring2binary(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + return netstring2string(value, vlen, retvalue, ret_vlen); + +} + +static int +netstring2struct(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + struct ha_msg* msg; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + msg = netstring2msg(value, vlen, 0); + if (!msg){ + cl_log(LOG_ERR, "netstring2struct:" + "netstring2msg failed"); + return HA_FAIL; + } + + *retvalue =(void* ) msg; + *ret_vlen = 0; + + return HA_OK; + +} + +static int +netstring2list(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + GList* list; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + + list = string_list_unpack(value, vlen); + if (list == NULL){ + cl_log(LOG_ERR, "netstring2list: unpacking string list failed"); + cl_log(LOG_INFO, "thisbuf=%s", (const char*)value); + return HA_FAIL; + } + *retvalue = (void*)list; + + *ret_vlen = string_list_pack_length(list); + + return HA_OK; + +} + + + + + +static int +add_string_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + size_t internal_type; + unsigned long tmptype; + char *cp_name = NULL; + size_t cp_namelen; + size_t cp_vallen; + void *cp_value = NULL; + int next; + + if ( !msg || !name || !value + || namelen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_string_field:" + " invalid input argument"); + return HA_FAIL; + } + + + + internal_type = FT_STRING; + if (name[0] == '('){ + + int nlo = 3; /*name length overhead */ + if (name[2] != ')'){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): no closing parentheses"); + } + return(HA_FAIL); + } + tmptype = name[1] - '0'; + if (tmptype < 0 || tmptype > 9) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): not a number."); + return(HA_FAIL); + } + + internal_type = tmptype; + + if (internal_type == FT_STRING){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): wrong type"); + return(HA_FAIL); + } + + cp_name = name; + cp_namelen = namelen - nlo ; + memmove(cp_name, name + nlo, namelen - nlo); + cp_name[namelen - nlo] = EOS; + }else { + cp_name = name; + cp_namelen = namelen; + + } + + if(internal_type < DIMOF(fieldtypefuncs)){ + int (*stringtofield)(void*, size_t, int depth, void**, size_t* ); + int (*fieldstringlen)( size_t, size_t, const void*); + + stringtofield= fieldtypefuncs[internal_type].stringtofield; + + if (!stringtofield || stringtofield(value, vallen, depth, &cp_value, &cp_vallen) != HA_OK){ + cl_log(LOG_ERR, "add_string_field: stringtofield failed"); + return HA_FAIL; + } + + fieldstringlen = fieldtypefuncs[internal_type].stringlen; + if (!fieldstringlen || + fieldstringlen(cp_namelen, cp_vallen, cp_value) <= 0 ){ + + cl_log(LOG_ERR, "add_string_field: stringlen failed"); + return HA_FAIL; + } + + } else { + cl_log(LOG_ERR, "add_string_field():" + " wrong type %lu", (unsigned long)internal_type); + return HA_FAIL; + } + + + next = msg->nfields; + msg->values[next] = cp_value; + msg->vlens[next] = cp_vallen; + msg->names[next] = cp_name; + msg->nlens[next] = cp_namelen; + msg->types[next] = internal_type; + msg->nfields++; + + return HA_OK; + +} + +static int +uncompress2compress(struct ha_msg* msg, int index) +{ + char* buf; + size_t buflen = MAXMSG; + int rc = HA_FAIL; + + buf = malloc(buflen); + if (!buf) { + cl_log(LOG_ERR, "%s: failed to allocate buffer", + __FUNCTION__); + goto err; + } + + if (msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: the %dth field is not FT_UNCOMPRESS type", + __FUNCTION__, index); + goto err; + } + + + if (cl_compress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compressing %dth field failed", __FUNCTION__, index); + goto err; + } + + rc = cl_msg_replace(msg, index, buf, buflen, FT_COMPRESS); + +err: + if (buf) { + free(buf); + } + + return rc; +} + +static int +compress2uncompress(struct ha_msg* msg, int index) +{ + char *buf = NULL; + size_t buflen = MAXUNCOMPRESSED; + struct ha_msg* msgfield; + int err = HA_FAIL; + + buf = malloc(buflen); + + if (!buf) { + cl_log(LOG_ERR, "%s: allocating buffer for uncompression failed", + __FUNCTION__); + goto out; + } + + if (cl_decompress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compress field failed", + __FUNCTION__); + goto out; + } + + msgfield = wirefmt2msg(buf, buflen, 0); + if (msgfield == NULL){ + cl_log(LOG_ERR, "%s: wirefmt to msg failed", + __FUNCTION__); + goto out; + } + + err = cl_msg_replace(msg, index, (char*)msgfield, 0, FT_UNCOMPRESS); + + ha_msg_del(msgfield); + +out: + if (buf) { + free(buf); + } + + return err; +} + +/* + * string FT_STRING + * string is the basic type used in heartbeat, it is used for printable ascii value + * + * binary FT_BINARY + * binary means the value can be any binary value, including non-printable ascii value + * + * struct FT_STRUCT + * struct means the value is also an ha_msg (actually it is a pointer to an ha message) + * + * list FT_LIST + * LIST means the value is a GList. Right now we only suppport a Glist of strings + * + * compress FT_COMPRESS + * This field and the next one(FT_UNCOMPRESS) is designed to optimize compression in message + * (see cl_compression.c for more about compression). This field is similar to the binary field. + * It stores a compressed field, which will be an ha_msg if uncompressed. Most of time this field + * act like a binary field until compress2uncompress() is called. That function will be called + * when someone calls cl_get_struct() to get this field value. After that this field is converted + * to a new type FT_UNCOMPRESS + * + * uncompress FT_UNCOMPRESS + * As said above, this field is used to optimize compression. This field is similar to the struct + * field. It's value is a pointer to an ha_msg. This field will be converted to a new type FT_COMPRESS + * when msg2wirefmt() is called, where uncompress2compress is called to do the field compression + */ + +struct fieldtypefuncs_s fieldtypefuncs[NUM_MSG_TYPES]= + { {string_memfree, string_dup, string_display, add_string_field, + string_stringlen,string_netstringlen, str2string,fields2netstring, + string2str, netstring2string, NULL, NULL}, + + {binary_memfree, binary_dup, binary_display, add_binary_field, + binary_stringlen,binary_netstringlen, binary2string,fields2netstring, + string2binary, netstring2binary, NULL, NULL}, + + {struct_memfree, struct_dup, struct_display, add_struct_field, + struct_stringlen, struct_netstringlen, struct2string, fields2netstring, \ + string2struct, netstring2struct, NULL, NULL}, + + {list_memfree, list_dup, list_display, add_list_field, + list_stringlen, list_netstringlen, list2string, fields2netstring, + string2list, netstring2list, NULL, NULL}, + + {binary_memfree, binary_dup, compress_display, add_compress_field, + binary_stringlen,binary_netstringlen, binary2string ,fields2netstring, + string2binary , netstring2binary, NULL, compress2uncompress}, /*FT_COMPRESS*/ + + {struct_memfree, struct_dup, uncompress_display, add_uncompress_field, + struct_stringlen, struct_netstringlen, NULL , fields2netstring, + NULL , netstring2struct, uncompress2compress, NULL}, /*FT_UNCOMPRSS*/ + }; + + diff --git a/lib/clplumbing/cl_netstring.c b/lib/clplumbing/cl_netstring.c new file mode 100644 index 0000000..f4040e0 --- /dev/null +++ b/lib/clplumbing/cl_netstring.c @@ -0,0 +1,570 @@ +/* + * netstring implementation + * + * Copyright (c) 2003 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/ipc.h> +#include <clplumbing/netstring.h> +#include <clplumbing/base64.h> +#include <assert.h> +#include <ctype.h> + +/* + * Avoid sprintf. Use snprintf instead, even if you count your bytes. + * It can detect calculation errors (if used properly) + * and will not make the security audit tools crazy. + */ + +#define MAX_AUTH_BYTES 64 + + +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int is_auth_netstring(const char*, size_t, const char*, size_t); +char* msg2netstring(const struct ha_msg*, size_t*); +int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +extern int bytes_for_int(int x); +extern const char * FT_strings[]; + +static int (*authmethod)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen) = NULL; + +void +cl_set_authentication_computation_method(int (*method)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen)) +{ + authmethod = method; +} + +int cl_parse_int(const char *sp, const char *smax, int* len); + +int +cl_parse_int(const char *sp, const char *smax, int* len) +{ + char ch = 0; + int offset = 0; + *len = 0; + + errno = 0; + for( ; sp+offset < smax; offset++) { + ch = sp[offset] - '0'; + if(ch > 9) { /* ch >= 0 is implied by the data type*/ + break; + } + *len *= 10; + *len += ch; + } + + if(offset == 0) { + cl_log(LOG_ERR, + "cl_parse_int: Couldn't parse an int from: %.5s", sp); + } + return offset; +} + +int +compose_netstring(char * s, const char * smax, const char* data, + size_t len, size_t* comlen) +{ + + char * sp = s; + + /* 2 == ":" + "," */ + if (s + len + 2 + bytes_for_int(len) > smax) { + cl_log(LOG_ERR, + "netstring pointer out of boundary(compose_netstring)"); + return(HA_FAIL); + } + + sp += sprintf(sp, "%ld:", (long)len); + + if(data){ + memcpy(sp, data, len); + } + sp += len; + *sp++ = ','; + + *comlen = sp - s; + + return(HA_OK); +} + + + +/* Converts a message into a netstring */ + +int +msg2netstring_buf(const struct ha_msg *m, char *s, + size_t buflen, size_t * slen) +{ + int i; + char * sp; + char * smax; + int ret = HA_OK; + + sp = s; + smax = s + buflen; + + strcpy(sp, MSG_START_NETSTRING); + + sp += strlen(MSG_START_NETSTRING); + + for (i=0; i < m->nfields; i++) { + size_t flen; + int tmplen; + + /* some of these functions in its turn invoke us again */ + ret = fieldtypefuncs[m->types[i]].tonetstring(sp, + smax, + m->names[i], + m->nlens[i], + m->values[i], + m->vlens[i], + m->types[i], + &flen); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "encoding msg to netstring failed"); + cl_log_message(LOG_ERR, m); + return ret; + } + + tmplen = netstring_extra(fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i])); + + if (flen != tmplen ){ + cl_log(LOG_ERR,"netstring len discrepency: actual usage is %d bytes" + "it should use %d", (int)flen, tmplen); + } + sp +=flen; + + } + + if (sp + strlen(MSG_END_NETSTRING) > smax){ + cl_log(LOG_ERR, "%s: out of boundary for MSG_END_NETSTRING", + __FUNCTION__); + return HA_FAIL; + } + strcpy(sp, MSG_END_NETSTRING); + sp += sizeof(MSG_END_NETSTRING) -1; + + if (sp > smax){ + cl_log(LOG_ERR, + "msg2netstring: exceed memory boundary sp =%p smax=%p", + sp, smax); + return(HA_FAIL); + } + + *slen = sp - s; + return(HA_OK); +} + + +int get_netstringlen_auth(const struct ha_msg* m); + +int get_netstringlen_auth(const struct ha_msg* m) +{ + int len = get_netstringlen(m) + MAX_AUTH_BYTES; + return len; +} + + + +static char * +msg2netstring_ll(const struct ha_msg *m, size_t * slen, int need_auth) +{ + int len; + char* s; + int authnum; + char authtoken[MAXLINE]; + char authstring[MAXLINE]; + char* sp; + size_t payload_len; + char* smax; + + len= get_netstringlen_auth(m) + 1; + + /* use MAXUNCOMPRESSED for the in memory size check */ + if (len >= MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg is too large; len=%d," + " MAX msg allowed=%d", __FUNCTION__, len, MAXUNCOMPRESSED); + return NULL; + } + + s = calloc(1, len); + if (!s){ + cl_log(LOG_ERR, "%s: no memory for netstring", __FUNCTION__); + return(NULL); + } + + smax = s + len; + + if (msg2netstring_buf(m, s, len, &payload_len) != HA_OK){ + cl_log(LOG_ERR, "%s: msg2netstring_buf() failed", __FUNCTION__); + free(s); + return(NULL); + } + + sp = s + payload_len; + + if ( need_auth && authmethod){ + int auth_strlen; + + authnum = authmethod(-1, s, payload_len, authtoken,sizeof(authtoken)); + if (authnum < 0){ + cl_log(LOG_WARNING + , "Cannot compute message authentication!"); + free(s); + return(NULL); + } + + sprintf(authstring, "%d %s", authnum, authtoken); + auth_strlen = strlen(authstring); + if (sp + 2 + auth_strlen + bytes_for_int(auth_strlen) >= smax){ + cl_log(LOG_ERR, "%s: out of boundary for auth", __FUNCTION__); + free(s); + return NULL; + } + sp += sprintf(sp, "%ld:%s,", (long)strlen(authstring), authstring); + + } + *slen = sp - s; + + return(s); +} + +char * +msg2netstring(const struct ha_msg *m, size_t * slen) +{ + char* ret; + ret = msg2netstring_ll(m, slen, TRUE); + + return ret; + +} +char * +msg2netstring_noauth(const struct ha_msg *m, size_t * slen) +{ + char * ret; + + ret = msg2netstring_ll(m, slen, FALSE); + + return ret; +} + + +/* + * Peel one string off in a netstring + */ + +static int +peel_netstring(const char * s, const char * smax, int* len, + const char ** data, int* parselen ) +{ + int offset = 0; + const char * sp = s; + + if (sp >= smax){ + return(HA_FAIL); + } + + offset = cl_parse_int(sp, smax, len); + if (*len < 0 || offset <= 0){ + cl_log(LOG_ERR, "peel_netstring: Couldn't parse an int starting at: %.5s", sp); + return(HA_FAIL); + } + + sp = sp+offset; + while (*sp != ':' && sp < smax) { + sp ++; + } + + if (sp >= smax) { + return(HA_FAIL); + } + + sp ++; + + *data = sp; + + sp += (*len); + if (sp >= smax) { + return(HA_FAIL); + } + if (*sp != ','){ + return(HA_FAIL); + } + sp++; + + *parselen = sp - s; + + return(HA_OK); +} + + +int +process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen) +{ + + const char *name; + int nlen; + const char *ns_value; + int ns_vlen; + void *value; + size_t vlen; + int type; + void (*memfree)(void*); + int ret = HA_OK; + + assert(*nvpair == '('); + nvpair++; + + type = nvpair[0] - '0'; + nvpair++; + + /* if this condition is no longer true, change the above to: + * nvpair += cl_parse_int(nvpair, nvpair+nvlen, &type) + */ + assert(type >= 0 && type < 10); + + assert(*nvpair == ')'); + nvpair++; + + if ((nlen = strcspn(nvpair, EQUAL)) <= 0 + || nvpair[nlen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "%s: line doesn't contain '='", __FUNCTION__); + cl_log(LOG_INFO, "%s", nvpair); + } + return(HA_FAIL); + } + + name = nvpair; + ns_value = name +nlen + 1; + ns_vlen = nvpair + nvlen - ns_value -3 ; + if (fieldtypefuncs[type].netstringtofield(ns_value,ns_vlen, &value, &vlen) != HA_OK){ + cl_log(LOG_ERR, "netstringtofield failed in %s", __FUNCTION__); + return HA_FAIL; + + } + + memfree = fieldtypefuncs[type].memfree; + + if (ha_msg_nadd_type(m , name, nlen, value, vlen,type) + != HA_OK) { + cl_log(LOG_ERR, "ha_msg_nadd fails(netstring2msg_rec)"); + ret = HA_FAIL; + } + + + if (memfree && value){ + memfree(value); + } else{ + cl_log(LOG_ERR, "netstring2msg_rec:" + "memfree or ret_value is NULL"); + ret= HA_FAIL; + } + + return ret; + + +} + + +/* Converts a netstring into a message*/ +static struct ha_msg * +netstring2msg_rec(const char *s, size_t length, int* slen) +{ + struct ha_msg* ret = NULL; + const char * sp = s; + const char * smax = s + length; + int startlen; + int endlen; + + if ((ret = ha_msg_new(0)) == NULL){ + return(NULL); + } + + startlen = sizeof(MSG_START_NETSTRING)-1; + + if (strncmp(sp, MSG_START_NETSTRING, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "netstring2msg_rec: no MSG_START"); + ha_msg_del(ret); + } + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END_NETSTRING) - 1; + + while (sp < smax && strncmp(sp, MSG_END_NETSTRING, endlen) !=0 ){ + + const char *nvpair; + int nvlen; + int parselen; + + if (peel_netstring(sp , smax, &nvlen, &nvpair,&parselen) != HA_OK){ + cl_log(LOG_ERR + , "%s:peel_netstring fails for name/value pair", __FUNCTION__); + cl_log(LOG_ERR, "sp=%s", sp); + ha_msg_del(ret); + return(NULL); + } + sp += parselen; + + if (process_netstring_nvpair(ret, nvpair, nvlen) != HA_OK){ + cl_log(LOG_ERR, "%s: processing nvpair failed", __FUNCTION__); + return HA_FAIL; + } + + } + + + sp += sizeof(MSG_END_NETSTRING) -1; + *slen = sp - s; + return(ret); + +} + + +struct ha_msg * +netstring2msg(const char* s, size_t length, int needauth) +{ + const char *sp; + struct ha_msg *msg; + const char *smax = s + length; + int parselen; + int authlen; + const char *authstring; + /*actual string length used excluding auth string*/ + int slen = 0; /* assign to keep compiler happy */ + + msg = netstring2msg_rec(s, length, &slen); + + if (needauth == FALSE || !authmethod){ + goto out; + } + + sp = s + slen; + + if (peel_netstring(sp , smax, &authlen, &authstring, &parselen) !=HA_OK){ + cl_log(LOG_ERR, + "peel_netstring() error in getting auth string"); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "s=%s", s); + ha_msg_del(msg); + return(NULL); + } + + if (sp + parselen > smax){ + cl_log(LOG_ERR, " netstring2msg: smax passed"); + ha_msg_del(msg); + return NULL; + } + + if (!is_auth_netstring(s, slen, authstring,authlen) ){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "netstring authentication" + " failed, s=%s, autotoken=%s" + , s, authstring); + cl_log_message(LOG_ERR, msg); + } + ha_msg_del(msg); + return(NULL); + } + + out: + return msg; +} + + + + +int +is_auth_netstring(const char * datap, size_t datalen, + const char * authstring, size_t authlen) +{ + + char authstr[MAXLINE]; /* A copy of authstring */ + int authwhich; + char authtoken[MAXLINE]; + + + /* + * If we don't have any authentication method - everything is authentic... + */ + if (!authmethod) { + return TRUE; + } + strncpy(authstr, authstring, MAXLINE); + authstr[authlen] = 0; + if (sscanf(authstr, "%d %s", &authwhich, authtoken) != 2) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Bad/invalid netstring auth string"); + } + return(0); + } + + memset(authstr, 0, MAXLINE); + if (authmethod(authwhich, datap, datalen, authstr, MAXLINE) + != authwhich) { + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Invalid authentication [%d] in message!" + , authwhich); + } + return(FALSE); + } + + if (strcmp(authtoken, authstr) == 0) { + return(TRUE); + } + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "authtoken does not match, authtoken=%s, authstr=%s" + , authtoken, authstr); + } + return(FALSE); +} + diff --git a/lib/clplumbing/cl_pidfile.c b/lib/clplumbing/cl_pidfile.c new file mode 100644 index 0000000..b94573b --- /dev/null +++ b/lib/clplumbing/cl_pidfile.c @@ -0,0 +1,294 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <string.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_pidfile.h> +#include <clplumbing/lsb_exitcodes.h> + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on FILE_LOCK_D (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 +#include <clplumbing/cl_log.h> +static int IsRunning(long pid) +{ + int rc = 0; + long mypid; + int running = 0; + char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX]; + + /* check if pid is running */ + if (CL_KILL(pid, 0) < 0 && errno == ESRCH) { + goto bail; + } + +#ifndef HAVE_PROC_PID + return 1; +#endif + + /* check to make sure pid hasn't been reused by another process */ + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid); + + rc = readlink(proc_path, exe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + exe_path[rc] = 0; + + mypid = (unsigned long) getpid(); + + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", mypid); + rc = readlink(proc_path, myexe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + myexe_path[rc] = 0; + + if(strcmp(exe_path, myexe_path) == 0) { + running = 1; + } + + bail: + return running; +} + +static int +DoLock(const char *filename) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s",filename); + + snprintf(tf_name, sizeof(tf_name), "%s.%lu", + filename, mypid); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && (getpid() != pid) + && IsRunning(pid)) { + /* is locked by existing process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + close(fd); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + rc = -3; + close(fd); + goto out; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + out: + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * filename) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s", filename); + + return unlink(lf_name); +} + + +int +cl_read_pidfile(const char*filename) +{ + long pid = 0; + + pid = cl_read_pidfile_no_checking(filename); + + if (pid < 0){ + return - LSB_STATUS_STOPPED; + } + + if (IsRunning(pid)){ + return pid; + }else{ + return -LSB_STATUS_VAR_PID; + } +} + + +int +cl_read_pidfile_no_checking(const char*filename) +{ + int fd; + long pid = 0; + char buf[LOCKSTRLEN+1]; + if ((fd = open(filename, O_RDONLY)) < 0) { + return -1; + } + + if (read(fd, buf, sizeof(buf)) < 1) { + close(fd); + return -1; + } + + if (sscanf(buf, "%lu", &pid) <= 0) { + close(fd); + return -1; + } + + if (pid <= 0){ + close(fd); + return -1; + } + close(fd); + return pid; +} + + +int +cl_lock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + return DoLock(filename); +} + +/* + * Unlock a file (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +cl_unlock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + + return(DoUnlock(filename)); +} diff --git a/lib/clplumbing/cl_plugin.c b/lib/clplumbing/cl_plugin.c new file mode 100644 index 0000000..c039a35 --- /dev/null +++ b/lib/clplumbing/cl_plugin.c @@ -0,0 +1,140 @@ + +/* + * cl_plugin.c: This file handle plugin loading and deleting + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <unistd.h> +#include <assert.h> +#include <glib.h> +#include <ha_msg.h> +#include <clplumbing/netstring.h> +#include <pils/plugin.h> +#include <pils/generic.h> +/* #include <stonith/stonith.h> */ +/* #include <stonith/stonith_plugin.h> */ +#include <clplumbing/cl_plugin.h> + +#define MAXTYPES 16 +#define MAXTYPELEN 64 + +static GHashTable* funcstable[MAXTYPES]; + +static PILPluginUniv* plugin_univ = NULL; + +static PILGenericIfMgmtRqst reqs[] = + { + {"compress", &funcstable[0], NULL, NULL, NULL}, + {"HBcoms", &funcstable[1], NULL, NULL, NULL}, + {"HBauth", &funcstable[2], NULL, NULL, NULL}, + {"RAExec", &funcstable[3], NULL, NULL, NULL}, + {"quorum", &funcstable[4], NULL, NULL, NULL}, + {"tiebreaker", &funcstable[5], NULL, NULL, NULL}, + {"quorumd", &funcstable[6], NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static int +init_pluginsys(void){ + + if (plugin_univ) { + return TRUE; + } + + plugin_univ = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (plugin_univ) { + if (PILLoadPlugin(plugin_univ, PI_IFMANAGER, "generic", reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(plugin_univ); + plugin_univ = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return plugin_univ != NULL; + +} + +int +cl_remove_plugin(const char* type, const char* pluginname) +{ + return HA_OK; +} + +void* +cl_load_plugin(const char* type, const char* pluginname) +{ + void* funcs = NULL; + int i = 0; + GHashTable** table = NULL; + + while (reqs[i].iftype != NULL){ + if ( strcmp(reqs[i].iftype,type) != 0){ + i++; + continue; + } + + table = reqs[i].ifmap; + break; + } + + if (table == NULL){ + cl_log(LOG_ERR, "%s: function table not found",__FUNCTION__); + return NULL; + } + + if (!init_pluginsys()){ + cl_log(LOG_ERR, "%s: init plugin universe failed", __FUNCTION__); + return NULL; + } + + if ((funcs = g_hash_table_lookup(*table, pluginname)) + == NULL){ + if (PILPluginExists(plugin_univ, type, pluginname) == PIL_OK){ + PIL_rc rc; + rc = PILLoadPlugin(plugin_univ, type, pluginname, NULL); + if (rc != PIL_OK){ + cl_log(LOG_ERR, + "Cannot load plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return NULL; + } + funcs = g_hash_table_lookup(*table, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: module(%s) not found", + __FUNCTION__, pluginname); + return NULL; + } + + return funcs; + +} + diff --git a/lib/clplumbing/cl_poll.c b/lib/clplumbing/cl_poll.c new file mode 100644 index 0000000..789eb1a --- /dev/null +++ b/lib/clplumbing/cl_poll.c @@ -0,0 +1,809 @@ +#include <lha_internal.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Substitute poll(2) function using POSIX real time signals. + * + * The poll(2) system call often has significant latencies and realtime + * impacts (probably because of its variable length argument list). + * + * These functions let us use real time signals and sigtimedwait(2) instead + * of poll - for those files which work with real time signals. + * In the 2.4 series of Linux kernels, this does *not* include FIFOs. + * + * NOTE: We (have to) grab the SIGPOLL signal for our own purposes. + * Hope that's OK with you... + * + * Special caution: We can only incompletely simulate the difference between + * the level-triggered interface of poll(2) and the edge-triggered behavior + * of I/O signals. As a result you *must* read all previously-indicated + * incoming data before calling cl_poll() again. Otherwise you may miss + * some incoming data (and possibly hang). + * + * + * Copyright (C) 2003 IBM Corporation + * + * Author: <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 + * + **************************************************************************/ + + +#define __USE_GNU 1 +# include <fcntl.h> +#undef __USE_GNU + +#include <errno.h> +#include <string.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/cl_signal.h> + + + +/* Turn on to log odd realtime behavior */ + +#define TIME_CALLS 1 +#ifdef TIME_CALLS +# include <clplumbing/longclock.h> +# include <clplumbing/cl_log.h> +#endif + +static int debug = 0; + +int /* Slightly sleazy... */ +cl_glibpoll(GPollFD* ufds, guint nfsd, gint timeout) +{ + (void)debug; + return cl_poll((struct pollfd*)ufds, nfsd, timeout); +} + +#if defined (F_SETSIG) && defined(F_SETOWN) && defined (O_ASYNC) +# define HAVE_FCNTL_F_SETSIG +#endif + +#ifndef HAVE_FCNTL_F_SETSIG + +/* + * Dummy cl_poll() and cl_poll_ignore() functions for systems where + * we don't have all the support we need. + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeout) +{ + return poll(fds, (nfds_t)nfds, timeout); +} + +int +cl_poll_ignore(int fd) +{ + return 0; +} + +#else /* HAVE_FCNTL_F_SETSIG */ +static void dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms); +static void check_fd_info(struct pollfd *fds, unsigned int nfds); +static void cl_real_poll_fd(int fd); +static void cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* , void*); +static void cl_poll_sigpoll_overflow(void); +static int cl_poll_get_sigqlimit(void); +typedef unsigned char poll_bool; + +/* + * Here's our strategy: + * We have a set of signals which we use for these file descriptors, + * and we use sigtimedwait(2) to wait on information from these various + * signals. + * + * If we are ever asked to wait for a particular signal, then we will + * enable signals for that file descriptor, and post the events in + * our own cache. The next time you include that signal in a call + * to cl_poll(), you will get the information delivered + * to you in your cl_poll() call. + * + * If you want to stop monitoring a particular file descriptor, use + * cl_poll_ignore() for that purpose. Doing this is a good idea, but + * not fatal if omitted... + */ + +/* Information about a file descriptor we're monitoring */ + +typedef struct poll_fd_info_s { + short nsig; /* Which signal goes with it? */ + short pendevents; /* Pending events */ +}poll_info_t; + +static int max_allocated = 0; +static poll_bool* is_monitored = NULL; /* Sized by max_allocated */ +static poll_info_t* monitorinfo = NULL; /* Sized by max_allocated */ +static int cl_nsig = 0; +static gboolean SigQOverflow = FALSE; + +static int cl_init_poll_sig(struct pollfd *fds, unsigned int nfds); +static short cl_poll_assignsig(int fd); +static void cl_poll_sigaction(int nsig, siginfo_t* info, void* v); +static int cl_poll_prepsig(int nsig); + + +/* + * SignalSet is the set of all file descriptors we're monitoring. + * + * We monitor a file descriptor forever, unless you tell us not to + * by calling cl_poll_ignore(), or you (mistakenly) give it to + * us to look at in another poll call after you've closed it. + */ + +static sigset_t SignalSet; + +/* Select the signal you want us to use (must be a RT signal) */ +int +cl_poll_setsig(int nsig) +{ + if (nsig < SIGRTMIN || nsig >= SIGRTMAX) { + errno = EINVAL; + return -1; + } + if (cl_poll_prepsig(nsig) < 0) { + return -1; + } + cl_nsig = nsig; + return 0; +} + +/* + * It's harmless to call us multiple times on the same signal. + */ +static int +cl_poll_prepsig(int nsig) +{ + static gboolean setinityet=FALSE; + + if (!setinityet) { + CL_SIGEMPTYSET(&SignalSet); + cl_signal_set_simple_action(SIGPOLL + , cl_poll_sigpoll_overflow_sigaction + , NULL); + setinityet = TRUE; + } + if (CL_SIGINTERRUPT(nsig, FALSE) < 0) { + cl_perror("sig_interrupt(%d, FALSE)", nsig); + return -1; + } + if (CL_SIGADDSET(&SignalSet, nsig) < 0) { + cl_perror("sig_addset(&SignalSet, %d)", nsig); + return -1; + } + if (CL_SIGPROCMASK(SIG_BLOCK, &SignalSet, NULL) < 0) { + cl_perror("sig_sigprocmask(SIG_BLOCK, sig %d)", nsig); + return -1; + } + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d belongs to us...", nsig); + cl_log(LOG_DEBUG, "cl_poll_prepsig(%d) succeeded.", nsig); + } + + return 0; +} + +#define FD_CHUNKSIZE 64 + +/* Set of events everyone must monitor whether they want to or not ;-) */ +#define CONSTEVENTS (POLLHUP|POLLERR|POLLNVAL) + +#define RECORDFDEVENT(fd, flags) (monitorinfo[fd].pendevents |= (flags)) + +/* + * Initialized our poll-simulation data structures. + * This means (among other things) registering any monitored + * file descriptors. + */ +static int +cl_init_poll_sig(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + int maxmonfd = -1; + int nmatch = 0; + + + if (cl_nsig == 0) { + cl_nsig = ((SIGRTMIN+SIGRTMAX)/2); + if (cl_poll_setsig(cl_nsig) < 0) { + return -1; + } + } + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + + if (fd > maxmonfd) { + maxmonfd = fd; + } + } + + /* See if we need to malloc/realloc our data structures */ + + if (maxmonfd >= max_allocated) { + int newsize; + int growthamount; + + newsize = ((maxmonfd + FD_CHUNKSIZE)/FD_CHUNKSIZE) + * FD_CHUNKSIZE; + growthamount = newsize - max_allocated; + + /* This can't happen ;-) */ + if (growthamount <= 0 || newsize <= maxmonfd) { + errno = EINVAL; + return -1; + } + + /* Allocate (more) memory! */ + + if ((is_monitored = (poll_bool*)realloc(is_monitored + , newsize * sizeof(poll_bool))) == NULL + || (monitorinfo = (poll_info_t*) realloc(monitorinfo + , newsize * sizeof(poll_info_t))) == NULL) { + + if (is_monitored) { + free(is_monitored); + is_monitored = NULL; + } + if (monitorinfo) { + free(monitorinfo); + monitorinfo = NULL; + } + max_allocated = 0; + errno = ENOMEM; + return -1; + } + memset(monitorinfo+max_allocated, 0 + , growthamount * sizeof(monitorinfo[0])); + memset(is_monitored+max_allocated, FALSE + , growthamount*sizeof(is_monitored[0])); + max_allocated = newsize; + } + + if (fds->events != 0 && debug) { + cl_log(LOG_DEBUG + , "Current event mask for fd [0] {%d} 0x%x" + , fds->fd, fds->events); + } + /* + * Examine each fd for the following things: + * Is it already monitored? + * if not, set it up for monitoring. + * Do we have events for it? + * if so, post events... + */ + + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + short nsig; + int badfd = FALSE; + + is_monitored[fd] = TRUE; + + if (moni->nsig <= 0) { + nsig = cl_poll_assignsig(fd); + if (nsig < 0) { + RECORDFDEVENT(fd, POLLERR); + badfd = TRUE; + }else{ + /* Use real poll(2) to get initial + * event status + */ + moni->nsig = nsig; + cl_real_poll_fd(fd); + } + }else if (fcntl(fd, F_GETFD) < 0) { + cl_log(LOG_ERR, "bad fd(%d)", fd); + RECORDFDEVENT(fd, POLLNVAL); + badfd = TRUE; + } + + /* Look for pending events... */ + + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + + if (fds[j].revents) { + ++nmatch; + moni->pendevents &= ~(fds[j].revents); + if (debug) { + cl_log(LOG_DEBUG + , "revents for fd %d: 0x%x" + , fds[j].fd, fds[j].revents); + cl_log(LOG_DEBUG + , "events for fd %d: 0x%x" + , fds[j].fd, fds[j].events); + } + }else if (fds[j].events && debug) { + cl_log(LOG_DEBUG + , "pendevents for fd %d: 0x%x" + , fds[j].fd, moni->pendevents); + } + if (badfd) { + cl_poll_ignore(fd); + } + } + if (nmatch != 0 && debug) { + cl_log(LOG_DEBUG, "Returning %d events from cl_init_poll_sig()" + , nmatch); + } + return nmatch; +} + +/* + * Initialize our current state of the world with info from the + * real poll(2) call. + * + * We call this when we first see a particular fd, and after a signal + * queue overflow. + */ +static void +cl_real_poll_fd(int fd) +{ + struct pollfd pfd[1]; + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + if (debug) { + cl_log(LOG_DEBUG + , "Calling poll(2) on fd %d", fd); + } + /* Get the current state of affaris from poll(2) */ + pfd[0].fd = fd; + pfd[0].revents = 0; + pfd[0].events = ~0; + if (poll(pfd, 1, 0) >= 0) { + RECORDFDEVENT(fd, pfd[0].revents); + if (pfd[0].revents & (POLLNVAL|POLLERR)) { + cl_log(LOG_INFO, "cl_poll_real_fd(%d): error in revents [%d]" + , fd, pfd[0].revents); + } + if (debug) { + cl_log(LOG_DEBUG + , "Old news from poll(2) for fd %d: 0x%x" + , fd, pfd[0].revents); + } + }else{ + if (fcntl(fd, F_GETFL) < 0) { + cl_perror("cl_poll_real_fd(%d): F_GETFL failure" + , fd); + RECORDFDEVENT(fd, POLLNVAL); + }else{ + RECORDFDEVENT(fd, POLLERR); + } + } +} + +/* + * Assign a signal for monitoring the given file descriptor + */ + +static short +cl_poll_assignsig(int fd) +{ + int flags; + + + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d monitors fd %d...", cl_nsig, fd); + } + + /* Test to see if the file descriptor is good */ + if ((flags = fcntl(fd, F_GETFL)) < 0) { + cl_perror("cl_poll_assignsig(%d) F_GETFL failure" + , fd); + return -1; + } + + /* Associate the right signal with the fd */ + + if (fcntl(fd, F_SETSIG, cl_nsig) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETSIG failure" + , fd); + return -1; + } + + /* Direct the signals to us */ + if (fcntl(fd, F_SETOWN, getpid()) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETOWN failure", fd); + return -1; + } + + /* OK... Go ahead and send us signals! */ + + if (fcntl(fd, F_SETFL, flags|O_ASYNC) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETFL(O_ASYNC) failure" + , fd); + return -1; + } + + return cl_nsig; +} + + +/* + * This is a function we call as a (fake) signal handler. + * + * It records events to our "monitorinfo" structure. + * + * Except for the cl_log() call, it could be called in a signal + * context. + */ + +static void +cl_poll_sigaction(int nsig, siginfo_t* info, void* v) +{ + int fd; + + /* What do you suppose all the various si_code values mean? */ + + fd = info->si_fd; + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_sigaction(nsig=%d fd=%d" + ", si_code=%d si_band=0x%lx)" + , nsig, fd, info->si_code + , (unsigned long)info->si_band); + } + + if (fd <= 0) { + return; + } + + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + /* We should not call logging functions in (real) signal handlers */ + if (nsig != monitorinfo[fd].nsig) { + cl_log(LOG_ERR, "cl_poll_sigaction called with signal %d/%d" + , nsig, monitorinfo[fd].nsig); + } + + /* Record everything as a pending event. */ + RECORDFDEVENT(fd, info->si_band); +} + + + +/* + * This is called whenever a file descriptor shouldn't be + * monitored any more. + */ +int +cl_poll_ignore(int fd) +{ + int flags; + + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_ignore(%d)", fd); + } + if (fd < 0 || fd >= max_allocated) { + errno = EINVAL; + return -1; + } + if (!is_monitored[fd]) { + return 0; + } + + is_monitored[fd] = FALSE; + memset(monitorinfo+fd, 0, sizeof(monitorinfo[0])); + + if ((flags = fcntl(fd, F_GETFL)) >= 0) { + flags &= ~O_ASYNC; + if (fcntl(fd, F_SETFL, flags) < 0) { + return -1; + } + }else{ + return flags; + } + return 0; +} + + +/* + * cl_poll: fake poll routine based on POSIX realtime signals. + * + * We want to emulate poll as exactly as possible, but poll has a couple + * of problems: scaleability, and it tends to sleep in the kernel + * because the first argument is an argument of arbitrary size, and + * generally requires allocating memory. + * + * The challenge is that poll is level-triggered, but the POSIX + * signals (and sigtimedwait(2)) are edge triggered. This is + * one of the reasons why we have the cl_real_poll_fd() function + * - to get the current "level" before we start. + * Once we have this level we can compute something like the current + * level + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + int nready; + struct timespec ts; + static const struct timespec zerotime = {0L, 0L}; + const struct timespec* itertime = &ts; + siginfo_t info; + int eventcount = 0; + unsigned int j; + int savederrno = errno; + int stw_errno; + int rc; + longclock_t starttime; + longclock_t endtime; + const int msfudge + = 2* 1000/hz_longclock(); + int mselapsed = 0; + + /* Do we have any old news to report? */ + if ((nready=cl_init_poll_sig(fds, nfds)) != 0) { + /* Return error or old news to report */ + if (debug) { + cl_log(LOG_DEBUG, "cl_poll: early return(%d)", nready); + } + return nready; + } + + /* Nothing to report yet... */ + + /* So, we'll do a sigtimedwait(2) to wait for signals + * and see if we can find something to report... + * + * cl_init_poll() prepared a set of file signals to watch... + */ + +recalcandwaitagain: + if (timeoutms >= 0) { + ts.tv_sec = timeoutms / 1000; + ts.tv_nsec = (((unsigned long)timeoutms) % 1000UL)*1000000UL; + }else{ + ts.tv_sec = G_MAXLONG; + ts.tv_nsec = 99999999UL; + } + + /* + * Perform a timed wait for any of our signals... + * + * We shouldn't sleep for any call but (possibly) the first one. + * Subsequent calls should just pick up other events without + * sleeping. + */ + + starttime = time_longclock(); + /* + * Wait up to the prescribed time for a signal. + * If we get a signal, then loop grabbing all other + * pending signals. Note that subsequent iterations will + * use &zerotime to get the minimum wait time. + */ + if (debug) { + check_fd_info(fds, nfds); + dump_fd_info(fds, nfds, timeoutms); + } +waitagain: + while (sigtimedwait(&SignalSet, &info, itertime) >= 0) { + int nsig = info.si_signo; + + /* Call signal handler to simulate signal reception */ + + cl_poll_sigaction(nsig, &info, NULL); + itertime = &zerotime; + } + stw_errno=errno; /* Save errno for later use */ + endtime = time_longclock(); + mselapsed = longclockto_ms(sub_longclock(endtime, starttime)); + +#ifdef TIME_CALLS + if (timeoutms >= 0 && mselapsed > timeoutms + msfudge) { + /* We slept too long... */ + cl_log(LOG_WARNING + , "sigtimedwait() sequence for %d ms took %d ms" + , timeoutms, mselapsed); + } +#endif + + if (SigQOverflow) { + /* OOPS! Better recover from this! */ + /* This will use poll(2) to correct our current status */ + cl_poll_sigpoll_overflow(); + } + + /* Post observed events and count them... */ + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + if (fds[j].revents) { + ++eventcount; + moni->pendevents &= ~(fds[j].revents); + /* Make POLLHUP persistent */ + if (fds[j].revents & POLLHUP) { + moni->pendevents |= POLLHUP; + /* Don't lose input events at EOF */ + if (fds[j].events & POLLIN) { + cl_real_poll_fd(fds[j].fd); + } + } + } + } + if (eventcount == 0 && stw_errno == EAGAIN && timeoutms != 0) { + /* We probably saw an event the user didn't ask to see. */ + /* Consquently, we may have more waiting to do */ + if (timeoutms < 0) { + /* Restore our infinite wait time */ + itertime = &ts; + goto waitagain; + }else if (timeoutms > 0) { + if (mselapsed < timeoutms) { + timeoutms -= mselapsed; + goto recalcandwaitagain; + } + } + } + rc = (eventcount > 0 ? eventcount : (stw_errno == EAGAIN ? 0 : -1)); + + if (rc >= 0) { + errno = savederrno; + } + return rc; +} +/* + * Debugging routine for printing current poll arguments, etc. + */ +static void +dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + unsigned j; + + cl_log(LOG_DEBUG, "timeout: %d milliseconds", timeoutms); + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + cl_log(LOG_DEBUG, "fd %d flags: 0%o, signal: %d, events: 0x%x" + ", revents: 0x%x, pendevents: 0x%x" + , fd, fcntl(fd, F_GETFL), moni->nsig + , fds[j].events, fds[j].revents, moni->pendevents); + } + for (j=SIGRTMIN; j < (unsigned)SIGRTMAX; ++j) { + if (!sigismember(&SignalSet, j)) { + continue; + } + cl_log(LOG_DEBUG, "Currently monitoring RT signal %d", j); + } +} + +/* + * Debugging routine for auditing our file descriptors, etc. + */ +static void +check_fd_info(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + if (!sigismember(&SignalSet, moni->nsig)) { + cl_log(LOG_ERR, "SIGNAL %d not in monitored SignalSet" + , moni->nsig); + } + } + for (j=0; j < 10; ++j) { + int sig; + int flags; + int pid; + if ((flags = fcntl(j, F_GETFL)) < 0 || (flags & O_ASYNC) ==0){ + continue; + } + sig = fcntl(j, F_GETSIG); + if (sig == 0) { + cl_log(LOG_ERR, "FD %d will get SIGIO", j); + } + if (!sigismember(&SignalSet, sig)) { + cl_log(LOG_ERR, "FD %d (signal %d) is not in SignalSet" + , j, sig); + } + if (sig < SIGRTMIN || sig >= SIGRTMAX) { + cl_log(LOG_ERR, "FD %d (signal %d) is not RealTime" + , j, sig); + } + pid = fcntl(j, F_GETOWN); + if (pid != getpid()) { + cl_log(LOG_ERR, "FD %d (signal %d) owner is pid %d" + , j, sig, pid); + } + } +} + +/* Note that the kernel signalled an event queue overflow */ +static void +cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* info, void* v) +{ + SigQOverflow = TRUE; +} + +#define MAXQNAME "rtsig-max" +/* + * Called when signal queue overflow is suspected. + * We then use poll(2) to get the current data. It's slow, but it + * should work quite nicely. + */ +static void +cl_poll_sigpoll_overflow(void) +{ + int fd; + int limit; + + if (!SigQOverflow) { + return; + } + cl_log(LOG_WARNING, "System signal queue overflow."); + limit = cl_poll_get_sigqlimit(); + if (limit > 0) { + cl_log(LOG_WARNING, "Increase '%s'. Current limit is %d" + " (see sysctl(8)).", MAXQNAME, limit); + } + + SigQOverflow = FALSE; + + for (fd = 0; fd < max_allocated; ++fd) { + if (is_monitored[fd]) { + cl_real_poll_fd(fd); + } + } +} + +#define PSK "/proc/sys/kernel/" + +/* Get current kernel signal queue limit */ +/* This only works on Linux - but that's not a big problem... */ +static int +cl_poll_get_sigqlimit(void) +{ + int limit = -1; + int pfd; + char result[32]; + + pfd = open(PSK MAXQNAME, O_RDONLY); + if (pfd >= 0 && read(pfd, result, sizeof(result)) > 1) { + limit = atoi(result); + if (limit < 1) { + limit = -1; + } + } + if (pfd >= 0) { + close(pfd); + } + return limit; +} +#endif /* HAVE_FCNTL_F_SETSIG */ diff --git a/lib/clplumbing/cl_random.c b/lib/clplumbing/cl_random.c new file mode 100644 index 0000000..4bafcfe --- /dev/null +++ b/lib/clplumbing/cl_random.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * Copyright (C) 2005 International Business Machines Inc. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <lha_internal.h> +#include <strings.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/cl_random.h> +#include <clplumbing/longclock.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#include <sys/time.h> +#include <sys/times.h> + +/* Used to provide seed to the random number generator */ +unsigned int +cl_randseed(void) +{ + char buf[16]; + FILE* fs; + struct timeval tv; + const char * randdevname [] = {"/dev/urandom", "/dev/random"}; + int idev; +#if 0 + long horrid; +#endif + + /* + * Notes, based on reading of man pages of Solaris, FreeBSD and Linux, + * and on proof-of-concept tests on Solaris and Linux (32- and 64-bit). + * + * Reminder of a subtlety: our intention is not to return a random + * number, but rather to return a random-enough seed for future + * random numbers. So don't bother trying (e.g.) "rand()" and + * "random()". + * + * /dev/random and dev/urandom seem to be a related pair. In the + * words of the song: "You can't have one without the other". + * + * /dev/random is probably the best. But it can block. The Solaris + * implementation can apparently honour "O_NONBLOCK" and "O_NDELAY". + * But can others? For this reason, I chose not to use it at present. + * + * /dev/urandom (with the "u") is also good. This doesn't block. + * But some OSes may lack it. It is tempting to detect its presence + * with autoconf and use the result in a "hash-if" here. BUT... in + * at least one OS, its presence can vary depending upon patch levels, + * so a binary/package built on an enabled machine might hit trouble + * when run on one where it is absent. (And vice versa: a build on a + * disabled machine would be unable to take advantage of it on an + * enabled machine.) Therefore always try for it at run time. + * + * "gettimeofday()" returns a random-ish number in its millisecond + * component. + * + * -- David Lee, Jan 2006 + */ + + /* + * Each block below is logically of the form: + * if good-feature appears present { + * try feature + * if feature worked { + * return its result + * } + * } + * -- fall through to not-quite-so-good feature -- + */ + + /* + * Does any of the random device names work? + */ + for (idev=0; idev < DIMOF(randdevname); ++idev) { + fs = fopen(randdevname[idev], "r"); + if (fs == NULL) { + cl_log(LOG_INFO, "%s: Opening file %s failed" + , __FUNCTION__, randdevname[idev]); + }else{ + if (fread(buf, 1, sizeof(buf), fs)!= sizeof(buf)){ + cl_log(LOG_INFO, "%s: reading file %s failed" + , __FUNCTION__, randdevname[idev]); + fclose(fs); + }else{ + fclose(fs); + return (unsigned int)cl_binary_to_int(buf, sizeof(buf)); + } + } + } + + /* + * Try "gettimeofday()"; use its microsecond output. + * (Might it be prudent to let, say, the seconds further adjust this, + * in case the microseconds are too predictable?) + */ + if (gettimeofday(&tv, NULL) != 0) { + cl_log(LOG_INFO, "%s: gettimeofday failed", + __FUNCTION__); + }else{ + return (unsigned int) tv.tv_usec; + } + /* + * times(2) returns the number of clock ticks since + * boot. Fairly predictable, but not completely so... + */ + return (unsigned int) cl_times(); + + +#if 0 + /* + * If all else has failed, return (as a number) the address of + * something on the stack. + * Poor, but at least it has a chance of some sort of variability. + */ + horrid = (long) &tv; + return (unsigned int) horrid; /* pointer to local variable exposed */ +#endif +} + +static gboolean inityet = FALSE; + +static void +cl_init_random(void) +{ + if (inityet) + return; + + inityet=TRUE; + srand(cl_randseed()); +} + +int +get_next_random(void) +{ + if (!inityet) + cl_init_random(); + + return rand(); +} diff --git a/lib/clplumbing/cl_reboot.c b/lib/clplumbing/cl_reboot.c new file mode 100644 index 0000000..c4c3ab0 --- /dev/null +++ b/lib/clplumbing/cl_reboot.c @@ -0,0 +1,59 @@ +#include <lha_internal.h> +#include <clplumbing/cl_reboot.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_SYS_REBOOT_H +# include <sys/reboot.h> +#endif +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif +#include <clplumbing/cl_log.h> +#include <clplumbing/timers.h> + +enum rebootopt { + REBOOT_DEFAULT = 0, + REBOOT_NOCOREDUMP = 1, + REBOOT_COREDUMP = 2, +}; +static enum rebootopt coredump = REBOOT_DEFAULT; + +void +cl_enable_coredump_before_reboot(gboolean yesno) +{ + coredump = (yesno ? REBOOT_COREDUMP : REBOOT_NOCOREDUMP); +} + + +void cl_reboot(int msdelaybeforereboot, const char * reason) +{ + int rebootflag = 0; + int systemrc = 0; +#ifdef RB_AUTOBOOT + rebootflag = RB_AUTOBOOT; +#endif +#ifdef RB_NOSYNC + rebootflag = RB_NOSYNC; +#endif +#ifdef RB_DUMP + if (coredump == REBOOT_COREDUMP) { + rebootflag = RB_DUMP; + } +#endif + cl_log(LOG_EMERG, "Rebooting system. Reason: %s", reason); + sync(); + mssleep(msdelaybeforereboot); +#if REBOOT_ARGS == 1 + reboot(rebootflag); +#elif REBOOT_ARGS == 2 + reboot(rebootflag, NULL); +#else +#error "reboot() call needs to take one or two args" +#endif + /* Shouldn't ever get here, but just in case... */ + systemrc=system(REBOOT " " REBOOT_OPTIONS); + cl_log(LOG_EMERG, "ALL REBOOT OPTIONS FAILED: %s returned %d" + , REBOOT " " REBOOT_OPTIONS, systemrc); + exit(1); +} diff --git a/lib/clplumbing/cl_signal.c b/lib/clplumbing/cl_signal.c new file mode 100644 index 0000000..feedb3d --- /dev/null +++ b/lib/clplumbing/cl_signal.c @@ -0,0 +1,209 @@ +/* + * cl_signal.c: signal handling routines to be used by Linux-HA programmes + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * 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 <string.h> +#include <errno.h> + +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_log.h> + + +int +cl_signal_set_handler(int sig, void (*handler)(int), sigset_t *mask +, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if (sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_handler(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_handler(): " + "sigemptyset()"); + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_handler()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_action(int sig, void (*action)(int, siginfo_t *, void *) +, sigset_t *mask, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_sigaction = action; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_action(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_action(int sig, void (*action)(int, siginfo_t *, void *) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigemptyset()"); + return(-1); + } + + sa.sa_sigaction = action; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_interrupt(int sig, int flag) +{ + if(siginterrupt(sig, flag) < 0) { + cl_perror("cl_signal_set_interrupt(): siginterrupt()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block(int how, int signal, sigset_t *oldset) +{ + sigset_t set; + + if(sigemptyset(&set) < 0) { + cl_perror("cl_signal_block(): sigemptyset()"); + return(-1); + } + + if(sigaddset(&set, signal) < 0) { + cl_perror("cl_signal_block(): sigaddset()"); + return(-1); + } + + if(sigprocmask(how, &set, oldset) < 0) { + cl_perror("cl_signal_block(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block_set(int how, const sigset_t *set, sigset_t *oldset) +{ + if(sigprocmask(how, set, oldset) < 0) { + cl_perror("cl_signal_block_mask(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_handler_mode(const cl_signal_mode_t *mode, sigset_t *set) +{ + size_t i; + sigset_t our_set; + sigset_t *use_set; + + use_set = (set) ? set : &our_set; + + for (i=0; mode[i].sig; ++i) { + if(sigaddset(use_set, mode[i].sig) < 0) { + cl_perror("cl_signal_set_handler_mode(): " + "sigaddset() [signum=%d]", mode[i].sig); + return(-1); + } + } + + if (sigprocmask(SIG_UNBLOCK, use_set, NULL) < 0) { + cl_perror("cl_signal_set_handler_mode()" + ": sigprocmask()"); + return(-1); + } + + for (i=0; mode[i].sig; ++i) { + if(cl_signal_set_handler(mode[i].sig, mode[i]. handler + , use_set, SA_NOCLDSTOP, NULL) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "ha_set_sig_handler()"); + return(-1); + } + if(cl_signal_set_interrupt(mode[i].sig, mode[i].interrupt) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "hb_signal_interrupt()"); + return(-1); + } + } + + return(0); +} + diff --git a/lib/clplumbing/cl_syslog.c b/lib/clplumbing/cl_syslog.c new file mode 100644 index 0000000..6920bd5 --- /dev/null +++ b/lib/clplumbing/cl_syslog.c @@ -0,0 +1,149 @@ +/* + * Functions to support syslog. + * David Lee (c) 2005 + */ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +/* + * Some OSes already have tables to convert names into corresponding numbers. + * For instance Linux makes these available if SYSLOG_NAMES is defined. + */ +#define SYSLOG_NAMES +#include <stdlib.h> +#include <clplumbing/cl_syslog.h> + +#include <syslog.h> +#include <string.h> + +struct _syslog_code { + const char *c_name; + int c_val; +}; + +#if defined(HAVE_SYSLOG_FACILITYNAMES) + +/* + * <cl_syslog.h> will have included a table called "facilitynames" structured + * as a "struct _syslog_code" but the tag "_syslog_code" may be something else. + */ + +#else + +struct _syslog_code facilitynames[] = +{ +#ifdef LOG_AUTH + { "auth", LOG_AUTH }, + { "security", LOG_AUTH }, /* DEPRECATED */ +#endif +#ifdef LOG_AUTHPRIV + { "authpriv", LOG_AUTHPRIV }, +#endif +#ifdef LOG_CRON + { "cron", LOG_CRON }, +#endif +#ifdef LOG_DAEMON + { "daemon", LOG_DAEMON }, +#endif +#ifdef LOG_FTP + { "ftp", LOG_FTP }, +#endif +#ifdef LOG_KERN + { "kern", LOG_KERN }, +#endif +#ifdef LOG_LPR + { "lpr", LOG_LPR }, +#endif +#ifdef LOG_MAIL + { "mail", LOG_MAIL }, +#endif + +/* { "mark", INTERNAL_MARK }, * INTERNAL */ + +#ifdef LOG_NEWS + { "news", LOG_NEWS }, +#endif +#ifdef LOG_SYSLOG + { "syslog", LOG_SYSLOG }, +#endif +#ifdef LOG_USER + { "user", LOG_USER }, +#endif +#ifdef LOG_UUCP + { "uucp", LOG_UUCP }, +#endif +#ifdef LOG_LOCAL0 + { "local0", LOG_LOCAL0 }, +#endif +#ifdef LOG_LOCAL1 + { "local1", LOG_LOCAL1 }, +#endif +#ifdef LOG_LOCAL2 + { "local2", LOG_LOCAL2 }, +#endif +#ifdef LOG_LOCAL3 + { "local3", LOG_LOCAL3 }, +#endif +#ifdef LOG_LOCAL4 + { "local4", LOG_LOCAL4 }, +#endif +#ifdef LOG_LOCAL5 + { "local5", LOG_LOCAL5 }, +#endif +#ifdef LOG_LOCAL6 + { "local6", LOG_LOCAL6 }, +#endif +#ifdef LOG_LOCAL7 + { "local7", LOG_LOCAL7 }, +#endif + { NULL, -1 } +}; + +#endif /* HAVE_SYSLOG_FACILITYNAMES */ + +/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */ +int +cl_syslogfac_str2int(const char *fname) +{ + int i; + + if(fname == NULL || strcmp("none", fname) == 0) { + return 0; + } + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (strcmp(fname, facilitynames[i].c_name) == 0) { + return facilitynames[i].c_val; + } + } + return -1; +} + +/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */ +const char * +cl_syslogfac_int2str(int fnum) +{ + int i; + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (facilitynames[i].c_val == fnum) { + return facilitynames[i].c_name; + } + } + return NULL; +} diff --git a/lib/clplumbing/cl_uuid.c b/lib/clplumbing/cl_uuid.c new file mode 100644 index 0000000..d0dfcb6 --- /dev/null +++ b/lib/clplumbing/cl_uuid.c @@ -0,0 +1,180 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +/* + * uuid: wrapper declarations. + * + * heartbeat originally used "uuid" functionality by calling directly, + * and only, onto the "e2fsprogs" implementation. + * + * The run-time usages in the code have since been abstracted, funnelled + * through a thin, common interface layer: a Good Thing. + * + * Similarly, the compile-time usages of "include <uuid/uuid.h>" are + * replaced, being funnelled through a reference to this header file. + * + * This header file interfaces onto the actual underlying implementation. + * In the case of the "e2fsprogs" implementation, it is simply a stepping + * stone onto "<uuid/uuid.h>". As other implementations are accommodated, + * so their header requirements can be accommodated here. + * + * Copyright (C) 2004 David Lee <t.d.lee@durham.ac.uk> + */ + +#if defined (HAVE_UUID_UUID_H) +/* + * Almost certainly the "e2fsprogs" implementation. + */ +# include <uuid/uuid.h> + +/* elif defined(HAVE...UUID_OTHER_1 e.g. OSSP ...) */ + +/* elif defined(HAVE...UUID_OTHER_2...) */ +#else +# include <replace_uuid.h> +#endif + +#include <clplumbing/cl_uuid.h> +#include <clplumbing/cl_log.h> +#include <assert.h> + +void +cl_uuid_copy(cl_uuid_t* dst, cl_uuid_t* src) +{ + if (dst == NULL || src == NULL){ + cl_log(LOG_ERR, "cl_uuid_copy: " + "wrong argument %s is NULL", + dst == NULL?"dst":"src"); + assert(0); + } + + uuid_copy(dst->uuid, src->uuid); +} + +void +cl_uuid_clear(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_clear: " + "wrong argument (uu is NULL)"); + assert(0); + } + + uuid_clear(uu->uuid); + +} + +int +cl_uuid_compare(const cl_uuid_t* uu1, const cl_uuid_t* uu2) +{ + if (uu1 == NULL || uu2 == NULL){ + cl_log(LOG_ERR, "cl_uuid_compare: " + " wrong argument (%s is NULL)", + uu1 == NULL?"uu1":"uu2"); + assert(0); + } + + return uuid_compare(uu1->uuid, uu2->uuid); + +} + + + +void +cl_uuid_generate(cl_uuid_t* out) +{ + if (out == NULL){ + cl_log(LOG_ERR, "cl_uuid_generate: " + " wrong argument (out is NULL)"); + assert(0); + } + + uuid_generate(out->uuid); + +} + +int +cl_uuid_is_null(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_is_null: " + "wrong argument (uu is NULL)"); + assert(0); + } + + return uuid_is_null(uu->uuid); + +} + +int +cl_uuid_parse( char *in, cl_uuid_t* uu) +{ + if (in == NULL || uu == NULL){ + + cl_log(LOG_ERR, "cl_uuid_parse: " + "wrong argument (%s is NULL)", + in == NULL? "in":"uu"); + assert(0); + } + + return uuid_parse(in, uu->uuid); +} + + +void +cl_uuid_unparse(const cl_uuid_t* uu, char *out){ + + if (uu == NULL || out == NULL){ + cl_log(LOG_ERR, "cl_uuid_unparse: " + "wrong argument (%s is NULL)", + uu == NULL? "uu":"out"); + assert(0); + } + + uuid_unparse(uu->uuid, out); +} + + +guint +cl_uuid_g_hash(gconstpointer uuid_ptr) +{ + guint ret = 0U; + guint32 value32; + int index; + const unsigned char * uuid_char = uuid_ptr; + + /* It is probably not strictly necessary, but I'm trying to get the + * same hash result on all platforms. After all, the uuids are the + * same on every platform. + */ + + for (index = 0; index < sizeof(cl_uuid_t); index += sizeof(value32)) { + memcpy(&value32, uuid_char+index, sizeof (value32)); + ret += g_ntohl(value32); + } + return ret; +} +gboolean +cl_uuid_g_equal(gconstpointer uuid_ptr_a, gconstpointer uuid_ptr_b) +{ + return cl_uuid_compare(uuid_ptr_a, uuid_ptr_b) == 0; +} diff --git a/lib/clplumbing/coredumps.c b/lib/clplumbing/coredumps.c new file mode 100644 index 0000000..79da737 --- /dev/null +++ b/lib/clplumbing/coredumps.c @@ -0,0 +1,309 @@ +/* + * Basic Core dump control functions. + * + * Author: Alan Robertson + * + * Copyright (C) 2004 IBM Corporation + * + * This software licensed under the GNU LGPL. + * + * 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 <unistd.h> +#include <sys/time.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <fcntl.h> +#include <pwd.h> +#include <string.h> +#include <stdlib.h> +#ifdef HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif +#include <clplumbing/coredumps.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/uids.h> +#include <clplumbing/cl_signal.h> + +static char * coreroot = NULL; + +/* Set the root directory of our core directory hierarchy */ +int +cl_set_corerootdir(const char * dir) +{ + if (dir == NULL || *dir != '/') { + cl_perror("Invalid dir in cl_set_corerootdir() [%s]" + , dir ? dir : "<NULL>"); + errno = EINVAL; + return -1; + } + if (coreroot != NULL) { + free(coreroot); + coreroot = NULL; + } + coreroot = strdup(dir); + if (coreroot == NULL) { + return -1; + } + return 0; +} + +/* + * Change directory to the directory our core file needs to go in + * Call after you establish the userid you're running under. + */ +int +cl_cdtocoredir(void) +{ + const char * dir = coreroot; + int rc; + struct passwd* pwent; + + if (dir == NULL) { + dir = HA_COREDIR; + } + if ((rc=chdir(dir)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s]", dir); + errno = errsave; + return rc; + } + pwent = getpwuid(getuid()); + if (pwent == NULL) { + int errsave = errno; + cl_perror("Cannot get name for uid [%d]", getuid()); + errno = errsave; + return -1; + } + if ((rc=chdir(pwent->pw_name)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s/%s]", dir, pwent->pw_name); + errno = errsave; + } + return rc; +} + +#define CHECKED_KERNEL_CORE_ENV "_PROC_SYS_CORE_CHECKED_" +#define PROC_SYS_KERNEL_CORE_PID "/proc/sys/kernel/core_uses_pid" +#define PROC_SYS_KERNEL_CORE_PAT "/proc/sys/kernel/core_pattern" + +static void cl_coredump_signal_handler(int nsig); + +/* + * core_uses_pid(): + * + * returns {-1, 0, 1} + * -1: not supported + * 0: supported and disabled + * 1: supported and enabled + */ +#define BUF_MAX 256 +static int +core_uses_pid(void) +{ + const char * uses_pid_pathnames[] = {PROC_SYS_KERNEL_CORE_PID}; + const char * corepats_pathnames[] = {PROC_SYS_KERNEL_CORE_PAT}; + const char * goodpats [] = {"%t", "%p"}; + int j; + + + for (j=0; j < DIMOF(corepats_pathnames); ++j) { + int fd; + char buf[BUF_MAX]; + int rc; + int k; + + if ((fd = open(corepats_pathnames[j], O_RDONLY)) < 0) { + continue; + } + + memset(buf, 0, BUF_MAX); + rc = read(fd, buf, BUF_MAX - 1); /* Ensure it is always NULL terminated */ + close(fd); + + for (k=0; rc > 0 && k < DIMOF(goodpats); ++k) { + if (strstr(buf, goodpats[k]) != NULL) { + return 1; + } + } + + break; + } + for (j=0; j < DIMOF(uses_pid_pathnames); ++j) { + int fd; + char buf[2]; + int rc; + if ((fd = open(uses_pid_pathnames[j], O_RDONLY)) < 0) { + continue; + } + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc < 1) { + continue; + } + return (buf[0] == '1'); + } + setenv(CHECKED_KERNEL_CORE_ENV, "1", TRUE); + return -1; +} + +/* Enable/disable core dumps for ourselves and our child processes */ +int +cl_enable_coredumps(int doenable) +{ + int rc; + struct rlimit rlim; + + if ((rc = getrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Cannot get current core limit value."); + errno = errsave; + return rc; + } + if (rlim.rlim_max == 0 && geteuid() == 0) { + rlim.rlim_max = RLIM_INFINITY; + } + + rlim.rlim_cur = (doenable ? rlim.rlim_max : 0); + + if (doenable && rlim.rlim_max == 0) { + cl_log(LOG_WARNING + , "Not possible to enable core dumps (rlim_max is 0)"); + } + + if ((rc = setrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Unable to %s core dumps" + , doenable ? "enable" : "disable"); + errno = errsave; + return rc; + } + if (getenv(CHECKED_KERNEL_CORE_ENV) == NULL + && core_uses_pid() == 0) { + cl_log(LOG_WARNING + , "Core dumps could be lost if multiple dumps occur."); + cl_log(LOG_WARNING + , "Consider setting non-default value in %s" + " (or equivalent) for maximum supportability", PROC_SYS_KERNEL_CORE_PAT); + cl_log(LOG_WARNING + , "Consider setting %s (or equivalent) to" + " 1 for maximum supportability", PROC_SYS_KERNEL_CORE_PID); + } + return 0; +} + +/* + * SIGQUIT 3 Core Quit from keyboard + * SIGILL 4 Core Illegal Instruction + * SIGABRT 6 Core Abort signal from abort(3) + * SIGFPE 8 Core Floating point exception + * SIGSEGV 11 Core Invalid memory reference + * SIGBUS 10,7,10 Core Bus error (bad memory access) + * SIGSYS 2,-,12 Core Bad argument to routine (SVID) + * SIGTRAP 5 Core Trace/breakpoint trap + * SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2 BSD) + * SIGXFSZ 25,25,31 Core File size limit exceeded (4.2 BSD) + */ + +/* + * This function exists to allow security-sensitive programs + * to safely take core dumps. Such programs can't can't call + * cl_untaint_coredumps() alone - because it might cause a + * leak of confidential information - as information which should + * only be known by the "high-privilege" user id will be written + * into a core dump which is readable by the "low-privilege" user id. + * This is a bad thing. + * + * This function causes this program to call a special signal handler + * on receipt of any core dumping signal. This handler then does + * the following four things on receipt of a core dumping signal: + * + * 1) Set privileges to "maximum" on receipt of a signal + * 2) "untaint" themselves with regard to core dumping + * 3) set SIG_DFLT for the received signal + * 4) Kill themselves with the received core-dumping signal + * + * Any process *could* do this to get core dumps, but if your stack + * is screwed up, then the signal handler might not work. + * If you're core dumping because of a stack overflow, it certainly won't work. + * + * On the other hand, this function may work on some OSes that don't support + * prctl(2). This is an untested theory at this time... + */ +void +cl_set_all_coredump_signal_handlers(void) +{ + static const int coresigs [] = {SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV +#ifdef SIGBUS +, SIGBUS +#endif +#ifdef SIGSYS +, SIGSYS +#endif +#ifdef SIGTRAP +, SIGTRAP +#endif +#ifdef SIGXCPU +, SIGXCPU +#endif +#ifdef SIGXFSZ +, SIGXFSZ +#endif +}; + int j; + + for (j=0; j < DIMOF(coresigs); ++j) { + cl_set_coredump_signal_handler(coresigs[j]); + } +} + +/* + * See note above about why using this function directly is sometimes + * a bad idea, and you might need to use cl_set_all_coredump_signal_handlers() + * instead. + */ +void +cl_untaint_coredumps(void) +{ +#if defined(PR_SET_DUMPABLE) + prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL); +#endif +} +static void +cl_coredump_signal_handler(int nsig) +{ + return_to_orig_privs(); + if (geteuid() == 0) { + /* Put ALL privileges back to root... */ + if (setuid(0) < 0) { + cl_perror("cl_coredump_signal_handler: unable to setuid(0)"); + } + } + cl_untaint_coredumps(); /* Do the best we know how to do... */ + CL_SIGNAL(nsig, SIG_DFL); + kill(getpid(), nsig); +} + +void +cl_set_coredump_signal_handler(int nsig) +{ + CL_SIGNAL(nsig, cl_coredump_signal_handler); +} diff --git a/lib/clplumbing/cpulimits.c b/lib/clplumbing/cpulimits.c new file mode 100644 index 0000000..4c03f23 --- /dev/null +++ b/lib/clplumbing/cpulimits.c @@ -0,0 +1,219 @@ +/* + * Functions to put dynamic limits on CPU consumption. + * + * Copyright (C) 2003 IBM Corporation + * + * Author: <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 + * + ************************************************************************** + * + * This allows us to better catch runaway realtime processes that + * might otherwise hang the whole system (if they're POSIX realtime + * processes). + * + * We do this by getting a "lease" on CPU time, and then extending + * the lease every so often as real time elapses. Since we only + * extend the lease by a bounded amount computed on the basis of an + * upper bound of how much CPU the code is "expected" to consume during + * the lease interval, this means that if we go into an infinite + * loop, it is highly probable that this will be detected and our + * process will be terminated by the operating system with a SIGXCPU. + * + * If you want to handle this signal, then fine... Do so... + * + * If not, the default is to terminate the process and produce a core + * dump. This is a great default for debugging... + * + * + * The process is basically this: + * - Set the CPU percentage limit with cl_cpu_limit_setpercent() + * according to what you expect the CPU percentage to top out at + * measured over an interval at >= 60 seconds + * - Call cl_cpu_limit_ms_interval() to figure out how often to update + * the CPU limit (it returns milliseconds) + * - At least as often as indicated above, call cl_cpu_limit_update() + * to update our current CPU limit. + * + * These limits are approximate, so be a little conservative. + * If you've gone into an infinite loop, it'll likely get caught ;-) + * + * As of this writing, this code will never set the soft CPU limit less + * than four seconds, or greater than 60 seconds. + * + */ +#include <lha_internal.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <clplumbing/longclock.h> +#include <unistd.h> +#include <clplumbing/cpulimits.h> +#include <clplumbing/cl_log.h> + +static longclock_t nexttimetoupdate; + +/* How long between checking out CPU usage? */ +static int cpuinterval_ms = 0; + +/* How much cpu (in seconds) allowed at each check interval? */ +static int cpusecs; + +#define ROUND(foo) ((int)((foo)+0.5)) + + +/* + * Update our current CPU limit (via setrlimit) according to our + * current resource consumption, and our current cpu % limit + * + * We only set the soft CPU limit, and do not change the maximum + * (hard) CPU limit, but we respect it if it's already set. + * + * As a result, this code can be used by privileged and non-privileged + * processes. + */ + +static int +update_cpu_interval(void) +{ + struct rusage ru; + struct rlimit rlim; + unsigned long timesecs; + unsigned long microsec; + + /* Compute how much CPU we've used so far... */ + + getrusage(RUSAGE_SELF, &ru); + timesecs = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; + microsec = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec; + + /* Round up to the next higher second */ + if (microsec > 1000000) { + timesecs += 2; + }else{ + timesecs += 1; + } + + /* Compute our next CPU limit */ + timesecs += cpusecs; + + /* Figure out when we next need to update our CPU limit */ + nexttimetoupdate = add_longclock(time_longclock() + , msto_longclock(cpuinterval_ms)); + + getrlimit(RLIMIT_CPU, &rlim); + + /* Make sure we don't exceed the hard CPU limit (if set) */ + if (rlim.rlim_max != RLIM_INFINITY && timesecs > rlim.rlim_max) { + timesecs = rlim.rlim_max; + } +#if 0 + cl_log(LOG_DEBUG + , "Setting max CPU limit to %ld seconds", timesecs); +#endif + + /* Update the OS-level soft CPU limit */ + rlim.rlim_cur = timesecs; + return setrlimit(RLIMIT_CPU, &rlim); +} + +#define MININTERVAL 60 /* seconds */ + +int +cl_cpu_limit_setpercent(int ipercent) +{ + float percent; + int interval; + + if (ipercent > 99) { + ipercent = 99; + } + if (ipercent < 1) { + ipercent = 1; + } + percent = ipercent; + percent /= (float)100; + + interval= MININTERVAL; + + /* + * Compute how much CPU we will allow to be used + * for each check interval. + * + * Rules: + * - we won't require checking more often than + * every 60 seconds + * - we won't limit ourselves to less than + * 4 seconds of CPU per checking interval + */ + for (;;) { + cpusecs = ROUND((float)interval*percent); + if (cpusecs >= 4) { + break; + } + interval *= 2; + } + + /* + * Now compute how long to go between updates to our CPU limit + * from the perspective of the OS (via setrlimit(2)). + * + * We do the computation this way because the CPU limit + * can only be set to the nearest second, but timers can + * generally be set more accurately. + */ + cpuinterval_ms = (int)(((float)cpusecs / percent)*1000.0); + + cl_log(LOG_DEBUG + , "Limiting CPU: %d CPU seconds every %d milliseconds" + , cpusecs, cpuinterval_ms); + + return update_cpu_interval(); +} + +int +cl_cpu_limit_ms_interval(void) +{ + return cpuinterval_ms; +} + +int +cl_cpu_limit_update(void) +{ + longclock_t now = time_longclock(); + long msleft; + + if (cpuinterval_ms <= 0) { + return 0; + } + if (cmp_longclock(now, nexttimetoupdate) > 0) { + return update_cpu_interval(); + } + msleft = longclockto_ms(sub_longclock(nexttimetoupdate, now)); + if (msleft < 500) { + return update_cpu_interval(); + } + return 0; +} +int +cl_cpu_limit_disable(void) +{ + struct rlimit rlim; + getrlimit(RLIMIT_CPU, &rlim); + rlim.rlim_cur = rlim.rlim_max; + return setrlimit(RLIMIT_CPU, &rlim); +} diff --git a/lib/clplumbing/ipcsocket.c b/lib/clplumbing/ipcsocket.c new file mode 100644 index 0000000..14c3504 --- /dev/null +++ b/lib/clplumbing/ipcsocket.c @@ -0,0 +1,2767 @@ +/* + * ipcsocket unix domain socket implementation of IPC abstraction. + * + * Copyright (c) 2002 Xiaoxiang Liu <xiliu@ncsa.uiuc.edu> + * + * Stream support (c) 2004,2006 David Lee <t.d.lee@durham.ac.uk> + * Note: many of the variable/function names "*socket*" should be + * interpreted as having a more generic "ipc-channel-type" meaning. + * + * 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 <clplumbing/ipc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <clplumbing/cl_poll.h> + +#include <ha_msg.h> +/* avoid including cib.h - used in gshi's "late message" code to avoid + * printing insanely large messages + */ +#define F_CIB_CALLDATA "cib_calldata" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/uio.h> +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif +#ifdef HAVE_SYS_SYSLIMITS_H +# include <sys/syslimits.h> +#endif +#ifdef HAVE_SYS_CRED_H +# include <sys/cred.h> +#endif +#ifdef HAVE_SYS_UCRED_H +# include <sys/ucred.h> +#endif + +/* For 'getpeerucred()' (Solaris 10 upwards) */ +#ifdef HAVE_UCRED_H +# include <ucred.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif + +/* + * Normally use "socket" code. But on some OSes alternatives may be + * preferred (or necessary). + */ +#define HB_IPC_SOCKET 1 +#define HB_IPC_STREAM 2 +/* #define HB_IPC_ANOTHER 3 */ + +#ifndef HB_IPC_METHOD +# if defined(SO_PEERCRED) || defined(HAVE_GETPEEREID) \ + || defined(SCM_CREDS) || defined(HAVE_GETPEERUCRED) +# define HB_IPC_METHOD HB_IPC_SOCKET +# elif defined(HAVE_STROPTS_H) +# define HB_IPC_METHOD HB_IPC_STREAM +# else +# error. Surely we have sockets or streams... +# endif +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET +# include <sys/poll.h> +# include <netinet/in.h> +# include <sys/un.h> +#elif HB_IPC_METHOD == HB_IPC_STREAM +# include <stropts.h> +#else +# error "IPC type invalid" +#endif + +#include <sys/ioctl.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# define MAX_LISTEN_NUM 128 + +# ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +# endif + +# ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +# endif + +#endif /* HB_IPC_METHOD */ + +/*********************************************************************** + * + * Determine the IPC authentication scheme... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ +#ifdef SO_PEERCRED +# define USE_SO_PEERCRED +#elif HAVE_GETPEEREID +# define USE_GETPEEREID +#elif defined(SCM_CREDS) +# define USE_SCM_CREDS +#elif HAVE_GETPEERUCRED /* e.g. Solaris 10 upwards */ +# define USE_GETPEERUCRED +#elif HB_IPC_METHOD == HB_IPC_STREAM +# define USE_STREAM_CREDS +#else +# define USE_DUMMY_CREDS +/* This will make it compile, but attempts to authenticate + * will fail. This is a stopgap measure ;-) + */ +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# ifdef USE_BINDSTAT_CREDS +# ifndef SUN_LEN +# define SUN_LEN(ptr) ((size_t) (offsetof (sockaddr_un, sun_path) + strlen ((ptr)->sun_path)) +# endif +# endif + +#endif /* HB_IPC_METHOD */ + +/* wait connection private data. */ +struct SOCKET_WAIT_CONN_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* the domain socket. */ + int s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* the streams pipe */ + int pipefds[2]; +#endif +}; + +/* channel private data. */ +struct SOCKET_CH_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; + /* the domain socket. */ + int s; + /* the size of expecting data for below buffered message buf_msg */ + int remaining_data; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* The address of our peer - used by USE_BINDSTAT_CREDS version of + * socket_verify_auth() + */ + struct sockaddr_un *peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + uid_t farside_uid; + gid_t farside_gid; +#endif + + /* the buf used to save unfinished message */ + struct IPC_MESSAGE *buf_msg; +}; + +struct IPC_Stats { + long nsent; + long noutqueued; + long send_count; + long nreceived; + long ninqueued; + long recv_count; + int last_recv_errno; + int last_recv_rc; + int last_send_errno; + int last_send_rc; +}; + +static struct IPC_Stats SocketIPCStats = {0, 0, 0, 0}; +extern int debug_level; + +/* unix domain socket implementations of IPC functions. */ + +static int socket_resume_io(struct IPC_CHANNEL *ch); + +static struct IPC_MESSAGE* socket_message_new(struct IPC_CHANNEL*ch +, int msg_len); + +struct IPC_WAIT_CONNECTION *socket_wait_conn_new(GHashTable* ch_attrs); + +/* *** FIXME: This is also declared in 'ocf_ipc.c'. */ +struct IPC_CHANNEL* socket_client_channel_new(GHashTable *attrs); + +static struct IPC_CHANNEL* socket_server_channel_new(int sockfd); + +static struct IPC_CHANNEL * channel_new(int sockfd, int conntype, const char *pathname); +static int client_channel_new_auth(int sockfd); +static int verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid); + +typedef void (*DelProc)(IPC_Message*); + +static struct IPC_MESSAGE * ipcmsg_new(struct IPC_CHANNEL* ch, + const void* data, int len, void* private, DelProc d); + +static pid_t socket_get_farside_pid(int sockfd); + +extern int (*ipc_pollfunc_ptr)(struct pollfd *, nfds_t, int); + +static int socket_resume_io_read(struct IPC_CHANNEL *ch, int*, gboolean read1anyway); + +static struct IPC_OPS socket_ops; +static gboolean ipc_time_debug_flag = TRUE; + +void +set_ipc_time_debug_flag(gboolean flag) +{ + ipc_time_debug_flag = flag; +} + +#ifdef IPC_TIME_DEBUG + +extern struct ha_msg* wirefmt2msg(const char* s, size_t length, int flag); +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); + +#define SET_ENQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->enqueue_time, &t, sizeof(longclock_t)) +#define SET_SEND_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->send_time, &t, sizeof(longclock_t)) +#define SET_RECV_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->recv_time, &t, sizeof(longclock_t)) +#define SET_DEQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->dequeue_time, &t, sizeof(longclock_t)) + +static +longclock_t +get_enqueue_time(IPC_Message *ipcmsg) +{ + longclock_t t; + + memcpy(&t, + &(((struct SOCKET_MSG_HEAD *)ipcmsg->msg_buf)->enqueue_time), + sizeof(longclock_t)); + + return t; +} + +int +timediff(longclock_t t1, longclock_t t2) +{ + longclock_t remain; + + remain = sub_longclock(t1, t2); + + return longclockto_ms(remain); +} + +void +ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos) +{ + int msdiff = 0; + longclock_t lnow = time_longclock(); + char positions[4][16]={ + "enqueue", + "send", + "recv", + "dequeue"}; + + if (ipc_time_debug_flag == FALSE) { + return ; + } + + if (ipcmsg->msg_body == NULL + || ipcmsg->msg_buf == NULL) { + cl_log(LOG_ERR, "msg_body =%p, msg_bu=%p", + ipcmsg->msg_body, ipcmsg->msg_buf); + abort(); + return; + } + + switch(whichpos) { + case MSGPOS_ENQUEUE: + SET_ENQUEUE_TIME(ipcmsg, lnow); + break; + case MSGPOS_SEND: + SET_SEND_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_RECV: + SET_RECV_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_DEQUEUE: + SET_DEQUEUE_TIME(ipcmsg, lnow); + + checktime: + msdiff = timediff(lnow, get_enqueue_time(ipcmsg)); + if (msdiff > MAXIPCTIME) { + struct ha_msg* hamsg = NULL; + cl_log(LOG_WARNING, + " message delayed from enqueue to %s %d ms " + "(enqueue-time=%lu, peer pid=%d) ", + positions[whichpos], + msdiff, + longclockto_ms(get_enqueue_time(ipcmsg)), + ch->farside_pid); + + (void)hamsg; +#if 0 + hamsg = wirefmt2msg(ipcmsg->msg_body, ipcmsg->msg_len, 0); + if (hamsg != NULL) { + struct ha_msg *crm_data = NULL; + crm_data = cl_get_struct( + hamsg, F_CRM_DATA); + + if(crm_data == NULL) { + crm_data = cl_get_struct( + hamsg, F_CIB_CALLDATA); + } + if(crm_data != NULL) { + cl_msg_remove_value( + hamsg, crm_data); + } + + cl_log_message(LOG_DEBUG, hamsg); + ha_msg_del(hamsg); + } else { + if (!ipcmsg) { + cl_log(LOG_ERR, + "IPC msg 0x%lx is unallocated" + , (gulong)ipcmsg); + return; + } + if (!ipcmsg->msg_body) { + cl_log(LOG_ERR, + "IPC msg body 0x%lx is unallocated" + , (gulong)ipcmsg->msg_body); + return; + } + } +#endif + + } + break; + default: + cl_log(LOG_ERR, "wrong position value in IPC:%d", whichpos); + return; + } +} +#endif + +void dump_ipc_info(const IPC_Channel* chan); + +#undef AUDIT_CHANNELS + +#ifndef AUDIT_CHANNELS +# define CHANAUDIT(ch) /*NOTHING */ +#else +# define CHANAUDIT(ch) socket_chan_audit(ch) +# define MAXPID 65535 + +static void +socket_chan_audit(const struct IPC_CHANNEL* ch) +{ + int badch = FALSE; + + struct SOCKET_CH_PRIVATE *chp; + struct stat b; + + if ((chp = ch->ch_private) == NULL) { + cl_log(LOG_CRIT, "Bad ch_private"); + badch = TRUE; + } + if (ch->ops != &socket_ops) { + cl_log(LOG_CRIT, "Bad socket_ops"); + badch = TRUE; + } + if (ch->ch_status == IPC_DISCONNECT) { + return; + } + if (!IPC_ISRCONN(ch)) { + cl_log(LOG_CRIT, "Bad ch_status [%d]", ch->ch_status); + badch = TRUE; + } + if (ch->farside_pid < 0 || ch->farside_pid > MAXPID) { + cl_log(LOG_CRIT, "Bad farside_pid"); + badch = TRUE; + } + if (fstat(chp->s, &b) < 0) { + badch = TRUE; + } else if ((b.st_mode & S_IFMT) != S_IFSOCK) { + cl_log(LOG_CRIT, "channel @ 0x%lx: not a socket" + , (unsigned long)ch); + badch = TRUE; + } + if (chp->remaining_data < 0) { + cl_log(LOG_CRIT, "Negative remaining_data"); + badch = TRUE; + } + if (chp->remaining_data < 0 || chp->remaining_data > MAXMSG) { + cl_log(LOG_CRIT, "Excessive/bad remaining_data"); + badch = TRUE; + } + if (chp->remaining_data && chp->buf_msg == NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx]" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (chp->remaining_data == 0 && chp->buf_msg != NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx] (2)" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (ch->send_queue == NULL || ch->recv_queue == NULL) { + cl_log(LOG_CRIT, "bad send/recv queue"); + badch = TRUE; + } + if (ch->recv_queue->current_qlen < 0 + || ch->recv_queue->current_qlen > ch->recv_queue->max_qlen) { + cl_log(LOG_CRIT, "bad recv queue"); + badch = TRUE; + } + if (ch->send_queue->current_qlen < 0 + || ch->send_queue->current_qlen > ch->send_queue->max_qlen) { + cl_log(LOG_CRIT, "bad send_queue"); + badch = TRUE; + } + if (badch) { + cl_log(LOG_CRIT, "Bad channel @ 0x%lx", (unsigned long)ch); + dump_ipc_info(ch); + abort(); + } +} +#endif + +#ifdef CHEAT_CHECKS +long SeqNums[32]; + +static long +cheat_get_sequence(IPC_Message* msg) +{ + const char header [] = "String-"; + size_t header_len = sizeof(header)-1; + char * body; + + if (msg == NULL || msg->msg_len < sizeof(header) + || msg->msg_len > sizeof(header) + 10 + || strncmp(msg->msg_body, header, header_len) != 0) { + return -1L; + } + body = msg->msg_body; + return atol(body+header_len); +} +static char SavedReadBody[32]; +static char SavedReceivedBody[32]; +static char SavedQueuedBody[32]; +static char SavedSentBody[32]; +#ifndef MIN +# define MIN(a,b) (a < b ? a : b) +#endif + +static void +save_body(struct IPC_MESSAGE *msg, char * savearea, size_t length) +{ + int mlen = strnlen(msg->msg_body, MIN(length, msg->msg_len)); + memcpy(savearea, msg->msg_body, mlen); + savearea[mlen] = EOS; +} + +static void +audit_readmsgq_msg(gpointer msg, gpointer user_data) +{ + long cheatseq = cheat_get_sequence(msg); + + if (cheatseq < SeqNums[1] || cheatseq > SeqNums[2]) { + cl_log(LOG_ERR + , "Read Q Message %ld not in range [%ld:%ld]" + , cheatseq, SeqNums[1], SeqNums[2]); + } +} + +static void +saveandcheck(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg, char * savearea +, size_t savesize, long* lastseq, const char * text) +{ + long cheatseq = cheat_get_sequence(msg); + + save_body(msg, savearea, savesize); + if (*lastseq != 0 ) { + if (cheatseq != *lastseq +1) { + int j; + cl_log(LOG_ERR + , "%s packets out of sequence! %ld versus %ld [pid %d]" + , text, cheatseq, *lastseq, (int)getpid()); + dump_ipc_info(ch); + for (j=0; j < 4; ++j) { + cl_log(LOG_DEBUG + , "SeqNums[%d] = %ld" + , j, SeqNums[j]); + } + cl_log(LOG_ERR + , "SocketIPCStats.nsent = %ld" + , SocketIPCStats.nsent); + cl_log(LOG_ERR + , "SocketIPCStats.noutqueued = %ld" + , SocketIPCStats.noutqueued); + cl_log(LOG_ERR + , "SocketIPCStats.nreceived = %ld" + , SocketIPCStats.nreceived); + cl_log(LOG_ERR + , "SocketIPCStats.ninqueued = %ld" + , SocketIPCStats.ninqueued); + } + + } + g_list_foreach(ch->recv_queue->queue, audit_readmsgq_msg, NULL); + if (cheatseq > 0) { + *lastseq = cheatseq; + } +} + +# define CHECKFOO(which, ch, msg, area, text) { \ + saveandcheck(ch,msg,area,sizeof(area),SeqNums+which,text); \ + } +#else +# define CHECKFOO(which, ch, msg, area, text) /* Nothing */ +#endif + +static void +dump_msg(struct IPC_MESSAGE *msg, const char * label) +{ +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "%s packet (length %d) [%s] %ld pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , cheat_get_sequence(msg), (int)getpid()); +#else + cl_log(LOG_DEBUG, "%s length %d [%s] pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , (int)getpid()); +#endif +} + +static void +dump_msgq_msg(gpointer data, gpointer user_data) +{ + dump_msg(data, user_data); +} + +void +dump_ipc_info(const IPC_Channel* chan) +{ + char squeue[] = "Send queue"; + char rqueue[] = "Receive queue"; +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "Saved Last Body read[%s]", SavedReadBody); + cl_log(LOG_DEBUG, "Saved Last Body received[%s]", SavedReceivedBody); + cl_log(LOG_DEBUG, "Saved Last Body Queued[%s]", SavedQueuedBody); + cl_log(LOG_DEBUG, "Saved Last Body Sent[%s]", SavedSentBody); +#endif + g_list_foreach(chan->send_queue->queue, dump_msgq_msg, squeue); + g_list_foreach(chan->recv_queue->queue, dump_msgq_msg, rqueue); + CHANAUDIT(chan); +} + +/* destroy socket wait channel */ +static void +socket_destroy_wait_conn(struct IPC_WAIT_CONNECTION * wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + + if (wc != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + if (wc->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, wc->s); + } + close(wc->s); + cl_poll_ignore(wc->s); + unlink(wc->path_name); + wc->s = -1; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + cl_poll_ignore(wc->pipefds[0]); + if (wc->pipefds[0] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[0] %d" + , __FUNCTION__, wc->pipefds[0]); + } + wc->pipefds[0] = -1; + } + if (wc->pipefds[1] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[1] %d" + , __FUNCTION__, wc->pipefds[1]); + } + wc->pipefds[0] = -1; + } + unlink(wc->path_name); +#endif + g_free(wc); + } + g_free((void*) wait_conn); +} + +/* return a fd which can be listened on for new connections. */ +static int +socket_wait_selectfd(struct IPC_WAIT_CONNECTION *wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + return (wc == NULL ? -1 : wc->s); +#elif HB_IPC_METHOD == HB_IPC_STREAM + return (wc == NULL ? -1 : wc->pipefds[0]); +#endif +} + +/* socket accept connection. */ +static struct IPC_CHANNEL* +socket_accept_connection(struct IPC_WAIT_CONNECTION * wait_conn +, struct IPC_AUTH *auth_info) +{ + struct IPC_CHANNEL * ch = NULL; + int s; + int new_sock; + struct SOCKET_WAIT_CONN_PRIVATE* conn_private; + struct SOCKET_CH_PRIVATE * ch_private ; + int auth_result = IPC_FAIL; + int saveerrno=errno; + gboolean was_error = FALSE; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* make peer_addr a pointer so it can be used by the + * USE_BINDSTAT_CREDS implementation of socket_verify_auth() + */ + struct sockaddr_un * peer_addr = NULL; + socklen_t sin_size; +#elif HB_IPC_METHOD == HB_IPC_STREAM + struct strrecvfd strrecvfd; +#endif + + /* get select fd */ + + s = wait_conn->ops->get_select_fd(wait_conn); + if (s < 0) { + cl_log(LOG_ERR, "get_select_fd: invalid fd"); + return NULL; + } + + /* Get client connection. */ +#if HB_IPC_METHOD == HB_IPC_SOCKET + peer_addr = g_new(struct sockaddr_un, 1); + *peer_addr->sun_path = '\0'; + sin_size = sizeof(struct sockaddr_un); + new_sock = accept(s, (struct sockaddr *)peer_addr, &sin_size); +#elif HB_IPC_METHOD == HB_IPC_STREAM + if (ioctl(s, I_RECVFD, &strrecvfd) == -1) { + new_sock = -1; + } + else { + new_sock = strrecvfd.fd; + } +#endif + saveerrno=errno; + if (new_sock == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + cl_perror("socket_accept_connection: accept(sock=%d)" + , s); + } + was_error = TRUE; + + } else { + if ((ch = socket_server_channel_new(new_sock)) == NULL) { + cl_log(LOG_ERR + , "socket_accept_connection:" + " Can't create new channel"); + was_error = TRUE; + } else { + conn_private=(struct SOCKET_WAIT_CONN_PRIVATE*) + ( wait_conn->ch_private); + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + strncpy(ch_private->path_name,conn_private->path_name + , sizeof(conn_private->path_name)); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + ch_private->peer_addr = peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + ch_private->farside_uid = strrecvfd.uid; + ch_private->farside_gid = strrecvfd.gid; +#endif + } + } + + /* Verify the client authorization information. */ + if(was_error == FALSE) { + auth_result = ch->ops->verify_auth(ch, auth_info); + if (auth_result == IPC_OK) { + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(new_sock); + return ch; + } + saveerrno=errno; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + g_free(peer_addr); + peer_addr = NULL; +#endif + errno=saveerrno; + return NULL; +} + +/* + * Called by socket_destroy(). Disconnect the connection + * and set ch_status to IPC_DISCONNECT. + * + * parameters : + * ch (IN) the pointer to the channel. + * + * return values : + * IPC_OK the connection is disconnected successfully. + * IPC_FAIL operation fails. +*/ + +static int +socket_disconnect(struct IPC_CHANNEL* ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s(sock=%d, ch=0x%lx){" + , __FUNCTION__ + , conn_info->s, (unsigned long)ch); + } +#if 0 + if (ch->ch_status != IPC_DISCONNECT) { + cl_log(LOG_INFO, "forced disconnect for fd %d", conn_info->s); + } +#endif + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + + if (conn_info->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, conn_info->s); + } + close(conn_info->s); + cl_poll_ignore(conn_info->s); + conn_info->s = -1; + } + ch->ch_status = IPC_DISCONNECT; + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d, ch=0x%lx)*/" + , __FUNCTION__, conn_info->s, (unsigned long)ch); + } + return IPC_OK; +} + +/* + * destroy a ipc queue and clean all memory space assigned to this queue. + * parameters: + * q (IN) the pointer to the queue which should be destroied. + * + * FIXME: This function does not free up messages that might + * be in the queue. + */ + +static void +socket_destroy_queue(struct IPC_QUEUE * q) +{ + g_list_free(q->queue); + + g_free((void *) q); +} + +static void +socket_destroy_channel(struct IPC_CHANNEL * ch) +{ + --ch->refcount; + if (ch->refcount > 0) { + return; + } + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + if (debug_level > 1) { + cl_log(LOG_DEBUG, "socket_destroy(ch=0x%lx){" + , (unsigned long)ch); + } + socket_disconnect(ch); + socket_destroy_queue(ch->send_queue); + socket_destroy_queue(ch->recv_queue); + + if (ch->pool) { + ipc_bufpool_unref(ch->pool); + } + + if (ch->ch_private != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct SOCKET_CH_PRIVATE *priv = (struct SOCKET_CH_PRIVATE *) + ch->ch_private; + if(priv->peer_addr != NULL) { + if (*priv->peer_addr->sun_path) { + unlink(priv->peer_addr->sun_path); + } + g_free((void*)(priv->peer_addr)); + } +#endif + g_free((void*)(ch->ch_private)); + } + memset(ch, 0xff, sizeof(*ch)); + g_free((void*)ch); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*socket_destroy(ch=0x%lx)*/" + , (unsigned long)ch); + } +} + +static int +socket_check_disc_pending(struct IPC_CHANNEL* ch) +{ + int rc; + struct pollfd sockpoll; + + if (ch->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "check_disc_pending() already disconnected"); + return IPC_BROKEN; + } + if (ch->recv_queue->current_qlen > 0) { + return IPC_OK; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + sockpoll.events = POLLIN; + + rc = ipc_pollfunc_ptr(&sockpoll, 1, 0); + + if (rc < 0) { + cl_log(LOG_INFO + , "socket_check_disc_pending() bad poll call"); + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + + if (sockpoll.revents & POLLHUP) { + if (sockpoll.revents & POLLIN) { + ch->ch_status = IPC_DISC_PENDING; + } else { +#if 1 + cl_log(LOG_INFO, "HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + } + if (sockpoll.revents & POLLIN) { + int dummy; + socket_resume_io_read(ch, &dummy, FALSE); + } + return IPC_OK; +} + +static int +socket_initiate_connection(struct IPC_CHANNEL * ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un peer_addr; /* connector's address information */ +#elif HB_IPC_METHOD == HB_IPC_STREAM +#endif + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* Prepare the socket */ + memset(&peer_addr, 0, sizeof(peer_addr)); + peer_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(conn_info->path_name) >= sizeof(peer_addr.sun_path)) { + return IPC_FAIL; + } + strncpy(peer_addr.sun_path, conn_info->path_name, sizeof(peer_addr.sun_path)); + + /* Send connection request */ + if (connect(conn_info->s, (struct sockaddr *)&peer_addr + , sizeof(struct sockaddr_un)) == -1) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(conn_info->s); + return IPC_OK; +} + +static void +socket_set_high_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->high_flow_callback = callback; + ch->high_flow_userdata = userdata; +} + +static void +socket_set_low_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->low_flow_callback = callback; + ch->low_flow_userdata = userdata; +} + +static void +socket_check_flow_control(struct IPC_CHANNEL* ch, + int orig_qlen, + int curr_qlen) +{ + if (!IPC_ISRCONN(ch)) { + return; + } + + if (curr_qlen >= ch->high_flow_mark + && ch->high_flow_callback) { + ch->high_flow_callback(ch, ch->high_flow_userdata); + } + + if (curr_qlen <= ch->low_flow_mark + && orig_qlen > ch->low_flow_mark + && ch->low_flow_callback) { + ch->low_flow_callback(ch, ch->low_flow_userdata); + } +} + +static int +socket_send(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg) +{ + int orig_qlen; + int diff; + struct IPC_MESSAGE* newmsg; + + if (msg->msg_len > MAXMSG) { + cl_log(LOG_ERR, "%s: sorry, cannot send messages " + "bigger than %d (requested %lu)", + __FUNCTION__, MAXMSG, (unsigned long)msg->msg_len); + return IPC_FAIL; + } + if (msg->msg_len < 0) { + cl_log(LOG_ERR, "socket_send: " + "invalid message"); + return IPC_FAIL; + } + + if (ch->ch_status != IPC_CONNECT) { + return IPC_FAIL; + } + + ch->ops->resume_io(ch); + + if (ch->send_queue->maxqlen_cnt && + time(NULL) - ch->send_queue->last_maxqlen_warn >= 60) { + cl_log(LOG_ERR, "%u messages dropped on a non-blocking channel (send queue maximum length %d)", + ch->send_queue->maxqlen_cnt, (int)ch->send_queue->max_qlen); + ch->send_queue->maxqlen_cnt = 0; + } + if ( !ch->should_send_block && + ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (!ch->send_queue->maxqlen_cnt) { + ch->send_queue->last_maxqlen_warn = time(NULL); + } + ch->send_queue->maxqlen_cnt++; + + if (ch->should_block_fail) { + return IPC_FAIL; + } else { + return IPC_OK; + } + } + + while (ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (ch->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "socket_send:" + " message queue exceeded and IPC not connected"); + return IPC_FAIL; + } + cl_shortsleep(); + ch->ops->resume_io(ch); + } + + /* add the message into the send queue */ + CHECKFOO(0,ch, msg, SavedQueuedBody, "queued message"); + SocketIPCStats.noutqueued++; + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + + if(msg->msg_done) { + msg->msg_done(msg); + }; + msg = newmsg; + } +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,msg, MSGPOS_ENQUEUE); +#endif + ch->send_queue->queue = g_list_append(ch->send_queue->queue, + msg); + orig_qlen = ch->send_queue->current_qlen++; + + socket_check_flow_control(ch, orig_qlen, orig_qlen +1 ); + + /* resume io */ + ch->ops->resume_io(ch); + return IPC_OK; +} + +static int +socket_recv(struct IPC_CHANNEL * ch, struct IPC_MESSAGE** message) +{ + GList *element; + + int nbytes; + int result; + + socket_resume_io(ch); + result = socket_resume_io_read(ch, &nbytes, TRUE); + + *message = NULL; + + if (ch->recv_queue->current_qlen == 0) { + return result != IPC_OK ? result : IPC_FAIL; + /*return IPC_OK;*/ + } + element = g_list_first(ch->recv_queue->queue); + + if (element == NULL) { + /* Internal accounting error, but correctable */ + cl_log(LOG_ERR + , "recv failure: qlen (%ld) > 0, but no message found." + , (long)ch->recv_queue->current_qlen); + ch->recv_queue->current_qlen = 0; + return IPC_FAIL; + } + *message = (struct IPC_MESSAGE *) (element->data); +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, *message, MSGPOS_DEQUEUE); +#endif + + CHECKFOO(1,ch, *message, SavedReadBody, "read message"); + SocketIPCStats.nreceived++; + ch->recv_queue->queue = g_list_remove(ch->recv_queue->queue + , element->data); + ch->recv_queue->current_qlen--; + return IPC_OK; +} + +static int +socket_check_poll(struct IPC_CHANNEL * ch +, struct pollfd * sockpoll) +{ + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_OK; + } + if (sockpoll->revents & POLLHUP) { + /* If input present, or this is an output-only poll... */ + if (sockpoll->revents & POLLIN + || (sockpoll-> events & POLLIN) == 0 ) { + ch->ch_status = IPC_DISC_PENDING; + return IPC_OK; + } +#if 1 + cl_log(LOG_INFO, "socket_check_poll(): HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + + } else if (sockpoll->revents & (POLLNVAL|POLLERR)) { + /* Have we already closed the socket? */ + if (fcntl(sockpoll->fd, F_GETFL) < 0) { + cl_perror("socket_check_poll(pid %d): bad fd [%d]" + , (int) getpid(), sockpoll->fd); + ch->ch_status = IPC_DISCONNECT; + return IPC_OK; + } + cl_log(LOG_ERR + , "revents failure: fd %d, flags 0x%x" + , sockpoll->fd, sockpoll->revents); + errno = EINVAL; + return IPC_FAIL; + } + return IPC_OK; +} + +static int +socket_waitfor(struct IPC_CHANNEL * ch +, gboolean (*finished)(struct IPC_CHANNEL * ch)) +{ + struct pollfd sockpoll; + + CHANAUDIT(ch); + if (finished(ch)) { + return IPC_OK; + } + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + + while (!finished(ch) && IPC_ISRCONN(ch)) { + int rc; + + sockpoll.events = POLLIN; + + /* Cannot call resume_io after the call to finished() + * and before the call to poll because we might + * change the state of the thing finished() is + * waiting for. + * This means that the poll call below would be + * not only pointless, but might + * make us hang forever waiting for this + * event which has already happened + */ + if (ch->send_queue->current_qlen > 0) { + sockpoll.events |= POLLOUT; + } + + rc = ipc_pollfunc_ptr(&sockpoll, 1, -1); + + if (rc < 0) { + return (errno == EINTR ? IPC_INTR : IPC_FAIL); + } + + rc = socket_check_poll(ch, &sockpoll); + if (sockpoll.revents & POLLIN) { + socket_resume_io(ch); + } + if (rc != IPC_OK) { + CHANAUDIT(ch); + return rc; + } + } + + CHANAUDIT(ch); + return IPC_OK; +} + +static int +socket_waitin(struct IPC_CHANNEL * ch) +{ + return socket_waitfor(ch, ch->ops->is_message_pending); +} +static gboolean +socket_is_output_flushed(struct IPC_CHANNEL * ch) +{ + return ! ch->ops->is_sending_blocked(ch); +} + +static int +socket_waitout(struct IPC_CHANNEL * ch) +{ + int rc; + CHANAUDIT(ch); + rc = socket_waitfor(ch, socket_is_output_flushed); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "socket_waitout failure: rc = %d", rc); + } else if (ch->ops->is_sending_blocked(ch)) { + cl_log(LOG_ERR, "socket_waitout output still blocked"); + } + CHANAUDIT(ch); + return rc; +} + +static gboolean +socket_is_message_pending(struct IPC_CHANNEL * ch) +{ + int nbytes; + + socket_resume_io_read(ch, &nbytes, TRUE); + ch->ops->resume_io(ch); + if (ch->recv_queue->current_qlen > 0) { + return TRUE; + } + + return !IPC_ISRCONN(ch); +} + +static gboolean +socket_is_output_pending(struct IPC_CHANNEL * ch) +{ + socket_resume_io(ch); + return ch->ch_status == IPC_CONNECT + && ch->send_queue->current_qlen > 0; +} + +static gboolean +socket_is_sendq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->send_queue->current_qlen == ch->send_queue->max_qlen); +} + +static gboolean +socket_is_recvq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->recv_queue->current_qlen == ch->recv_queue->max_qlen); +} + +static int +socket_get_conntype(struct IPC_CHANNEL* ch) +{ + return ch->conntype; +} + +static int +socket_assert_auth(struct IPC_CHANNEL *ch, GHashTable *auth) +{ + cl_log(LOG_ERR + , "the assert_auth function for domain socket is not implemented"); + return IPC_FAIL; +} + +static int +socket_resume_io_read(struct IPC_CHANNEL *ch, int* nbytes, gboolean read1anyway) +{ + struct SOCKET_CH_PRIVATE* conn_info; + int retcode = IPC_OK; + struct pollfd sockpoll; + int debug_loopcount = 0; + int debug_bytecount = 0; + size_t maxqlen = ch->recv_queue->max_qlen; + struct ipc_bufpool* pool = ch->pool; + int nmsgs = 0; + int spaceneeded; + *nbytes = 0; + + CHANAUDIT(ch); + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + + if (pool == NULL) { + ch->pool = pool = ipc_bufpool_new(0); + if (pool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for ipc pool failed"); + return IPC_FAIL; + } + } + + if (ipc_bufpool_full(pool, ch, &spaceneeded)) { + struct ipc_bufpool* newpool; + + newpool = ipc_bufpool_new(spaceneeded); + if (newpool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for a new ipc pool failed"); + return IPC_FAIL; + } + + ipc_bufpool_partial_copy(newpool, pool); + ipc_bufpool_unref(pool); + ch->pool = pool = newpool; + } + if (maxqlen <= 0 && read1anyway) { + maxqlen = 1; + } + if (ch->recv_queue->current_qlen < maxqlen && retcode == IPC_OK) { + void * msg_begin; + int msg_len; + int len; +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int flags, rc; +#endif + + CHANAUDIT(ch); + ++debug_loopcount; + + len = ipc_bufpool_spaceleft(pool); + msg_begin = pool->currpos; + + CHANAUDIT(ch); + + /* Now try to receive some data */ + +#if HB_IPC_METHOD == HB_IPC_SOCKET + msg_len = recv(conn_info->s, msg_begin, len, MSG_DONTWAIT); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = len; + d.len = 0; + d.buf = msg_begin; + flags = 0; + rc = getmsg(conn_info->s, NULL, &d, &flags); + msg_len = (rc < 0) ? rc : d.len; +#endif + SocketIPCStats.last_recv_rc = msg_len; + SocketIPCStats.last_recv_errno = errno; + ++SocketIPCStats.recv_count; + + /* Did we get an error? */ + if (msg_len < 0) { + switch (errno) { + case EAGAIN: + if (ch->ch_status==IPC_DISC_PENDING) { + ch->ch_status =IPC_DISCONNECT; + retcode = IPC_BROKEN; + } + break; + + case ECONNREFUSED: + case ECONNRESET: + ch->ch_status = IPC_DISC_PENDING; + retcode= socket_check_disc_pending(ch); + break; + + default: + cl_perror("socket_resume_io_read" + ": unknown recv error, peerpid=%d", + ch->farside_pid); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + + } else if (msg_len == 0) { + ch->ch_status = IPC_DISC_PENDING; + if(ch->recv_queue->current_qlen <= 0) { + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } + } else { + /* We read something! */ + /* Note that all previous cases break out of the loop */ + debug_bytecount += msg_len; + *nbytes = msg_len; + nmsgs = ipc_bufpool_update(pool, ch, msg_len, ch->recv_queue) ; + + if (nmsgs < 0) { + /* we didn't like the other side */ + cl_log(LOG_ERR, "socket_resume_io_read: " + "disconnecting the other side"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } else { + SocketIPCStats.ninqueued += nmsgs; + } + } + } + + /* Check for errors uncaught by recv() */ + /* NOTE: It doesn't seem right we have to do this every time */ + /* FIXME?? */ + + memset(&sockpoll,0, sizeof(struct pollfd)); + if ((retcode == IPC_OK) + && (sockpoll.fd = conn_info->s) >= 0) { + /* Just check for errors, not for data */ + sockpoll.events = 0; + ipc_pollfunc_ptr(&sockpoll, 1, 0); + retcode = socket_check_poll(ch, &sockpoll); + } + + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io_write(struct IPC_CHANNEL *ch, int* nmsg) +{ + int retcode = IPC_OK; + struct SOCKET_CH_PRIVATE* conn_info; + + CHANAUDIT(ch); + *nmsg = 0; + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + while (ch->ch_status == IPC_CONNECT + && retcode == IPC_OK + && ch->send_queue->current_qlen > 0) { + + GList * element; + struct IPC_MESSAGE * msg; + struct SOCKET_MSG_HEAD head; + struct IPC_MESSAGE* oldmsg = NULL; + int sendrc = 0; + struct IPC_MESSAGE* newmsg; + char* p; + unsigned int bytes_remaining; + int diff; + + CHANAUDIT(ch); + element = g_list_first(ch->send_queue->queue); + if (element == NULL) { + /* OOPS! - correct consistency problem */ + ch->send_queue->current_qlen = 0; + break; + } + msg = (struct IPC_MESSAGE *) (element->data); + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + oldmsg = msg; + msg = newmsg; + } + + head.msg_len = msg->msg_len; + head.magic = HEADMAGIC; + memcpy(msg->msg_buf, &head, sizeof(struct SOCKET_MSG_HEAD)); + + if (ch->bytes_remaining == 0) { + /*we start to send a new message*/ +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, msg, MSGPOS_SEND); +#endif + bytes_remaining = msg->msg_len + ch->msgpad; + p = msg->msg_buf; + } else { + bytes_remaining = ch->bytes_remaining; + p = ((char*)msg->msg_buf) + msg->msg_len + ch->msgpad + - bytes_remaining; + + } + + sendrc = 0; + + do { +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int msglen, putmsgrc; +#endif + + CHANAUDIT(ch); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + sendrc = send(conn_info->s, p + , bytes_remaining, (MSG_DONTWAIT|MSG_NOSIGNAL)); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = 0; + d.len = msglen = bytes_remaining; + d.buf = p; + putmsgrc = putmsg(conn_info->s, NULL, &d, 0); + sendrc = putmsgrc == 0 ? msglen : -1; +#endif + SocketIPCStats.last_send_rc = sendrc; + SocketIPCStats.last_send_errno = errno; + ++SocketIPCStats.send_count; + + if (sendrc <= 0) { + break; + } else { + p = p + sendrc; + bytes_remaining -= sendrc; + } + + } while(bytes_remaining > 0 ); + + ch->bytes_remaining = bytes_remaining; + + if (sendrc < 0) { + switch (errno) { + case EAGAIN: + retcode = IPC_OK; + break; + case EPIPE: + ch->ch_status = IPC_DISC_PENDING; + socket_check_disc_pending(ch); + retcode = IPC_BROKEN; + break; + default: + cl_perror("socket_resume_io_write" + ": send2 bad errno"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + break; + } else { + int orig_qlen; + + CHECKFOO(3,ch, msg, SavedSentBody, "sent message") + + if (oldmsg) { + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + msg=oldmsg; + } + + if(ch->bytes_remaining ==0) { + ch->send_queue->queue = g_list_remove(ch->send_queue->queue, msg); + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + + SocketIPCStats.nsent++; + orig_qlen = ch->send_queue->current_qlen--; + socket_check_flow_control(ch, orig_qlen, orig_qlen -1 ); + (*nmsg)++; + } + } + } + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io(struct IPC_CHANNEL *ch) +{ + int rc1 = IPC_OK; + int rc2 = IPC_OK; + int nwmsg = 1; + int nbytes_r = 1; + gboolean OKonce = FALSE; + + CHANAUDIT(ch); + if (!IPC_ISRCONN(ch)) { + return IPC_BROKEN; + } + + do { + if (nbytes_r > 0) { + rc1 = socket_resume_io_read(ch, &nbytes_r, FALSE); + } + if (nwmsg > 0) { + nwmsg = 0; + rc2 = socket_resume_io_write(ch, &nwmsg); + } + if (rc1 == IPC_OK || rc2 == IPC_OK) { + OKonce = TRUE; + } + } while ((nbytes_r > 0 || nwmsg > 0) && IPC_ISRCONN(ch)); + + if (IPC_ISRCONN(ch)) { + if (rc1 != IPC_OK) { + cl_log(LOG_ERR + , "socket_resume_io_read() failure"); + } + if (rc2 != IPC_OK && IPC_CONNECT == ch->ch_status) { + cl_log(LOG_ERR + , "socket_resume_io_write() failure"); + } + } else { + return (OKonce ? IPC_OK : IPC_BROKEN); + } + + return (rc1 != IPC_OK ? rc1 : rc2); +} + +static int +socket_get_recv_fd(struct IPC_CHANNEL *ch) +{ + struct SOCKET_CH_PRIVATE* chp = ch ->ch_private; + + return (chp == NULL ? -1 : chp->s); +} + +static int +socket_get_send_fd(struct IPC_CHANNEL *ch) +{ + return socket_get_recv_fd(ch); +} + +static void +socket_adjust_buf(struct IPC_CHANNEL *ch, int optname, unsigned q_len) +{ + const char *direction = optname == SO_SNDBUF ? "snd" : "rcv"; + int fd = socket_get_send_fd(ch); + unsigned byte; + + /* Arbitrary scaling. + * DEFAULT_MAX_QLEN is 64, default socket buf is often 64k to 128k, + * at least on those linux I checked. + * Keep that ratio, and allow for some overhead. */ + if (q_len == 0) + /* client does not want anything, + * reduce system buffers as well */ + byte = 4096; + else if (q_len < 512) + byte = (32 + q_len) * 1024; + else + byte = q_len * 1024; + + if (0 == setsockopt(fd, SOL_SOCKET, optname, &byte, sizeof(byte))) { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "adjusted %sbuf size to %u", + direction, byte); + } + } else { + /* If this fails, you may need to adjust net.core.rmem_max, + * ...wmem_max, or equivalent */ + cl_log(LOG_WARNING, "adjust %sbuf size to %u failed: %s", + direction, byte, strerror(errno)); + } +} + +static int +socket_set_send_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->send_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_SNDBUF, q_len); + ch->send_queue->max_qlen = q_len; + return IPC_OK; +} + +static int +socket_set_recv_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->recv_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_RCVBUF, q_len); + ch->recv_queue->max_qlen = q_len; + return IPC_OK; +} + +static int ipcmsg_count_allocated = 0; +static int ipcmsg_count_freed = 0; +void socket_ipcmsg_dump_stats(void); +void +socket_ipcmsg_dump_stats(void) { + cl_log(LOG_INFO, "ipcsocket ipcmsg allocated=%d, freed=%d, diff=%d", + ipcmsg_count_allocated, + ipcmsg_count_freed, + ipcmsg_count_allocated - ipcmsg_count_freed); +} + +static void +socket_del_ipcmsg(IPC_Message* m) +{ + if (m == NULL) { + cl_log(LOG_ERR, "socket_del_ipcmsg:" + "msg is NULL"); + return; + } + + if (m->msg_body) { + memset(m->msg_body, 0, m->msg_len); + } + if (m->msg_buf) { + g_free(m->msg_buf); + } + + memset(m, 0, sizeof(*m)); + g_free(m); + + ipcmsg_count_freed ++; +} + +static IPC_Message* +socket_new_ipcmsg(IPC_Channel* ch, const void* data, int len, void* private) +{ + IPC_Message* hdr; + + if (ch == NULL || len < 0) { + cl_log(LOG_ERR, "socket_new_ipcmsg:" + " invalid parameter"); + return NULL; + } + + if (ch->msgpad > MAX_MSGPAD) { + cl_log(LOG_ERR, "socket_new_ipcmsg: too many pads " + "something is wrong"); + return NULL; + } + + hdr = ipcmsg_new(ch, data, len, private, socket_del_ipcmsg); + + if (hdr) ipcmsg_count_allocated ++; + + return hdr; +} + +static +struct IPC_MESSAGE * +ipcmsg_new(struct IPC_CHANNEL * ch, const void* data, int len, void* private, + DelProc delproc) +{ + struct IPC_MESSAGE * hdr; + char* copy = NULL; + char* buf; + char* body; + + if ((hdr = g_new(struct IPC_MESSAGE, 1)) == NULL) { + return NULL; + } + memset(hdr, 0, sizeof(*hdr)); + + if (len > 0) { + if ((copy = (char*)g_malloc(ch->msgpad + len)) == NULL) { + g_free(hdr); + return NULL; + } + if (data) { + memcpy(copy + ch->msgpad, data, len); + } + buf = copy; + body = copy + ch->msgpad;; + } else { + len = 0; + buf = body = NULL; + } + hdr->msg_len = len; + hdr->msg_buf = buf; + hdr->msg_body = body; + hdr->msg_ch = ch; + hdr->msg_done = delproc; + hdr->msg_private = private; + + return hdr; +} + +static int +socket_get_chan_status(IPC_Channel* ch) +{ + socket_resume_io(ch); + return ch->ch_status; +} + +/* socket object of the function table */ +static struct IPC_WAIT_OPS socket_wait_ops = { + socket_destroy_wait_conn, + socket_wait_selectfd, + socket_accept_connection, +}; + +/* + * create a new ipc queue whose length = 0 and inner queue = NULL. + * return the pointer to a new ipc queue or NULL is the queue can't be created. + */ + +static struct IPC_QUEUE* +socket_queue_new(void) +{ + struct IPC_QUEUE *temp_queue; + + /* temp queue with length = 0 and inner queue = NULL. */ + temp_queue = g_new(struct IPC_QUEUE, 1); + temp_queue->current_qlen = 0; + temp_queue->max_qlen = DEFAULT_MAX_QLEN; + temp_queue->queue = NULL; + temp_queue->last_maxqlen_warn = 0; + temp_queue->maxqlen_cnt = 0; + return temp_queue; +} + +/* + * socket_wait_conn_new: + * Called by ipc_wait_conn_constructor to get a new socket + * waiting connection. + * (better explanation of this role might be nice) + * + * Parameters : + * ch_attrs (IN) the attributes used to create this connection. + * + * Return : + * the pointer to the new waiting connection or NULL if the connection + * can't be created. + * + * NOTE : + * for domain socket implementation, the only attribute needed is path name. + * so the user should + * create the hash table like this: + * GHashTable * attrs; + * attrs = g_hash_table_new(g_str_hash, g_str_equal); + * g_hash_table_insert(attrs, PATH_ATTR, path_name); + * here PATH_ATTR is defined as "path". + * + * NOTE : + * The streams implementation uses "Streams Programming Guide", Solaris 8, + * as its guide (sample code near end of "Configuration" chapter 11). + */ +struct IPC_WAIT_CONNECTION * +socket_wait_conn_new(GHashTable *ch_attrs) +{ + struct IPC_WAIT_CONNECTION * temp_ch; + char *path_name; + char *mode_attr; + int s, flags; + struct SOCKET_WAIT_CONN_PRIVATE *wait_private; + mode_t s_mode; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un my_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + int pipefds[2]; +#endif + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + mode_attr = (char *) g_hash_table_lookup(ch_attrs, IPC_MODE_ATTR); + + if (mode_attr != NULL) { + s_mode = (mode_t)strtoul((const char *)mode_attr, NULL, 8); + } else { + s_mode = 0777; + } + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the unix domain socket */ + if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_wait_conn_new: socket() failure"); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(path_name) >= sizeof(my_addr.sun_path)) { + close(s); + return NULL; + } + + strncpy(my_addr.sun_path, path_name, sizeof(my_addr.sun_path)); + + if (bind(s, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1) { + cl_perror("socket_wait_conn_new: trying to create in %s bind:" + , path_name); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* Set up the communication channel the clients will use to us (server) */ + if (pipe(pipefds) == -1) { + cl_perror("pipe() failure"); + return NULL; + } + + /* Let clients have unique connections to us */ + if (ioctl(pipefds[1], I_PUSH, "connld") == -1) { + cl_perror("ioctl(%d, I_PUSH, \"connld\") failure", pipefds[1]); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + + if (mkfifo(path_name, s_mode) == -1) { + cl_perror("socket_wait_conn_new: mkfifo(%s, ...) failure", path_name); + return NULL; + } + + if (fattach(pipefds[1], path_name) == -1) { + cl_perror("socket_wait_conn_new: fattach(..., %s) failure", path_name); + return NULL; + } + + /* the pseudo-socket is the other part of the pipe */ + s = pipefds[0]; +#endif + + /* Change the permission of the socket */ + if (chmod(path_name,s_mode) < 0) { + cl_perror("socket_wait_conn_new: failure trying to chmod %s" + , path_name); + close(s); + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* listen to the socket */ + if (listen(s, MAX_LISTEN_NUM) == -1) { + cl_perror("socket_wait_conn_new: listen(MAX_LISTEN_NUM)"); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + flags = fcntl(s, F_GETFL); + if (flags == -1) { + cl_perror("socket_wait_conn_new: cannot read file descriptor flags"); + close(s); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(s, F_SETFL, flags) < 0) { + cl_perror("socket_wait_conn_new: cannot set O_NONBLOCK"); + close(s); + return NULL; + } + + wait_private = g_new(struct SOCKET_WAIT_CONN_PRIVATE, 1); +#if HB_IPC_METHOD == HB_IPC_SOCKET + wait_private->s = s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + wait_private->pipefds[0] = pipefds[0]; + wait_private->pipefds[1] = pipefds[1]; +#endif + strncpy(wait_private->path_name, path_name, sizeof(wait_private->path_name)); + temp_ch = g_new(struct IPC_WAIT_CONNECTION, 1); + temp_ch->ch_private = (void *) wait_private; + temp_ch->ch_status = IPC_WAIT; + temp_ch->ops = (struct IPC_WAIT_OPS *)&socket_wait_ops; + + return temp_ch; +} + +/* + * will be called by ipc_channel_constructor to create a new socket channel. + * parameters : + * attrs (IN) the hash table of the attributes used to create this channel. + * + * return: + * the pointer to the new waiting channel or NULL if the channel can't be created. +*/ + +struct IPC_CHANNEL * +socket_client_channel_new(GHashTable *ch_attrs) { + char *path_name; + int sockfd; + + /* + * I don't really understand why the client and the server use different + * parameter names... + * + * It's a really bad idea to store both integers and strings + * in the same table. + * + * Maybe we need an internal function with a different set of parameters? + */ + + /* + * if we want to seperate them. I suggest + * <client side> + * user call ipc_channel_constructor(ch_type,attrs) to create a new channel. + * ipc_channel_constructor() call socket_channel_new(GHashTable*)to + * create a new socket channel. + * <server side> + * wait_conn->accept_connection() will call another function to create a + * new channel. This function will take socketfd as the parameter to + * create a socket channel. + */ + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the socket */ + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_client_channel_new: socket"); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + sockfd = open(path_name, O_RDWR|O_NONBLOCK); + if (sockfd == -1) { + cl_perror("socket_client_channel_new: open(%s, ...) failure", path_name); + return NULL; + } +#endif + + if (client_channel_new_auth(sockfd) < 0) { + close(sockfd); + return NULL; + } + return channel_new(sockfd, IPC_CLIENT, path_name); +} + +static +int client_channel_new_auth(int sockfd) { +#ifdef USE_BINDSTAT_CREDS + char rand_id[16]; + char uuid_str_tmp[40]; + struct sockaddr_un sock_addr; + + /* Prepare the socket */ + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sun_family = AF_UNIX; + + /* make sure socket paths never clash */ + uuid_generate(rand_id); + uuid_unparse(rand_id, uuid_str_tmp); + + snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path), + "%s/%s", HA_VARLIBHBDIR, uuid_str_tmp); + + unlink(sock_addr.sun_path); + if(bind(sockfd, (struct sockaddr*)&sock_addr, SUN_LEN(&sock_addr)) < 0) { + perror("Client bind() failure"); + return 0; + } +#endif + + return 0; +} + +static +struct IPC_CHANNEL * +socket_server_channel_new(int sockfd) { + return channel_new(sockfd, IPC_SERVER, "?"); +} + +static +struct IPC_CHANNEL * +channel_new(int sockfd, int conntype, const char *path_name) { + struct IPC_CHANNEL * temp_ch; + struct SOCKET_CH_PRIVATE* conn_info; + int flags; + + if (path_name == NULL || strlen(path_name) >= sizeof(conn_info->path_name)) { + return NULL; + } + + temp_ch = g_new(struct IPC_CHANNEL, 1); + if (temp_ch == NULL) { + cl_log(LOG_ERR, "channel_new: allocating memory for channel failed"); + return NULL; + } + memset(temp_ch, 0, sizeof(struct IPC_CHANNEL)); + + conn_info = g_new(struct SOCKET_CH_PRIVATE, 1); + + flags = fcntl(sockfd, F_GETFL); + if (flags == -1) { + cl_perror("channel_new: cannot read file descriptor flags"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) { + cl_perror("channel_new: cannot set O_NONBLOCK"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + + conn_info->s = sockfd; + conn_info->remaining_data = 0; + conn_info->buf_msg = NULL; +#if HB_IPC_METHOD == HB_IPC_SOCKET + conn_info->peer_addr = NULL; +#endif + strncpy(conn_info->path_name, path_name, sizeof(conn_info->path_name)); + +#ifdef DEBUG + cl_log(LOG_INFO, "Initializing socket %d to DISCONNECT", sockfd); +#endif + temp_ch->ch_status = IPC_DISCONNECT; + temp_ch->ch_private = (void*) conn_info; + temp_ch->ops = (struct IPC_OPS *)&socket_ops; + temp_ch->msgpad = sizeof(struct SOCKET_MSG_HEAD); + temp_ch->bytes_remaining = 0; + temp_ch->should_send_block = FALSE; + temp_ch->should_block_fail = TRUE; + temp_ch->send_queue = socket_queue_new(); + temp_ch->recv_queue = socket_queue_new(); + temp_ch->pool = NULL; + temp_ch->high_flow_mark = temp_ch->send_queue->max_qlen; + temp_ch->low_flow_mark = -1; + temp_ch->conntype = conntype; + temp_ch->refcount = 0; + temp_ch->farside_uid = -1; + temp_ch->farside_gid = -1; + + return temp_ch; +} + +/* + * Create a new pair of pre-connected IPC channels similar to + * the result of pipe(2), or socketpair(2). + */ + +int +ipc_channel_pair(IPC_Channel* channels[2]) +{ + int sockets[2]; + int rc; + int j; + const char *pname; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + pname = "[socketpair]"; + + if ((rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets)) < 0) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + pname = "[pipe]"; + + if ((rc = pipe(sockets)) < 0) { + return IPC_FAIL; + } + rc = 0; + for (j=0; j < 2; ++j) { + if (fcntl(sockets[j], F_SETFL, O_NONBLOCK) < 0) { + cl_perror("ipc_channel_pair: cannot set O_NONBLOCK"); + rc = -1; + } + } + if (rc < 0) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } +#endif + + if ((channels[0] = socket_server_channel_new(sockets[0])) == NULL) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } + if ((channels[1] = socket_server_channel_new(sockets[1])) == NULL) { + close(sockets[0]); + close(sockets[1]); + channels[0]->ops->destroy(channels[0]); + return IPC_FAIL; + } + for (j=0; j < 2; ++j) { + struct SOCKET_CH_PRIVATE* p = channels[j]->ch_private; + channels[j]->ch_status = IPC_CONNECT; + channels[j]->conntype = IPC_PEER; + /* Valid, but not terribly meaningful */ + channels[j]->farside_pid = getpid(); + strncpy(p->path_name, pname, sizeof(p->path_name)); + } + + return IPC_OK; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +socket_free_message(struct IPC_MESSAGE * msg) { +#if 0 + memset(msg->msg_body, 0xff, msg->msg_len); +#endif + if (msg->msg_buf) { + g_free(msg->msg_buf); + } else { + g_free(msg->msg_body); + } +#if 0 + memset(msg, 0xff, sizeof(*msg)); +#endif + g_free((void *)msg); +} + +/* + * create a new ipc message whose msg_body's length is msg_len. + * + * parameters : + * msg_len (IN) the length of this message body in this message. + * + * return : + * the pointer to the new message or NULL if the message can't be created. + */ + +static struct IPC_MESSAGE* +socket_message_new(struct IPC_CHANNEL *ch, int msg_len) +{ + return ipcmsg_new(ch, NULL, msg_len, NULL, socket_free_message); +} + +/*********************************************************************** + * + * IPC authentication schemes... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ + +static int +verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid) +{ + int ret = IPC_FAIL; + + if (!auth_info || (!auth_info->uid && !auth_info->gid)) { + return IPC_OK; + } + if ( auth_info->uid + && (g_hash_table_lookup(auth_info->uid + , GUINT_TO_POINTER((guint)uid)) != NULL)) { + ret = IPC_OK; + } else if (auth_info->gid + && (g_hash_table_lookup(auth_info->gid + , GUINT_TO_POINTER((guint)gid)) != NULL)) { + ret = IPC_OK; + } + return ret; +} + +/*********************************************************************** + * SO_PEERCRED VERSION... (Linux) + ***********************************************************************/ + +#ifdef USE_SO_PEERCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE * conn_info; + int ret = IPC_FAIL; + struct ucred cred; + socklen_t n = sizeof(cred); + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + + /* Get the credential information for our peer */ + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + if (getsockopt(conn_info->s, SOL_SOCKET, SO_PEERCRED, &cred, &n) != 0 + || (size_t)n != sizeof(cred)) { + return ret; + } + + ch->farside_uid = cred.uid; + ch->farside_gid = cred.gid; + if (ret == IPC_OK) { + return ret; + } +#if 0 + cl_log(LOG_DEBUG, "SO_PEERCRED returned [%d, (%ld:%ld)]" + , cred.pid, (long)cred.uid, (long)cred.uid); + cl_log(LOG_DEBUG, "Verifying authentication: cred.uid=%d cred.gid=%d" + , cred.uid, cred.gid); + cl_log(LOG_DEBUG, "Verifying authentication: uidptr=0x%lx gidptr=0x%lx" + , (unsigned long)auth_info->uid + , (unsigned long)auth_info->gid); +#endif + /* verify the credential information. */ + return verify_creds(auth_info, cred.uid, cred.gid); +} + +/* get farside pid for our peer process */ + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + socklen_t n; + struct ucred *cred; + pid_t f_pid; + + /* Get the credential information from peer */ + n = sizeof(struct ucred); + cred = g_new(struct ucred, 1); + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, cred, &n) != 0) { + g_free(cred); + return -1; + } + + f_pid = cred->pid; + g_free(cred); + return f_pid; +} +#endif /* SO_PEERCRED version */ + +#ifdef USE_GETPEEREID +/* + * This is implemented in OpenBSD and FreeBSD. + * + * It's not a half-bad interface... + * + * This should probably be our standard way of doing it, and put it + * as a replacement library. That would simplify things... + */ + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + uid_t euid; + gid_t egid; + int ret = IPC_FAIL; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (getpeereid(conn_info->s, &euid, &egid) < 0) { + cl_perror("getpeereid() failure"); + return ret; + } + + ch->farside_uid = euid; + ch->farside_gid = egid; + + /* verify the credential information. */ + return verify_creds(auth_info, euid, egid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* USE_GETPEEREID */ + +/*********************************************************************** + * SCM_CREDS VERSION... (*BSD systems) + ***********************************************************************/ +#ifdef USE_SCM_CREDS +/* FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * This isn't an emergency, but should be done in the future... + * Hint: * Postgresql does both types of authentication... + * see src/backend/libpq/auth.c + * Not clear its SO_PEERCRED implementation works though ;-) + */ + +/* Done.... Haven't tested yet. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct msghdr msg; + /* Credentials structure */ + +#define EXTRASPACE 0 + +#ifdef HAVE_STRUCT_CMSGCRED + /* FreeBSD */ + typedef struct cmsgcred Cred; +# define crRuid cmcred_uid +# define crEuid cmcred_euid +# define crRgid cmcred_gid +# define crEgid cmcred_groups[0] /* Best guess */ +# define crpid cmcred_pid +# define crngrp cmcred_ngroups +# define crgrps cmcred_groups + +#elif HAVE_STRUCT_FCRED + /* Stevens' book */ + typedef struct fcred Cred; +# define crRuid fc_uid +# define crRgid fc_rgid +# define crEgid fc_gid +# define crngrp fc_ngroups +# define crgrps fc_groups + +#elif HAVE_STRUCT_SOCKCRED + /* NetBSD */ + typedef struct sockcred Cred; +# define crRuid sc_uid +# define crEuid sc_euid +# define crRgid sc_gid +# define crEgid sc_egid +# define crngrp sc_ngroups +# define crgrps sc_groups +# undef EXTRASPACE +# define EXTRASPACE SOCKCREDSIZE(ngroups) + +#elif HAVE_STRUCT_CRED + typedef struct cred Cred; +#define cruid c_uid + +#elif HAVE_STRUCT_UCRED + typedef struct ucred Cred; + + /* reuse this define for the moment */ +# if HAVE_STRUCT_UCRED_DARWIN +# define crEuid cr_uid +# define crEgid cr_groups[0] /* Best guess */ +# define crgrps cr_groups +# define crngrp cr_ngroups +# else +# define crEuid c_uid +# define crEgid c_gid +# endif +#else +# error "No credential type found!" +#endif + + struct SOCKET_CH_PRIVATE *conn_info; + int ret = IPC_FAIL; + char buf; + + /* Compute size without padding */ + #define CMSGSIZE (sizeof(struct cmsghdr)+(sizeof(Cred))+EXTRASPACE) + + union { + char mem[CMSGSIZE]; + struct cmsghdr hdr; + Cred credu; + }cmsgmem; + Cred cred; + + /* Point to start of first structure */ + struct cmsghdr *cmsg = &cmsgmem.hdr; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = g_new(struct iovec, 1); + msg.msg_iovlen = 1; + msg.msg_control = (char *) cmsg; + msg.msg_controllen = CMSGSIZE; + memset(cmsg, 0, sizeof(cmsgmem)); + + /* + * The one character which is received here is not meaningful; its + * purpose is only to make sure that recvmsg() blocks long enough for + * the other side to send its credentials. + */ + msg.msg_iov->iov_base = &buf; + msg.msg_iov->iov_len = 1; + + if (recvmsg(conn_info->s, &msg, 0) < 0 + || cmsg->cmsg_len < CMSGSIZE + || cmsg->cmsg_type != SCM_CREDS) { + cl_perror("can't get credential information from peer"); + return ret; + } + + /* Avoid alignment issues - just copy it! */ + memcpy(&cred, CMSG_DATA(cmsg), sizeof(cred)); + + ch->farside_uid = cred.crEuid; + ch->farside_gid = cred.crEgid; + if (ret == IPC_OK) { + return ret; + } + + /* verify the credential information. */ + return verify_creds(auth_info, cred.crEuid, cred.crEgid); +} + +/* + * FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * this is similar to the SCM_CREDS mechanism for verify_auth() function. + * here we just want to get the pid of the other side from the credential + * information. + */ + +static +pid_t +socket_get_farside_pid(int sock) +{ + /* FIXME! */ + return -1; +} +#endif /* SCM_CREDS version */ + +/*********************************************************************** + * Bind/Stat VERSION... (Supported on OSX/Darwin and 4.3+BSD at least...) + * + * This is for use on systems such as OSX-Darwin where + * none of the other options is available. + * + * This implementation has been adapted from "Advanced Programming + * in the Unix Environment", Section 15.5.2, by W. Richard Stevens. + * + */ +#ifdef USE_BINDSTAT_CREDS + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + int len = 0; + int ret = IPC_FAIL; + struct stat stat_buf; + struct sockaddr_un *peer_addr = NULL; + struct SOCKET_CH_PRIVATE *ch_private = NULL; + + if(ch != NULL) { + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + if(ch_private != NULL) { + peer_addr = ch_private->peer_addr; + } + } + + if(ch == NULL) { + cl_log(LOG_ERR, "No channel to authenticate"); + return IPC_FAIL; + + } else if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + + } + + if(ch_private == NULL) { + cl_log(LOG_ERR, "No channel private data available"); + return ret; + + } else if(peer_addr == NULL) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + + len = SUN_LEN(peer_addr); + + if(len < 1) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + peer_addr->sun_path[len] = 0; + stat(peer_addr->sun_path, &stat_buf); + + ch->farside_uid = stat_buf.st_uid; + ch->farside_gid = stat_buf.st_gid; + if (ret == IPC_OK) { + return ret; + } + + if ((auth_info->uid == NULL || g_hash_table_size(auth_info->uid) == 0) + && auth_info->gid != NULL + && g_hash_table_size(auth_info->gid) != 0) { + cl_log(LOG_WARNING, + "GID-Only IPC security is not supported" + " on this platform."); + return IPC_BROKEN; + } + + /* verify the credential information. */ + return verify_creds(auth_info, stat_buf.st_uid, stat_buf.st_gid); +} + +static pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Bind/stat version */ + +/*********************************************************************** + * USE_STREAM_CREDS VERSION... (e.g. Solaris pre-10) + ***********************************************************************/ +#ifdef USE_STREAM_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + ch->farside_uid = conn_info->farside_uid; + ch->farside_gid = conn_info->farside_gid; + + /* verify the credential information. */ + return verify_creds(auth_info, + conn_info->farside_uid, conn_info->farside_gid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif + +/*********************************************************************** + * GETPEERUCRED VERSION... (e.g. Solaris 10 upwards) + ***********************************************************************/ + +#ifdef USE_GETPEERUCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + ucred_t *ucred = NULL; + int rc = IPC_FAIL; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + rc = IPC_OK; /* no restriction for authentication */ + } + + if (getpeerucred(conn_info->s, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return rc; + } + + ch->farside_uid = ucred_geteuid(ucred); + ch->farside_gid = ucred_getegid(ucred); + if (rc == IPC_OK) { + return rc; + } + + /* verify the credential information. */ + rc = verify_creds(auth_info, + ucred_geteuid(ucred), ucred_getegid(ucred)); + ucred_free(ucred); + return rc; +} + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + ucred_t *ucred = NULL; + pid_t pid; + + if (getpeerucred(sockfd, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return IPC_FAIL; + } + + pid = ucred_getpid(ucred); + + ucred_free(ucred); + + return pid; +} +#endif + +/*********************************************************************** + * DUMMY VERSION... (other systems...) + * + * Other options that seem to be out there include + * SCM_CREDENTIALS and LOCAL_CREDS + * There are some kludgy things you can do with SCM_RIGHTS + * to pass an fd which could only be opened by the user id to + * validate the user id, but I don't know of a similar kludge which + * would work for group ids. And, even the uid one will fail + * if normal users are allowed to give away (chown) files. + * + * Unfortunately, this set of authentication routines have become + * very important to this API and its users (like heartbeat). + * + ***********************************************************************/ + +#ifdef USE_DUMMY_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + return IPC_FAIL; +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Dummy version */ + +/* socket object of the function table */ +static struct IPC_OPS socket_ops = { + socket_destroy_channel, + socket_initiate_connection, + socket_verify_auth, + socket_assert_auth, + socket_send, + socket_recv, + socket_waitin, + socket_waitout, + socket_is_message_pending, + socket_is_output_pending, + socket_resume_io, + socket_get_send_fd, + socket_get_recv_fd, + socket_set_send_qlen, + socket_set_recv_qlen, + socket_set_high_flow_callback, + socket_set_low_flow_callback, + socket_new_ipcmsg, + socket_get_chan_status, + socket_is_sendq_full, + socket_is_recvq_full, + socket_get_conntype, + socket_disconnect, +}; diff --git a/lib/clplumbing/ipctest.c b/lib/clplumbing/ipctest.c new file mode 100644 index 0000000..333d3a0 --- /dev/null +++ b/lib/clplumbing/ipctest.c @@ -0,0 +1,1377 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include <lha_internal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +/* libgen.h: for 'basename()' on Solaris */ +#include <libgen.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/GSource.h> +#include <clplumbing/ipc.h> + +#define MAXERRORS 1000 +#define MAXERRORS_RECV 10 + +typedef int (*TestFunc_t)(IPC_Channel*chan, int count); + +static int channelpair(TestFunc_t client, TestFunc_t server, int count); +#if 0 +static void clientserverpair(IPC_Channel* channels[2]); +#endif + +static int echoserver(IPC_Channel*, int repcount); +static int echoclient(IPC_Channel*, int repcount); +static int asyn_echoserver(IPC_Channel*, int repcount); +static int asyn_echoclient(IPC_Channel*, int repcount); +static int mainloop_server(IPC_Channel* chan, int repcount); +static int mainloop_client(IPC_Channel* chan, int repcount); + +static int checksock(IPC_Channel* channel); +static void checkifblocked(IPC_Channel* channel); + +static int (*PollFunc)(struct pollfd * fds, unsigned int, int) += (int (*)(struct pollfd * fds, unsigned int, int)) poll; +static gboolean checkmsg(IPC_Message* rmsg, const char * who, int rcount); + +static const char *procname; + +static const int iter_def = 10000; /* number of iterations */ +static int verbosity; /* verbosity level */ + +/* + * The ipc interface can be invoked as either: + * 1. pair (pipe/socketpair); + * 2. separate connect/accept (like server with multiple independent clients). + * + * If number of clients is given as 0, the "pair" mechanism is used, + * otherwise the client/server mechanism. + */ +/* *** CLIENTS_MAX currently 1 while coding *** */ +#define CLIENTS_MAX 1 /* max. number of independent clients */ +static int clients_def; /* number of independent clients */ + +static int +channelpair(TestFunc_t clientfunc, TestFunc_t serverfunc, int count) +{ + IPC_Channel* channels[2]; + int rc = 0; + int waitstat = 0; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while (wait(&waitstat) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + /* Child continues here... */ + if (ipc_channel_pair(channels) != IPC_OK) { + cl_perror("Can't create ipc channel pair"); + exit(1); + } + checksock(channels[0]); + checksock(channels[1]); + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + channels[1]->ops->destroy(channels[1]); + channels[1] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client starting...", + procname, (int)getpid(), __LINE__); + } + rc = clientfunc(channels[0], count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + channels[0]->ops->destroy(channels[0]); + channels[0] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + rc = serverfunc(channels[1], count); + wait(&waitstat); + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + return(rc); +} + +/* server with many clients */ +static int +clientserver(TestFunc_t clientfunc, TestFunc_t serverfunc, int count, int clients) +{ + IPC_Channel* channel; + int rc = 0; + int waitstat = 0; + struct IPC_WAIT_CONNECTION *wconn; + char path[] = IPC_PATH_ATTR; + char commpath[] = "/tmp/foobar"; /* *** CHECK/FIX: Is this OK? */ + GHashTable * wattrs; + int i; + pid_t pid; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while ((pid = wait(&waitstat)) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* set up a server */ + wattrs = g_hash_table_new(g_str_hash, g_str_equal); + if (! wattrs) { + cl_perror("g_hash_table_new() failed"); + exit(1); + } + g_hash_table_insert(wattrs, path, commpath); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + wconn = ipc_wait_conn_constructor(IPC_ANYTYPE, wattrs); + if (! wconn) { + cl_perror("could not establish server"); + exit(1); + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* spawn the clients */ + for (i = 1; i <= clients; i++) { + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: fork client %d of %d", + procname, (int)getpid(), __LINE__, i, clients); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d starting...", + procname, (int)getpid(), __LINE__, i); + } + channel = ipc_channel_constructor(IPC_ANYTYPE, wattrs); + if (channel == NULL) { + cl_perror("client: channel creation failed"); + exit(1); + } + + rc = channel->ops->initiate_connection(channel); + if (rc != IPC_OK) { + cl_perror("channel[1] failed to connect"); + exit(1); + } + checksock(channel); + rc = clientfunc(channel, count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d ended rc:%d", + procname, (int)getpid(), __LINE__, rc, i); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + /* accept on server */ + /* *** + * Two problems (or more) here: + * 1. What to do if no incoming call pending? + * At present, fudge by sleeping a little so client gets started. + * 2. How to handle multiple clients? + * Would need to be able to await both new connections and + * data on existing connections. + * At present, fudge CLIENTS_MAX as 1. + * *** + */ + sleep(1); /* *** */ + channel = wconn->ops->accept_connection(wconn, NULL); + if (channel == NULL) { + cl_perror("server: acceptance failed"); + } + + checksock(channel); + + rc = serverfunc(channel, count); + + /* server finished: tidy up */ + wconn->ops->destroy(wconn); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + + /* reap the clients */ + for (i = 1; i <= clients; i++) { + pid_t pid; + + pid = wait(&waitstat); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d reaped:%d", + procname, (int)getpid(), __LINE__, + (int) pid, WIFEXITED(waitstat)); + } + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + + return(rc); +} + +static void +echomsgbody(void * body, int n, int niter, size_t * len) +{ + char *str = body; + int l; + + l = snprintf(str, n-1, "String-%d", niter); + if (l < (n-1)) { + memset(&str[l], 'a', (n - (l+1))); + } + str[n-1] = '\0'; + *len = n; +} + +static void +checkifblocked(IPC_Channel* chan) +{ + if (chan->ops->is_sending_blocked(chan)) { + cl_log(LOG_INFO, "Sending is blocked."); + chan->ops->resume_io(chan); + } +} + +#ifdef CHEAT_CHECKS +extern long SeqNums[32]; +#endif + +static int +transport_tests(int iterations, int clients) +{ + int rc = 0; + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(echoclient, echoserver, iterations) + : clientserver(echoclient, echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(asyn_echoclient, asyn_echoserver, iterations) + : clientserver(asyn_echoclient, asyn_echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(mainloop_client, mainloop_server, iterations) + : clientserver(mainloop_client, mainloop_server, iterations, clients); + + return rc; +} + +static int data_size = 20; + +int +main(int argc, char ** argv) +{ + int argflag, argerrs; + int iterations; + int clients; + int rc = 0; + + /* + * Check and process arguments. + * -v: verbose + * -i: number of iterations + * -c: number of clients (invokes client/server mechanism) + * -s: data-size + */ + procname = basename(argv[0]); + + argerrs = 0; + iterations = iter_def; + clients = clients_def; + while ((argflag = getopt(argc, argv, "i:vuc:s:")) != EOF) { + switch (argflag) { + case 'i': /* iterations */ + iterations = atoi(optarg); + break; + case 'v': /* verbosity */ + verbosity++; + break; + case 'c': /* number of clients */ + clients = atoi(optarg); + if (clients < 1 || clients > CLIENTS_MAX) { + fprintf(stderr, "number of clients out of range" + "(1 to %d)\n", CLIENTS_MAX); + argerrs++; + } + break; + case 's': /* data size */ + data_size = atoi(optarg); + if (data_size < 0) { + fprintf(stderr, "data size must be >=0\n"); + argerrs++; + } + if (data_size > MAXMSG) { + fprintf(stderr, "maximum data size is %d\n", MAXMSG); + argerrs++; + } + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-v] [-i iterations] [-c clients] [-s size]\n" + "\t-v : verbose\n" + "\t-i : iterations (default %d)\n" + "\t-c : number of clients (default %d; nonzero invokes client/server)\n" + "\t-s : data size (default 20 bytes)\n", + procname, iter_def, clients_def); + exit(1); + } + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + + + rc += transport_tests(iterations, clients); + +#if 0 + /* Broken for the moment - need to fix it long term */ + cl_log(LOG_INFO, "NOTE: Enabling poll(2) replacement code."); + PollFunc = cl_poll; + g_main_set_poll_func(cl_glibpoll); + ipc_set_pollfunc(cl_poll); + + rc += transport_tests(5 * iterations, clients); +#endif + + cl_log(LOG_INFO, "TOTAL errors: %d", rc); + + return (rc > 127 ? 127 : rc); +} + +static int +checksock(IPC_Channel* channel) +{ + + if (!channel) { + cl_log(LOG_ERR, "Channel null"); + return 1; + } + if (!IPC_ISRCONN(channel)) { + cl_log(LOG_ERR, "Channel status is %d" + ", not IPC_CONNECT", channel->ch_status); + return 1; + } + return 0; +} + +static void +EOFcheck(IPC_Channel* chan) +{ + int fd = chan->ops->get_recv_select_fd(chan); + struct pollfd pf[1]; + int rc; + + cl_log(LOG_INFO, "channel state: %d", chan->ch_status); + + if (chan->recv_queue->current_qlen > 0) { + cl_log(LOG_INFO, "EOF Receive queue has %ld messages in it" + , (long)chan->recv_queue->current_qlen); + } + if (fd <= 0) { + cl_log(LOG_INFO, "EOF receive fd: %d", fd); + } + + + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + + rc = poll(pf, 1, 0); + + if (rc < 0) { + cl_perror("failed poll(2) call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (real poll)" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck poll(2) bits: 0x%lx" + , (unsigned long)pf[0].revents); + } + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + rc = PollFunc(pf, 1, 0); + if (rc < 0) { + cl_perror("failed PollFunc() call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (PollFunc())" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck PollFunc() bits: 0x%lx" + , (unsigned long)pf[0].revents); + } +} + +static int +echoserver(IPC_Channel* wchan, int repcount) +{ + char *str; + int j; + int errcount = 0; + IPC_Message wmsg; + IPC_Message* rmsg = NULL; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + memset(&wmsg, 0, sizeof(wmsg)); + wmsg.msg_private = NULL; + wmsg.msg_done = NULL; + wmsg.msg_body = str; + wmsg.msg_buf = NULL; + wmsg.msg_ch = wchan; + + cl_log(LOG_INFO, "Echo server: %d reps pid %d.", repcount, getpid()); + for (j=1; j <= repcount + ;++j, rmsg != NULL && (rmsg->msg_done(rmsg),1)) { + int rc; + + echomsgbody(str, data_size, j, &(wmsg.msg_len)); + if ((rc = wchan->ops->send(wchan, &wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest: send failed %d rc iter %d" + , rc, j); + ++errcount; + continue; + } + + /*fprintf(stderr, "+"); */ + wchan->ops->waitout(wchan); + checkifblocked(wchan); + /*fprintf(stderr, "S"); */ + + /* Try and induce a failure... */ + if (j == repcount) { + sleep(1); + } + + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + + /*fprintf(stderr, "-"); */ + if ((rc = wchan->ops->recv(wchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "s"); */ + if (rmsg->msg_len != wmsg.msg_len) { + cl_log(LOG_ERR + , "echotest: length mismatch [%lu,%lu] iter %d" + , (unsigned long)rmsg->msg_len + , (unsigned long)wmsg.msg_len, j); + ++errcount; + continue; + } + if (strncmp(rmsg->msg_body, wmsg.msg_body, wmsg.msg_len) + != 0) { + cl_log(LOG_ERR + , "echotest: data mismatch. iteration %d" + , j); + ++errcount; + continue; + } + + } + cl_log(LOG_INFO, "echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + + free(str); + + return errcount; +} +static int +echoclient(IPC_Channel* rchan, int repcount) +{ + int j; + int errcount = 0; + IPC_Message* rmsg; + + + + cl_log(LOG_INFO, "Echo client: %d reps pid %d." + , repcount, (int)getpid()); + for (j=1; j <= repcount ;++j) { + + int rc; + + while ((rc = rchan->ops->waitin(rchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest client: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + /*fprintf(stderr, "/"); */ + + if ((rc = rchan->ops->recv(rchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + if (errcount > MAXERRORS_RECV) { + cl_log(LOG_ERR, + "echoclient: errcount excessive: %d: abandoning", + errcount); + exit(1); + } + --j; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "c"); */ + if ((rc = rchan->ops->send(rchan, rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: send failed %d rc iter %d" + , rc, j); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + ++errcount; + continue; + } + /*fprintf(stderr, "%%"); */ + rchan->ops->waitout(rchan); + checkifblocked(rchan); + /*fprintf(stderr, "C"); */ + } + cl_log(LOG_INFO, "echoclient: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)rchan); +#endif + rchan->ops->destroy(rchan); rchan = NULL; + return errcount; +} + +void dump_ipc_info(IPC_Channel* chan); + +static int +checkinput(IPC_Channel* chan, const char * where, int* rdcount, int maxcount) +{ + IPC_Message* rmsg = NULL; + int errs = 0; + int rc; + + while (chan->ops->is_message_pending(chan) + && errs < 10 && *rdcount < maxcount) { + + if (chan->ch_status == IPC_DISCONNECT && *rdcount < maxcount){ + cl_log(LOG_ERR + , "checkinput1[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + if (rmsg != NULL) { + rmsg->msg_done(rmsg); + rmsg = NULL; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (chan->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR + , "checkinput2[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + return errs; + } + cl_log(LOG_ERR + , "checkinput[%s]: recv" + " failed: rc %d rdcount %d errno=%d" + , where, rc, *rdcount, errno); + cl_perror("recv"); + rmsg=NULL; + ++errs; + continue; + } + *rdcount += 1; + if (!checkmsg(rmsg, where, *rdcount)) { + dump_ipc_info(chan); + ++errs; + } + if (*rdcount < maxcount && chan->ch_status == IPC_DISCONNECT){ + cl_log(LOG_ERR + , "checkinput3[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + } + return errs; +} + +static void +async_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 1; + +} + +static void +async_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 0; + +} + + +static int +asyn_echoserver(IPC_Channel* wchan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + int blockedcount = 0; + IPC_Message* wmsg; + const char* w = "asyn_echoserver"; + int stopsending = 0; + + cl_log(LOG_INFO, "Asyn echo server: %d reps pid %d." + , repcount, (int)getpid()); + + (void)async_high_flow_callback; + (void)async_low_flow_callback; + + + wchan->ops->set_high_flow_callback(wchan, async_high_flow_callback, &stopsending); + wchan->ops->set_low_flow_callback(wchan, async_low_flow_callback, &stopsending); + + wchan->low_flow_mark = 2; + wchan->high_flow_mark = 20; + + while (rdcount < repcount) { + int rc; + + while (wrcount < repcount && blockedcount < 10 + && wchan->ch_status != IPC_DISCONNECT + ){ + + if (!stopsending){ + ++wrcount; + if (wrcount > repcount) { + break; + } + wmsg = wchan->ops->new_ipcmsg(wchan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, wrcount, &wmsg->msg_len); + if ((rc = wchan->ops->send(wchan, wmsg)) != IPC_OK){ + + cl_log(LOG_INFO, "channel sstatus in echo server is %d", + wchan->ch_status); + if (wchan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR + , "asyn_echoserver: send failed" + " %d rc iter %d" + , rc, wrcount); + ++errcount; + continue; + }else {/*send failed because of channel busy + * roll back + */ + --wrcount; + } + } + + if (wchan->ops->is_sending_blocked(wchan)) { + /* fprintf(stderr, "b"); */ + ++blockedcount; + }else{ + blockedcount = 0; + } + } + + + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount < repcount + && wchan->ch_status == IPC_DISCONNECT) { + ++errcount; + break; + } + } + +/* cl_log(LOG_INFO, "async_echoserver: wrcount =%d rdcount=%d B", wrcount, rdcount); */ + + wchan->ops->waitout(wchan); + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount >= repcount && rdcount < repcount) { + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "asyn_echoserver: waitin()" + " failed %d rc rdcount %d errno=%d" + , rc, rdcount, errno); + cl_perror("waitin"); + exit(1); + } + } + if (wchan->ch_status == IPC_DISCONNECT + && rdcount < repcount) { + cl_log(LOG_ERR, + "asyn_echoserver: EOF in iter %d (wrcount=%d)", + rdcount, wrcount); + EOFcheck(wchan); + ++errcount; + break; + } + + blockedcount = 0; + + } + + cl_log(LOG_INFO, "asyn_echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx", getpid(), (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + return errcount; +} + +static int +asyn_echoclient(IPC_Channel* chan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + IPC_Message* rmsg; + int rfd = chan->ops->get_recv_select_fd(chan); + int wfd = chan->ops->get_send_select_fd(chan); + gboolean rdeqwr = (rfd == wfd); + + + cl_log(LOG_INFO, "Async Echo client: %d reps pid %d." + , repcount, (int)getpid()); + ipc_set_pollfunc(PollFunc); + + while (rdcount < repcount && errcount < repcount) { + + int rc; + struct pollfd pf[2]; + int nfd = 1; + + pf[0].fd = rfd; + pf[0].events = POLLIN|POLLHUP; + + + if (chan->ops->is_sending_blocked(chan)) { + if (rdeqwr) { + pf[0].events |= POLLOUT; + }else{ + nfd = 2; + pf[1].fd = wfd; + pf[1].events = POLLOUT|POLLHUP; + } + } + + /* Have input? */ + /* fprintf(stderr, "i"); */ + while (chan->ops->is_message_pending(chan) + && rdcount < repcount) { + /*fprintf(stderr, "r"); */ + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: disconnect" + " iter %d", rdcount+1); + ++errcount; + return errcount; + } + cl_log(LOG_ERR + , "Async echoclient: recv" + " failed %d rc iter %d errno=%d" + , rc, rdcount+1, errno); + cl_perror("recv"); + rmsg=NULL; + ++errcount; + cl_log(LOG_INFO, "sleep(1)"); + sleep(1); + continue; + } + /*fprintf(stderr, "c"); */ + ++rdcount; + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + ++errcount; + cl_perror("send"); + cl_log(LOG_ERR + , "Async echoclient: send failed" + " rc %d, iter %d", rc, rdcount); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: EOF(2)" + " iter %d", rdcount+1); + EOFcheck(chan); + return errcount; + } + continue; + + } + + + ++wrcount; + /*fprintf(stderr, "x"); */ + } + if (rdcount >= repcount) { + break; + } + /* + * At this point it is possible that the POLLOUT bit + * being on is no longer necessary, but this will only + * cause an extra (false) output poll iteration at worst... + * This is because (IIRC) both is_sending_blocked(), and + * is_message_pending() both perform a resume_io(). + * This might be confusing, but -- oh well... + */ + + /* + fprintf(stderr, "P"); + cl_log(LOG_INFO, "poll[%d, 0x%x]" + , pf[0].fd, pf[0].events); + cl_log(LOG_DEBUG, "poll[%d, 0x%x]..." + , pf[0].fd, pf[0].events); + fprintf(stderr, "%%"); + cl_log(LOG_DEBUG, "CallingPollFunc()"); + */ + rc = PollFunc(pf, nfd, -1); + + /* Bad poll? */ + if (rc <= 0) { + cl_perror("Async echoclient: bad poll rc." + " %d rc iter %d", rc, rdcount); + ++errcount; + continue; + } + + /* Error indication? */ + if ((pf[0].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + continue; + } + + /* HUP without input... Premature EOF... */ + if ((pf[0].revents & POLLHUP) + && ((pf[0].revents&POLLIN) == 0)) { + cl_log(LOG_ERR + , "Async echoclient: premature pollhup." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + EOFcheck(chan); + ++errcount; + continue; + } + + /* Error indication? */ + if (nfd > 1 + && (pf[1].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents[1]." + " revents: 0x%x iter %d", pf[1].revents, rdcount); + ++errcount; + continue; + } + + /* Output unblocked (only) ? */ + if (pf[nfd-1].revents & POLLOUT) { + /*fprintf(stderr, "R");*/ + chan->ops->resume_io(chan); + }else if ((pf[0].revents & POLLIN) == 0) { + /* Neither I nor O available... */ + cl_log(LOG_ERR + , "Async echoclient: bad events." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + } + } + cl_poll_ignore(rfd); + cl_poll_ignore(wfd); + cl_log(LOG_INFO, "Async echoclient: %d errors, %d reads, %d writes", + errcount, rdcount, wrcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx",getpid(), (unsigned long)chan); +#endif + + + chan->ops->waitout(chan); + + chan->ops->destroy(chan); chan = NULL; + return errcount; +} + + +struct iterinfo { + int wcount; + int rcount; + int errcount; + IPC_Channel* chan; + int max; + gboolean sendingsuspended; +}; + +static GMainLoop* loop = NULL; + + + + +static gboolean +s_send_msg(gpointer data) +{ + struct iterinfo*i = data; + IPC_Message* wmsg; + int rc; + + ++i->wcount; + + wmsg = i->chan->ops->new_ipcmsg(i->chan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, i->wcount, &wmsg->msg_len); + + /*cl_log(LOG_INFO, "s_send_msg: sending out %d", i->wcount);*/ + + if ((rc = i->chan->ops->send(i->chan, wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_send_msg: send failed" + " %d rc iter %d" + , rc, i->wcount); + cl_log(LOG_ERR + , "s_send_msg: channel status: %d qlen: %ld" + , i->chan->ch_status + , (long)i->chan->send_queue->current_qlen); + ++i->errcount; + if (i->chan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR, "s_send_msg: Exiting."); + return FALSE; + } + if (i->errcount >= MAXERRORS) { + g_main_quit(loop); + return FALSE; + } + } + return !i->sendingsuspended?i->wcount < i->max: FALSE; +} + + + + +static void +mainloop_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + if (i->sendingsuspended){ + i->sendingsuspended = FALSE; + g_idle_add(s_send_msg, i); + } + + return; + +} + +static void +mainloop_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + i->sendingsuspended = TRUE; + +} + + +static gboolean +s_rcv_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo*i = data; + + i->errcount += checkinput(chan, "s_rcv_msg", &i->rcount, i->max); + + if (chan->ch_status == IPC_DISCONNECT + || i->rcount >= i->max || i->errcount > MAXERRORS) { + if (i->rcount < i->max) { + ++i->errcount; + cl_log(LOG_INFO, "Early exit from s_rcv_msg"); + } + g_main_quit(loop); + return FALSE; + } + + return TRUE; +} + +static gboolean +checkmsg(IPC_Message* rmsg, const char * who, int rcount) +{ + char *str; + size_t len; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + echomsgbody(str, data_size, rcount, &len); + + if (rmsg->msg_len != len) { + cl_log(LOG_ERR + , "checkmsg[%s]: length mismatch" + " [expected %u, got %lu] iteration %d" + , who, (unsigned)len + , (unsigned long)rmsg->msg_len + , rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; + } + if (strncmp(rmsg->msg_body, str, len) != 0) { + cl_log(LOG_ERR + , "checkmsg[%s]: data mismatch" + ". input iteration %d" + , who, rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; +#if 0 + }else if (strcmp(who, "s_rcv_msg") == 0) { +#if 0 + + || strcmp(who, "s_echo_msg") == 0) { +#endif + cl_log(LOG_ERR + , "checkmsg[%s]: data Good" + "! input iteration %d" + , who, rcount); +#endif + } + + free(str); + + return TRUE; +} + +static gboolean +s_echo_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo* i = data; + int rc; + IPC_Message* rmsg; + + while (chan->ops->is_message_pending(chan)) { + if (chan->ch_status == IPC_DISCONNECT) { + break; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_echo_msg: recv failed %d rc iter %d" + " errno=%d" + , rc, i->rcount+1, errno); + cl_perror("recv"); + ++i->errcount; + goto retout; + } + i->rcount++; + if (!checkmsg(rmsg, "s_echo_msg", i->rcount)) { + ++i->errcount; + } + + + + /*cl_log(LOG_INFO, "s_echo_msg: rcount= %d, wcount =%d", i->rcount, i->wcount);*/ + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, + "s_echo_msg: send failed %d rc iter %d qlen %ld", + rc, i->rcount, (long)chan->send_queue->current_qlen); + cl_perror("send"); + i->errcount ++; + + } + + i->wcount+=1; + /*cl_log(LOG_INFO, "s_echo_msg: end of this ite");*/ + } + retout: + /*fprintf(stderr, "%%");*/ + if (i->rcount >= i->max || chan->ch_status == IPC_DISCONNECT + || i->errcount > MAXERRORS) { + chan->ops->waitout(chan); + g_main_quit(loop); + return FALSE; + } + return TRUE; +} + +static void +init_iterinfo(struct iterinfo * i, IPC_Channel* chan, int max) +{ + memset(i, 0, sizeof(*i)); + i->chan = chan; + i->max = max; + i->sendingsuspended = FALSE; +} + +static int +mainloop_server(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + guint sendmsgsrc; + + + + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + + chan->ops->set_high_flow_callback(chan, mainloop_high_flow_callback, &info); + chan->ops->set_low_flow_callback(chan, mainloop_low_flow_callback, &info); + chan->high_flow_mark = 20; + chan->low_flow_mark = 2; + + sendmsgsrc = g_idle_add(s_send_msg, &info); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_rcv_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo server: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + g_source_remove(sendmsgsrc); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo server: %d errors", info.errcount); + return info.errcount; +} +static int +mainloop_client(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_echo_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo client: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo client: %d errors, %d read %d written" + , info.errcount, info.rcount, info.wcount); + return info.errcount; +} diff --git a/lib/clplumbing/ipctransient.h b/lib/clplumbing/ipctransient.h new file mode 100644 index 0000000..9c1746c --- /dev/null +++ b/lib/clplumbing/ipctransient.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 Andrew Beekhof <andrew@beekhof.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include <lha_internal.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/GSource.h> +#include <clplumbing/ipc.h> +#include <clplumbing/realtime.h> +#include <clplumbing/lsb_exitcodes.h> +#include <errno.h> + +#define MAXERRORS 1000 +#define MAX_IPC_FAIL 10 +#define FIFO_LEN 1024 + +extern const char *procname; + +extern const char *commdir; + +void trans_getargs(int argc, char **argv); + +void default_ipctest_input_destroy(gpointer user_data); + +IPC_Message * create_simple_message(const char *text, IPC_Channel *ch); diff --git a/lib/clplumbing/ipctransientclient.c b/lib/clplumbing/ipctransientclient.c new file mode 100644 index 0000000..080acf2 --- /dev/null +++ b/lib/clplumbing/ipctransientclient.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 <ipctransient.h> + +#define MAX_MESSAGES 3 +static char *messages[MAX_MESSAGES]; + +IPC_Message *create_simple_message(const char *text, IPC_Channel *ch); +IPC_Channel *init_client_ipctest_comms( + const char *child, gboolean (*dispatch)( + IPC_Channel* source_data, gpointer user_data), + void *user_data); +gboolean transient_client_callback(IPC_Channel* server, void* private_data); +void client_send_message( + const char *message_text, IPC_Channel *server_channel, int iteration); + +#define MAXTSTMSG 1000 + +int +main(int argc, char ** argv) +{ + int lpc =0, iteration=0; + GMainLoop* client_main = NULL; + IPC_Channel *server_channel = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + /* give the server a chance to start */ + cl_log(LOG_INFO, "#--#--#--#--# Beginning test run %d against server %d...", lpc, iteration); + client_main = g_main_new(FALSE); + + /* connect, send messages */ + server_channel = init_client_ipctest_comms("echo", transient_client_callback, client_main); + + if(server_channel == NULL) { + cl_log(LOG_ERR, "[Client %d] Could not connect to server", lpc); + return 1; + } + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + messages[lpc] = (char *)malloc(sizeof(char)*MAXTSTMSG); + } + snprintf(messages[0], MAXTSTMSG + , "%s_%ld%c", "hello", (long)getpid(), '\0'); + snprintf(messages[1], MAXTSTMSG + , "%s_%ld%c", "hello_world", (long)getpid(), '\0'); + snprintf(messages[2], MAXTSTMSG + , "%s_%ld%c", "hello_world_again", (long)getpid(), '\0'); + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + client_send_message(messages[lpc], server_channel, lpc); + } + + server_channel->ops->waitout(server_channel); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + + cl_log(LOG_DEBUG, "Waiting for replies from the echo server"); + g_main_run(client_main); + cl_log(LOG_INFO, "[Iteration %d] Client %d completed successfully", iteration, lpc); + + return 0; +} + + +IPC_Channel * +init_client_ipctest_comms(const char *child, + gboolean (*dispatch)(IPC_Channel* source_data + ,gpointer user_data), + void *user_data) +{ + IPC_Channel *ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + return NULL; + } + sprintf(commpath, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + cl_log(LOG_DEBUG, "[Client] Attempting to talk on: %s", commpath); + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + ch = ipc_channel_constructor(IPC_ANYTYPE, attrs); + g_hash_table_destroy(attrs); + + if (ch == NULL) { + cl_log(LOG_ERR, "[Client] Could not access channel on: %s", commpath); + return NULL; + } else if(ch->ops->initiate_connection(ch) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Could not init comms on: %s", commpath); + return NULL; + } + + G_main_add_IPC_Channel(G_PRIORITY_LOW, + ch, FALSE, dispatch, user_data, + default_ipctest_input_destroy); + + return ch; +} + + +gboolean +transient_client_callback(IPC_Channel* server, void* private_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + static int received_responses = 0; + + GMainLoop *mainloop = (GMainLoop*)private_data; + + while(server->ops->is_message_pending(server) == TRUE) { + if (server->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is the + * new status of IPC_DISCONNECT */ + break; + } + if(server->ops->recv(server, &msg) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Error while invoking recv()"); + perror("[Client] Receive failure:"); + return FALSE; + } + + if (msg != NULL) { + buffer = (char*)msg->msg_body; + cl_log(LOG_DEBUG, "[Client] Got text [text=%s]", buffer); + received_responses++; + + if(lpc < MAX_MESSAGES && strcmp(messages[lpc], buffer) != 0) + { + cl_log(LOG_ERR, "[Client] Received someone else's message [%s] instead of [%s]", buffer, messages[lpc]); + } + else if(lpc >= MAX_MESSAGES) + { + cl_log(LOG_ERR, "[Client] Receivedan extra message [%s]", buffer); + } + + lpc++; + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Client] No message this time"); + } + } + + if(server->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Client] Client received HUP"); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Client] Processed %d IPC messages this time, %d total", lpc, received_responses); + + if(received_responses > 2) { + cl_log(LOG_INFO, "[Client] Processed %d IPC messages, all done.", received_responses); + received_responses = 0; + g_main_quit(mainloop); + cl_log(LOG_INFO, "[Client] Exiting."); + return FALSE; + } + + return TRUE; +} + +void +client_send_message(const char *message_text, + IPC_Channel *server_channel, + int iteration) +{ + IPC_Message *a_message = NULL; + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "[Client %d] Channel is in state %d before sending message [%s]", + iteration, server_channel->ch_status, message_text); + return; + } + + a_message = create_simple_message(message_text, server_channel); + if(a_message == NULL) { + cl_log(LOG_ERR, "Could not create message to send"); + } else { + cl_log(LOG_DEBUG, "[Client %d] Sending message: %s", iteration, (char*)a_message->msg_body); + while(server_channel->ops->send(server_channel, a_message) == IPC_FAIL) { + cl_log(LOG_ERR, "[Client %d] IPC channel is blocked", iteration); + cl_shortsleep(); + } + + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, + "[Client %d] Channel is in state %d after sending message [%s]", + iteration, server_channel->ch_status, message_text); + } + } +} diff --git a/lib/clplumbing/ipctransientlib.c b/lib/clplumbing/ipctransientlib.c new file mode 100644 index 0000000..7a6721e --- /dev/null +++ b/lib/clplumbing/ipctransientlib.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 <ipctransient.h> + +/* for basename() on some OSes (e.g. Solaris) */ +#include <libgen.h> + +#define WORKING_DIR HA_VARLIBHBDIR + +const char *procname = NULL; + +const char *commdir = WORKING_DIR; + +void +trans_getargs(int argc, char **argv) +{ + int argflag, argerrs; + + procname = basename(argv[0]); + + argerrs = 0; + while ((argflag = getopt(argc, argv, "C:")) != EOF) { + switch (argflag) { + case 'C': /* directory to commpath */ + commdir = optarg; + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-C commdir]\n" + "\t-C : directory to commpath (default %s)\n", + procname, WORKING_DIR); + exit(1); + } + +} + +void +default_ipctest_input_destroy(gpointer user_data) +{ + cl_log(LOG_INFO, "default_ipctest_input_destroy:received HUP"); +} + +IPC_Message * +create_simple_message(const char *text, IPC_Channel *ch) +{ + IPC_Message *ack_msg = NULL; + char *copy_text = NULL; + + if(text == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no text"); + return NULL; + } else if(ch == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no channel"); + return NULL; + } + + ack_msg = (IPC_Message *)malloc(sizeof(IPC_Message)); + if (ack_msg == NULL){ + cl_log(LOG_ERR, "create_simple_message:" + "allocating memory for IPC_Message failed"); + return NULL; + } + + memset(ack_msg, 0, sizeof(IPC_Message)); + + copy_text = strdup(text); + + ack_msg->msg_private = NULL; + ack_msg->msg_done = NULL; + ack_msg->msg_body = copy_text; + ack_msg->msg_ch = ch; + + ack_msg->msg_len = strlen(text)+1; + + return ack_msg; +} diff --git a/lib/clplumbing/ipctransientserver.c b/lib/clplumbing/ipctransientserver.c new file mode 100644 index 0000000..d7ee61d --- /dev/null +++ b/lib/clplumbing/ipctransientserver.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 <ipctransient.h> + +gboolean transient_server_callback(IPC_Channel *client, gpointer user_data); +gboolean transient_server_connect(IPC_Channel *client_channel, gpointer user_data); +int init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll); + +int +main(int argc, char ** argv) +{ + int iteration = 0; + GMainLoop* mainloop = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + init_server_ipc_comms("echo", transient_server_connect, default_ipctest_input_destroy, FALSE); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + mainloop = g_main_new(FALSE); + + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is active...", iteration); + g_main_run(mainloop); + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is stopped...", iteration); + + return 0; +} + + +int +init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll) +{ + /* the clients wait channel is the other source of events. + * This source delivers the clients connection events. + * listen to this source at a relatively lower priority. + */ + mode_t mask; + IPC_WaitConnection *wait_ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + exit(1); + } + snprintf(commpath, local_sock_len, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + + mask = umask(0); + wait_ch = ipc_wait_conn_constructor(IPC_ANYTYPE, attrs); + if (wait_ch == NULL){ + cl_perror("[Server] Can't create wait channel of type %s", IPC_ANYTYPE); + exit(1); + } + mask = umask(mask); + g_hash_table_destroy(attrs); + + G_main_add_IPC_WaitConnection(G_PRIORITY_LOW, + wait_ch, + NULL, + FALSE, + channel_client_connect, + wait_ch, /* user data passed to ?? */ + channel_input_destroy); + + cl_log(LOG_INFO, "[Server] Listening on: %s", commpath); + +/* if (!usenormalpoll) { */ +/* g_main_set_poll_func(cl_glibpoll); */ +/* ipc_set_pollfunc(cl_poll); */ +/* } */ + return 0; +} + +gboolean +transient_server_callback(IPC_Channel *client, gpointer user_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + IPC_Message *reply = NULL; + int llpc = 0; + + cl_log(LOG_DEBUG, "channel: %p", client); + + cl_log(LOG_DEBUG, "Client status %d (disconnect=%d)", client->ch_status, IPC_DISCONNECT); + + while(client->ops->is_message_pending(client)) { + if (client->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is that + * the IPC status is now IPC_DISCONNECT */ + break; + } + if(client->ops->recv(client, &msg) != IPC_OK) { + cl_perror("[Server] Receive failure"); + return FALSE; + } + + if (msg != NULL) { + lpc++; + buffer = (char*)g_malloc(msg->msg_len+1); + memcpy(buffer,msg->msg_body, msg->msg_len); + buffer[msg->msg_len] = '\0'; + cl_log(LOG_DEBUG, "[Server] Got xml [text=%s]", buffer); + + reply = create_simple_message(strdup(buffer), client); + if (!reply) { + cl_log(LOG_ERR, "[Server] Could allocate reply msg."); + return FALSE; + } + + llpc = 0; + while(llpc++ < MAX_IPC_FAIL && client->ops->send(client, reply) == IPC_FAIL) { + cl_log(LOG_WARNING, "[Server] ipc channel blocked"); + cl_shortsleep(); + } + + if(lpc == MAX_IPC_FAIL) { + cl_log(LOG_ERR, "[Server] Could not send IPC, message. Channel is dead."); + free(reply); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Server] Sent reply"); + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Server] No message this time"); + continue; + } + } + + cl_log(LOG_DEBUG, "[Server] Processed %d messages", lpc); + + cl_log(LOG_DEBUG, "[Server] Client status %d", client->ch_status); + if(client->ch_status != IPC_CONNECT) { + cl_log(LOG_INFO, "[Server] Server received HUP from child"); + return FALSE; + } + + return TRUE; +} + + +gboolean +transient_server_connect(IPC_Channel *client_channel, gpointer user_data) +{ + /* assign the client to be something, or put in a hashtable */ + cl_log(LOG_DEBUG, "A client tried to connect... and there was much rejoicing."); + + if(client_channel == NULL) { + cl_log(LOG_ERR, "[Server] Channel was NULL"); + } else if(client_channel->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Server] Channel was disconnected"); + } else { + cl_log(LOG_DEBUG, "[Server] Client is %s %p", client_channel == NULL?"NULL":"valid", client_channel); + cl_log(LOG_DEBUG, "[Server] Client status %d (disconnect=%d)", client_channel->ch_status, IPC_DISCONNECT); + + cl_log(LOG_DEBUG, "[Server] Adding IPC Channel to main thread."); + G_main_add_IPC_Channel(G_PRIORITY_LOW, + client_channel, + FALSE, + transient_server_callback, + NULL, + default_ipctest_input_destroy); + } + + return TRUE; +} diff --git a/lib/clplumbing/longclock.c b/lib/clplumbing/longclock.c new file mode 100644 index 0000000..594c9c5 --- /dev/null +++ b/lib/clplumbing/longclock.c @@ -0,0 +1,275 @@ +/* + * Longclock operations + * + * Copyright (c) 2002 International Business Machines + * Author: 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 <unistd.h> +#include <sys/times.h> +#include <errno.h> +#include <clplumbing/longclock.h> +#include <clplumbing/cl_log.h> + +static unsigned Hz = 0; +static longclock_t Lc_Hz; +static double d_Hz; + + +const longclock_t zero_longclock = 0UL; + +#ifndef CLOCK_T_IS_LONG_ENOUGH +# undef time_longclock +#endif + +#ifdef HAVE_LONGCLOCK_ARITHMETIC +# undef msto_longclock +# undef longclockto_ms +# undef secsto_longclock +# undef add_longclock +# undef sub_longclock +# undef cmp_longclock +#endif + + +unsigned +hz_longclock(void) +{ + if (Hz == 0) { + /* Compute various hz-related constants */ + + Hz = sysconf(_SC_CLK_TCK); + Lc_Hz = (longclock_t)Hz; + d_Hz = (double) Hz; + } + return Hz; +} + +#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 + +unsigned long +cl_times(void) /* Make times(2) behave rationally on Linux */ +{ + clock_t ret; +#ifndef DISABLE_TIMES_KLUDGE + int save_errno = errno; + + /* + * times(2) really returns an unsigned value ... + * + * We don't check to see if we got back the error value (-1), because + * the only possibility for an error would be if the address of + * longclock_dummy_tms_struct was invalid. Since it's a + * compiler-generated address, we assume that errors are impossible. + * And, unfortunately, it is quite possible for the correct return + * from times(2) to be exactly (clock_t)-1. Sigh... + * + */ + errno = 0; +#endif /* DISABLE_TIMES_KLUDGE */ + ret = times(TIMES_PARAM); + +#ifndef DISABLE_TIMES_KLUDGE +/* + * This is to work around a bug in the system call interface + * for times(2) found in glibc on Linux (and maybe elsewhere) + * It changes the return values from -1 to -4096 all into + * -1 and then dumps the -(return value) into errno. + * + * This totally bizarre behavior seems to be widespread in + * versions of Linux and glibc. + * + * Many thanks to Wolfgang Dumhs <wolfgang.dumhs (at) gmx.at> + * for finding and documenting this bizarre behavior. + */ + if (errno != 0) { + ret = (clock_t) (-errno); + } + errno = save_errno; +#endif /* DISABLE_TIMES_KLUDGE */ + + /* sizeof(long) may be larger than sizeof(clock_t). + * Don't jump from 0x7fffffff to 0xffffffff80000000 + * because of sign extension. + * We do expect sizeof(clock_t) <= sizeof(long), however. + */ + BUILD_BUG_ON(sizeof(clock_t) > sizeof(unsigned long)); +#define CLOCK_T_MAX (~0UL >> (8*(sizeof(unsigned long) - sizeof(clock_t)))) + return (unsigned long)ret & CLOCK_T_MAX; +} + +#ifdef CLOCK_T_IS_LONG_ENOUGH +longclock_t +time_longclock(void) +{ + /* See note below about deliberately ignoring errors... */ + return (longclock_t)cl_times(); +} + +#else /* clock_t is shorter than 64 bits */ + +#define BITSPERBYTE 8 +#define WRAPSHIFT (BITSPERBYTE*sizeof(clock_t)) +#define WRAPAMOUNT (((longclock_t) 1) << WRAPSHIFT) +#define MINJUMP ((CLOCK_T_MAX/100UL)*99UL) + +longclock_t +time_longclock(void) +{ + /* Internal note: This updates the static fields; care should be + * taken to not call a function like cl_log (which internally + * calls time_longclock() as well) just before this happens, + * because then this can recurse infinitely; that is why the + * cl_log call is where it is; found by Simon Graham. */ + static gboolean calledbefore = FALSE; + static unsigned long lasttimes = 0L; + static unsigned long callcount = 0L; + static longclock_t lc_wrapcount = 0L; + unsigned long timesval; + + ++callcount; + + timesval = cl_times(); + + if (calledbefore && timesval < lasttimes) { + unsigned long jumpbackby = lasttimes - timesval; + + if (jumpbackby < MINJUMP) { + /* Kernel weirdness */ + cl_log(LOG_CRIT + , "%s: clock_t from times(2) appears to" + " have jumped backwards (in error)!" + , __FUNCTION__); + cl_log(LOG_CRIT + , "%s: old value was %lu" + ", new value is %lu, diff is %lu, callcount %lu" + , __FUNCTION__ + , (unsigned long)lasttimes + , (unsigned long)timesval + , (unsigned long)jumpbackby + , callcount); + /* Assume jump back was the error and ignore it */ + /* (i.e., hope it goes away) */ + }else{ + /* Normal looking wraparound */ + /* update last time BEFORE loging as log call + can call this routine recursively leading + to double update of wrapcount! */ + + lasttimes = timesval; + lc_wrapcount += WRAPAMOUNT; + + cl_log(LOG_INFO + , "%s: clock_t wrapped around (uptime)." + , __FUNCTION__); + } + } + else { + lasttimes = timesval; + calledbefore = TRUE; + } + return (lc_wrapcount | timesval); +} +#endif /* ! CLOCK_T_IS_LONG_ENOUGH */ + +longclock_t +msto_longclock(unsigned long ms) +{ + unsigned long secs = ms / 1000UL; + unsigned long msec = ms % 1000; + longclock_t result; + + (void)(Hz == 0 && hz_longclock()); + + if (ms == 0) { + return (longclock_t)0UL; + } + result = secs * Lc_Hz + (msec * Lc_Hz)/1000; + + if (result == 0) { + result = 1; + } + return result; +} + +longclock_t +secsto_longclock(unsigned long Secs) +{ + longclock_t secs = Secs; + + (void)(Hz == 0 && hz_longclock()); + + return secs * Lc_Hz; +} + +longclock_t +dsecsto_longclock(double v) +{ + (void)(Hz == 0 && hz_longclock()); + + return (longclock_t) ((v * d_Hz)+0.5); + +} + +unsigned long +longclockto_ms(longclock_t t) +{ + (void)(Hz == 0 && hz_longclock()); + + if (t == 0) { + return 0UL; + } + return (unsigned long) ((t*1000UL)/Lc_Hz); +} +#ifndef CLOCK_T_IS_LONG_ENOUGH +long +longclockto_long(longclock_t t) +{ + return ((long)(t)); +} + +longclock_t +add_longclock(longclock_t l, longclock_t r) +{ + return l + r; +} + +longclock_t +sub_longclock(longclock_t l, longclock_t r) +{ + return l - r; +} + +int +cmp_longclock(longclock_t l, longclock_t r) +{ + if (l < r) { + return -1; + } + if (l > r) { + return 1; + } + return 0; +} +#endif /* CLOCK_T_IS_LONG_ENOUGH */ diff --git a/lib/clplumbing/md5.c b/lib/clplumbing/md5.c new file mode 100644 index 0000000..b893483 --- /dev/null +++ b/lib/clplumbing/md5.c @@ -0,0 +1,335 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Cleaned up for heartbeat by + * Mitja Sarp <mitja@lysator.liu.se> + * Sun Jiang Dong <sunjd@cn.ibm.com> + * Pan Jia Ming <jmltc@cn.ibm.com> + * + */ + +#include <lha_internal.h> + +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for memcpy() */ +#include <sys/types.h> /* for stupid systems */ +#include <netinet/in.h> /* for ntohl() */ +#include <clplumbing/cl_log.h> +#include <clplumbing/md5.h> + +#define MD5_DIGESTSIZE 16 +#define MD5_BLOCKSIZE 64 + +typedef struct MD5Context_st { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}MD5Context; + +#define md5byte unsigned char + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +void MD5Init(MD5Context *context); +void MD5Update(MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +#ifdef CONFIG_BIG_ENDIAN +static inline void byteSwap(uint32_t * buf, uint32_t len); + +static inline void +byteSwap(uint32_t * buf, uint32_t len) +{ + int i; + for (i = 0; i < len; i ++) { + uint32_t tmp = buf[i]; + buf[i] = ( (uint32_t) ((unsigned char *) &tmp)[0] ) | + (((uint32_t) ((unsigned char *) &tmp)[1]) << 8) | + (((uint32_t) ((unsigned char *) &tmp)[2]) << 16) | + (((uint32_t) ((unsigned char *) &tmp)[3]) << 24); + } +} +#elif defined(CONFIG_LITTLE_ENDIAN) + #define byteSwap(buf,words) +#else + #error "Neither CONFIG_BIG_ENDIAN nor CONFIG_LITTLE_ENDIAN defined!" +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301ul; + ctx->buf[1] = 0xefcdab89ul; + ctx->buf[2] = 0x98badcfeul; + ctx->buf[3] = 0x10325476ul; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 16); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) ((x) ^ (y) ^ (z)) +#define F4(x, y, z) ((y) ^ ((x) | ~(z))) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + (in), (w) = ((w)<<(s) | (w)>>(32-(s))) + (x)) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478ul, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756ul, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbul, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeul, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faful, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aul, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613ul, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501ul, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8ul, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7aful, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1ul, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beul, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122ul, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193ul, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eul, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821ul, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562ul, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340ul, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51ul, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaul, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dul, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453ul, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681ul, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8ul, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6ul, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6ul, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87ul, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edul, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905ul, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8ul, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9ul, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aul, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942ul, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681ul, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122ul, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cul, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44ul, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9ul, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60ul, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70ul, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6ul, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faul, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085ul, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05ul, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039ul, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5ul, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8ul, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665ul, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244ul, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97ul, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7ul, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039ul, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3ul, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92ul, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dul, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1ul, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4ful, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0ul, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314ul, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1ul, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82ul, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235ul, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbul, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391ul, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +int MD5( const unsigned char *data + , unsigned long len + , unsigned char *digest) +{ + MD5Context context; + + MD5Init(&context); + MD5Update(&context, data, len); + MD5Final(digest, &context); + + return 0; +} + +int HMAC( const unsigned char * key + , unsigned int key_len + , const unsigned char * text + , unsigned long textlen + , unsigned char * digest) +{ + MD5Context context; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - * key XORd with opad */ + unsigned char k_opad[65]; + unsigned char tk[MD5_DIGESTSIZE]; + int i; + + /* if key is longer than MD5_BLOCKSIZE bytes reset it to key=MD5(key) */ + if (key_len > MD5_BLOCKSIZE) { + MD5Context tctx; + MD5Init(&tctx); + MD5Update(&tctx, (const unsigned char *)key, key_len); + MD5Final(tk, &tctx); + + key = (unsigned char *)tk; + key_len = MD5_DIGESTSIZE; + } + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<MD5_BLOCKSIZE; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* perform inner MD5 */ + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, MD5_BLOCKSIZE); /* start with inner pad */ + MD5Update(&context, text, textlen); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + + /* perform outer MD5 */ + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, MD5_BLOCKSIZE); /* start with outer pad */ + MD5Update(&context, digest, MD5_DIGESTSIZE); /* then results of 1st hash */ + + MD5Final(digest, &context); /* finish up 2nd pass */ + + return 0; +} diff --git a/lib/clplumbing/mkstemp_mode.c b/lib/clplumbing/mkstemp_mode.c new file mode 100644 index 0000000..69c080b --- /dev/null +++ b/lib/clplumbing/mkstemp_mode.c @@ -0,0 +1,56 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <clplumbing/mkstemp_mode.h> + + +/* + * A slightly safer version of mkstemp(3) + * + * In this version, the file is initially created mode 0, and then chmod-ed + * to the requested permissions. This guarantees that the file is never + * open to others beyond the specified permissions at any time. + */ +int +mkstemp_mode(char* template, mode_t filemode) +{ + + mode_t maskval; + int fd; + + maskval = umask(0777); + + /* created file should now be mode 0000 */ + fd = mkstemp(template); + + umask(maskval); /* cannot fail :-) */ + + if (fd >= 0) { + if (chmod(template, filemode) < 0) { + int save = errno; + close(fd); + errno = save; + fd = -1; + } + } + return fd; +} diff --git a/lib/clplumbing/netstring_test.c b/lib/clplumbing/netstring_test.c new file mode 100644 index 0000000..1f498ec --- /dev/null +++ b/lib/clplumbing/netstring_test.c @@ -0,0 +1,255 @@ +/* + * netstring_test: Test program for testing the heartbeat binary/struct API + * + * Copyright (C) 2000 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <stdarg.h> +#include <syslog.h> +#include <hb_api_core.h> +#include <hb_api.h> + +/* + * A heartbeat API test program... + */ + +void NodeStatus(const char * node, const char * status, void * private); +void LinkStatus(const char * node, const char *, const char *, void*); +void gotsig(int nsig); + +void +NodeStatus(const char * node, const char * status, void * private) +{ + cl_log(LOG_NOTICE, "Status update: Node %s now has status %s" + , node, status); +} + +void +LinkStatus(const char * node, const char * lnk, const char * status +, void * private) +{ + cl_log(LOG_NOTICE, "Link Status update: Link %s/%s now has status %s" + , node, lnk, status); +} + +int quitnow = 0; +void gotsig(int nsig) +{ + (void)nsig; + quitnow = 1; +} + +#define BUFSIZE 16 +extern int netstring_format; + +int +main(int argc, char ** argv) +{ + struct ha_msg* reply; + struct ha_msg* pingreq = NULL; + unsigned fmask; + ll_cluster_t* hb; + int msgcount=0; + char databuf[BUFSIZE]; + int i; +#if 0 + char * ctmp; + const char * cval; + int j; +#endif + + netstring_format = 0; + + cl_log_set_entity(argv[0]); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + hb = ll_cluster_new("heartbeat"); + cl_log(LOG_INFO, "PID=%ld", (long)getpid()); + cl_log(LOG_INFO, "Signing in with heartbeat"); + if (hb->llc_ops->signon(hb, "ping")!= HA_OK) { + cl_log(LOG_ERR, "Cannot sign on with heartbeat"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(1); + } + + if (hb->llc_ops->set_nstatus_callback(hb, NodeStatus, NULL) !=HA_OK){ + cl_log(LOG_ERR, "Cannot set node status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(2); + } + + if (hb->llc_ops->set_ifstatus_callback(hb, LinkStatus, NULL)!=HA_OK){ + cl_log(LOG_ERR, "Cannot set if status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(3); + } + +#if 0 + fmask = LLC_FILTER_RAW; +#else + fmask = LLC_FILTER_DEFAULT; +#endif + /* This isn't necessary -- you don't need this call - it's just for testing... */ + cl_log(LOG_INFO, "Setting message filter mode"); + if (hb->llc_ops->setfmode(hb, fmask) != HA_OK) { + cl_log(LOG_ERR, "Cannot set filter mode"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(4); + } + + CL_SIGINTERRUPT(SIGINT, 1); + CL_SIGNAL(SIGINT, gotsig); + + pingreq = ha_msg_new(0); + ha_msg_add(pingreq, F_TYPE, "ping"); + { + struct ha_msg *childmsg; + struct ha_msg *grandchildmsg; + + for(i = 0 ;i < BUFSIZE;i ++){ + databuf[i] = 1 + i ; + } + databuf[4] = 0; + + ha_msg_addbin(pingreq, "data",databuf , BUFSIZE); + + + childmsg = ha_msg_new(0); + ha_msg_add(childmsg, "name","testchild"); + ha_msg_addbin(childmsg, "data",databuf , BUFSIZE); + + grandchildmsg = ha_msg_new(0); + ha_msg_add(grandchildmsg, "name","grandchild"); + ha_msg_addstruct(childmsg, "child",grandchildmsg); + + if( ha_msg_addstruct(pingreq, "child", childmsg) != HA_OK){ + cl_log(LOG_ERR, "adding a child message to the message failed"); + exit(1); + } + + } + + cl_log(LOG_INFO, "printing out the pingreq message:"); + + ha_log_message(pingreq); + if (hb->llc_ops->sendclustermsg(hb, pingreq) == HA_OK) { + cl_log(LOG_INFO, "Sent ping request to cluster"); + }else{ + cl_log(LOG_ERR, "PING request FAIL to cluster"); + } + errno = 0; + for(; !quitnow && (reply=hb->llc_ops->readmsg(hb, 1)) != NULL;) { + const char * type; + const char * orig; + ++msgcount; + if ((type = ha_msg_value(reply, F_TYPE)) == NULL) { + type = "?"; + } + if ((orig = ha_msg_value(reply, F_ORIG)) == NULL) { + orig = "?"; + } + cl_log(LOG_INFO, " "); + cl_log(LOG_NOTICE, "Got message %d of type [%s] from [%s]" + , msgcount, type, orig); + + if (strcmp(type, "ping") ==0) { + int datalen = 0; + const char *data; + struct ha_msg *childmsg; + + cl_log(LOG_INFO, "****************************************"); + ha_log_message(reply); + + data = cl_get_binary(reply, "data", &datalen); + if(data){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "%d of data received,data=%s", datalen,data); + for(i = 0; i < datalen; i++){ + if( databuf[i] != data[i]){ + cl_log(LOG_ERR, "data does not match at %d",i); + break; + } + } + if(i == datalen){ + cl_log(LOG_INFO,"data matches"); + } + }else { + cl_log(LOG_WARNING, "cl_get_binary failed"); + } + + childmsg = cl_get_struct(reply,"child"); + if(childmsg){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "child message found"); + ha_log_message(childmsg); + }else{ + cl_log(LOG_WARNING, "cl_get_struct failed"); + } + + } + +#if 1 + { + struct ha_msg *cpmsg; + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "****************************************************"); + cl_log(LOG_INFO, "Testing ha_msg_copy():"); + cpmsg = ha_msg_copy(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "orginal message is :"); + cl_log(LOG_INFO, " "); + ha_log_message(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "copied message is: "); + cl_log(LOG_INFO, " "); + ha_log_message(cpmsg); + ha_msg_del(cpmsg); + } + + ha_msg_del(reply); reply=NULL; +#endif + } + + if (!quitnow) { + cl_log(LOG_ERR, "read_hb_msg returned NULL"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + } + if (hb->llc_ops->signoff(hb, TRUE) != HA_OK) { + cl_log(LOG_ERR, "Cannot sign off from heartbeat."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(10); + } + if (hb->llc_ops->delete(hb) != HA_OK) { + cl_log(LOG_ERR, "Cannot delete API object."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(11); + } + return 0; +} diff --git a/lib/clplumbing/ocf_ipc.c b/lib/clplumbing/ocf_ipc.c new file mode 100644 index 0000000..c243934 --- /dev/null +++ b/lib/clplumbing/ocf_ipc.c @@ -0,0 +1,594 @@ +/* + * + * ocf_ipc.c: IPC abstraction implementation. + * + * + * Copyright (c) 2002 Xiaoxiang Liu <xiliu@ncsa.uiuc.edu> + * + * 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 <clplumbing/ipc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/poll.h> +#include <clplumbing/cl_log.h> +#include <sys/types.h> +#include <unistd.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> + +static int num_pool_allocated = 0; +static int num_pool_freed = 0; + +#ifdef IPC_TIME_DEBUG +struct ha_msg; +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); +#endif + +struct IPC_WAIT_CONNECTION * socket_wait_conn_new(GHashTable* ch_attrs); +struct IPC_CHANNEL * socket_client_channel_new(GHashTable* ch_attrs); + +int (*ipc_pollfunc_ptr)(struct pollfd*, unsigned int, int) += (int (*)(struct pollfd*, unsigned int, int)) poll; + +/* Set the IPC poll function to the given function */ +void +ipc_set_pollfunc(int (*pf)(struct pollfd*, unsigned int, int)) +{ + ipc_pollfunc_ptr = pf; +} + +struct IPC_WAIT_CONNECTION * +ipc_wait_conn_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + return socket_wait_conn_new(ch_attrs); + } + return NULL; +} + +struct IPC_CHANNEL * +ipc_channel_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + + return socket_client_channel_new(ch_attrs); + } + return NULL; +} + +static int +gnametonum(const char * gname, int gnlen) +{ + char grpname[64]; + struct group* grp; + + if (isdigit((int) gname[0])) { + return atoi(gname); + } + if (gnlen >= (int)sizeof(grpname)) { + return -1; + } + strncpy(grpname, gname, gnlen); + grpname[gnlen] = EOS; + if ((grp = getgrnam(grpname)) == NULL) { + cl_log(LOG_ERR + , "Invalid group name [%s]", grpname); + return -1; + } + return (int)grp->gr_gid; +} + +static int +unametonum(const char * lname, int llen) +{ + char loginname[64]; + struct passwd* pwd; + + if (llen >= (int)sizeof(loginname)) { + cl_log(LOG_ERR + , "user id name [%s] is too long", loginname); + return -1; + } + strncpy(loginname, lname, llen); + loginname[llen] = EOS; + + if (isdigit((int) loginname[0])) { + return atoi(loginname); + } + if ((pwd = getpwnam(loginname)) == NULL) { + cl_log(LOG_ERR + , "Invalid user id name [%s]", loginname); + return -1; + } + return (int)pwd->pw_uid; +} + +static GHashTable* +make_id_table(const char * list, int listlen, int (*map)(const char *, int)) +{ + GHashTable* ret; + const char * id; + const char * lastid = list + listlen; + int idlen; + int idval; + static int one = 1; + + ret = g_hash_table_new(g_direct_hash, g_direct_equal); + + id = list; + while (id < lastid && *id != EOS) { + idlen = strcspn(id, ","); + if (id+idlen >= lastid) { + idlen = (lastid - id); + } + idval = map(id, idlen); + if (idval < 0) { + g_hash_table_destroy(ret); + return NULL; + } +#if 0 + cl_log(LOG_DEBUG + , "Adding [ug]id %*s [%d] to authorization g_hash_table" + , idlen, id, idval); +#endif + g_hash_table_insert(ret, GUINT_TO_POINTER(idval), &one); + id += idlen; + if (id < lastid) { + id += strspn(id, ","); + } + } + return ret; +} + +struct IPC_AUTH* +ipc_str_to_auth(const char* uidlist, int uidlen, const char* gidlist, int gidlen) +{ + struct IPC_AUTH* auth; + + auth = malloc(sizeof(struct IPC_AUTH)); + if (auth == NULL) { + cl_log(LOG_ERR, "Out of memory for IPC_AUTH"); + return NULL; + } + + memset(auth, 0, sizeof(*auth)); + + if (uidlist) { + auth->uid = make_id_table(uidlist, uidlen, unametonum); + if (auth->uid == NULL) { + cl_log(LOG_ERR, + "Bad uid list [%*s]", + uidlen, uidlist); + goto errout; + } + } + if (gidlist) { + auth->gid = make_id_table(gidlist, gidlen, gnametonum); + if (auth->gid == NULL) { + cl_log(LOG_ERR , + "Bad gid list [%*s]", + gidlen, gidlist); + goto errout; + } + } + return auth; + + errout: + if (auth->uid) { + g_hash_table_destroy(auth->uid); + auth->uid = NULL; + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + auth->gid = NULL; + } + free(auth); + auth = NULL; + return NULL; +} + +struct IPC_AUTH * +ipc_set_auth(uid_t * a_uid, gid_t * a_gid, int num_uid, int num_gid) +{ + struct IPC_AUTH *temp_auth; + int i; + static int v = 1; + + temp_auth = malloc(sizeof(struct IPC_AUTH)); + if (temp_auth == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed",__FUNCTION__); + return NULL; + } + temp_auth->uid = g_hash_table_new(g_direct_hash, g_direct_equal); + temp_auth->gid = g_hash_table_new(g_direct_hash, g_direct_equal); + + if (num_uid > 0) { + for (i=0; i<num_uid; i++) { + g_hash_table_insert(temp_auth->uid, GINT_TO_POINTER((gint)a_uid[i]) + , &v); + } + } + + if (num_gid > 0) { + for (i=0; i<num_gid; i++) { + g_hash_table_insert(temp_auth->gid, GINT_TO_POINTER((gint)a_gid[i]) + , &v); + } + } + + return temp_auth; +} + +void +ipc_destroy_auth(struct IPC_AUTH *auth) +{ + if (auth != NULL) { + if (auth->uid) { + g_hash_table_destroy(auth->uid); + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + } + free((void *)auth); + } +} + +static void +ipc_bufpool_display(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + cl_log(LOG_INFO, "pool: refcount=%d, startpos=%p, currpos=%p," + "consumepos=%p, endpos=%p, size=%d", + pool->refcount, pool->startpos, + pool->currpos, pool->consumepos, + pool->endpos, pool->size); +} + +void +ipc_bufpool_dump_stats(void) +{ + cl_log(LOG_INFO, "num_pool_allocated=%d, num_pool_freed=%d, diff=%d", + num_pool_allocated, + num_pool_freed, + num_pool_allocated - num_pool_freed); +} + +#define POOLHDR_SIZE \ + (sizeof(struct ipc_bufpool) + 2*sizeof(struct SOCKET_MSG_HEAD)) + +struct ipc_bufpool* +ipc_bufpool_new(int size) +{ + struct ipc_bufpool* pool; + int totalsize; + + /* there are memories for two struct SOCKET_MSG_HEAD + * one for the big message, the other one for the next + * message. This code prevents allocating + * <big memory> <4k> <big memory><4k> ... + * from happening when a client sends big messages + * constantly*/ + + totalsize = size + POOLHDR_SIZE; + + if (totalsize < POOL_SIZE) { + totalsize = POOL_SIZE; + } + + if (totalsize > MAXMSG + POOLHDR_SIZE) { + cl_log(LOG_INFO, "ipc_bufpool_new: " + "asking for buffer with size %d; " + "corrupted data len???", totalsize); + return NULL; + } + + pool = (struct ipc_bufpool*)malloc(totalsize+1); + if (pool == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed", __FUNCTION__); + return NULL; + } + memset(pool, 0, totalsize); + pool->refcount = 1; + pool->startpos = pool->currpos = pool->consumepos = + ((char*)pool) + sizeof(struct ipc_bufpool); + + pool->endpos = ((char*)pool) + totalsize; + pool->size = totalsize; + + num_pool_allocated ++ ; + + return pool; +} + +void +ipc_bufpool_del(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + + if (pool->refcount > 0) { + cl_log(LOG_ERR," ipc_bufpool_del:" + " IPC buffer pool reference count > 0"); + return; + } + + memset(pool, 0, pool->size); + free(pool); + num_pool_freed ++ ; +} + +int +ipc_bufpool_spaceleft(struct ipc_bufpool* pool) +{ + if( pool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_spaceleft:" + " invalid input argument"); + return 0; + } + return pool->endpos - pool->currpos; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +ipc_bufpool_msg_done(struct IPC_MESSAGE * msg) +{ + struct ipc_bufpool* pool; + + if (msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_done: invalid input"); + return; + } + + pool = (struct ipc_bufpool*)msg->msg_private; + + ipc_bufpool_unref(pool); + free(msg); +} + +static struct IPC_MESSAGE* +ipc_bufpool_msg_new(void) +{ + struct IPC_MESSAGE * temp_msg; + + temp_msg = malloc(sizeof(struct IPC_MESSAGE)); + if (temp_msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_new:" + "allocating new msg failed"); + return NULL; + } + + memset(temp_msg, 0, sizeof(struct IPC_MESSAGE)); + + return temp_msg; +} + +static void +ipcmsg_display(IPC_Message* ipcmsg) +{ + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipcmsg is NULL"); + return; + } + cl_log(LOG_INFO, "ipcmsg: msg_len=%lu, msg_buf=%p, msg_body=%p," + "msg_done=%p, msg_private=%p, msg_ch=%p", + (unsigned long)ipcmsg->msg_len, + ipcmsg->msg_buf, + ipcmsg->msg_body, + ipcmsg->msg_done, + ipcmsg->msg_private, + ipcmsg->msg_ch); +} + +/* after a recv call, we have new data + * in the pool buf, we need to update our + * pool struct to consume it + * + */ + +int +ipc_bufpool_update(struct ipc_bufpool* pool, + struct IPC_CHANNEL * ch, + int msg_len, + IPC_Queue* rqueue) +{ + IPC_Message* ipcmsg; + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + int nmsgs = 0 ; + + if (rqueue == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "invalid input"); + return 0; + } + + pool->currpos += msg_len; + + while(TRUE) { + /*not enough data for head*/ + if ((int)(pool->currpos - pool->consumepos) < (int)ch->msgpad) { + break; + } + + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + if (head->magic != HEADMAGIC) { + GList* last = g_list_last(rqueue->queue); + cl_log(LOG_ERR, "ipc_bufpool_update: " + "magic number in head does not match. " + "Something very bad happened, farside pid =%d", + ch->farside_pid); + cl_log(LOG_ERR, "magic=%x, expected value=%x", head->magic, HEADMAGIC); + ipc_bufpool_display(pool); + cl_log(LOG_INFO, "nmsgs=%d", nmsgs); + /*print out the last message in queue*/ + if (last) { + IPC_Message* m = (IPC_Message*)last; + ipcmsg_display(m); + } + return -1; + } + + if ( head->msg_len > MAXMSG) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "msg length is corruptted(%d)", + head->msg_len); + break; + } + + if (pool->consumepos + ch->msgpad + head->msg_len + > pool->currpos) { + break; + } + + ipcmsg = ipc_bufpool_msg_new(); + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "allocating memory for new ipcmsg failed"); + break; + + } + ipcmsg->msg_buf = pool->consumepos; + ipcmsg->msg_body = pool->consumepos + ch->msgpad; + ipcmsg->msg_len = head->msg_len; + ipcmsg->msg_private = pool; + ipcmsg->msg_done = ipc_bufpool_msg_done; +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,ipcmsg, MSGPOS_RECV); +#endif + rqueue->queue = g_list_append(rqueue->queue, ipcmsg); + rqueue->current_qlen ++; + nmsgs++; + + pool->consumepos += ch->msgpad + head->msg_len; + ipc_bufpool_ref(pool); + } + return nmsgs; +} + +gboolean +ipc_bufpool_full(struct ipc_bufpool* pool, + struct IPC_CHANNEL* ch, + int* dataspaceneeded) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + + *dataspaceneeded = 0; + /* not enough space for head */ + if ((int)(pool->endpos - pool->consumepos) < (int)ch->msgpad) { + return TRUE; + } + + /*enough space for head*/ + if ((int)(pool->currpos - pool->consumepos) >= (int)ch->msgpad) { + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + /* not enough space for data*/ + if ( pool->consumepos + ch->msgpad + head->msg_len >= pool->endpos) { + *dataspaceneeded = head->msg_len; + return TRUE; + } + } + + /* Either we are sure we have enough space + * or we cannot tell because we have not received + * head yet. But we are sure we have enough space + * for head + */ + return FALSE; +} + +int +ipc_bufpool_partial_copy(struct ipc_bufpool* dstpool, + struct ipc_bufpool* srcpool) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD *head = &localhead; + int space_needed; + int nbytes; + + if (dstpool == NULL + || srcpool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + "invalid input"); + return IPC_FAIL; + } + + if (srcpool->currpos - srcpool->consumepos >= + (ssize_t)sizeof(struct SOCKET_MSG_HEAD)) { + + memcpy(head, srcpool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + space_needed = head->msg_len + sizeof(*head); + + if (space_needed > ipc_bufpool_spaceleft(dstpool)) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + " not enough space left in dst pool,spaced needed=%d", + space_needed); + return IPC_FAIL; + } + } + + nbytes = srcpool->currpos - srcpool->consumepos; + memcpy(dstpool->consumepos, srcpool->consumepos,nbytes); + + srcpool->currpos = srcpool->consumepos; + dstpool->currpos = dstpool->consumepos + nbytes; + + return IPC_OK; +} + +void +ipc_bufpool_ref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "ref_pool:" + " invalid input"); + return; + } + pool->refcount ++; +} + +void +ipc_bufpool_unref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "unref_pool:" + " invalid input"); + return; + } + pool->refcount --; + if (pool->refcount <= 0) { + ipc_bufpool_del(pool); + } +} diff --git a/lib/clplumbing/proctrack.c b/lib/clplumbing/proctrack.c new file mode 100644 index 0000000..f6a9df2 --- /dev/null +++ b/lib/clplumbing/proctrack.c @@ -0,0 +1,515 @@ +/* + * Process tracking object. + * + * Copyright (c) 2002 International Business Machines + * Author: 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 <errno.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <time.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/uids.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/Gmain_timeout.h> + +#define DEBUGPROCTRACK debugproctrack + + +int debugproctrack = 0; +static int LoggingIsEnabled = 1; +static GHashTable* ProcessTable = NULL; +static void InitProcTable(void); +static void ForEachProcHelper(gpointer key, gpointer value +, void * helper); +static gboolean TrackedProcTimeoutFunction(gpointer p); + +static void +InitProcTable() +{ + if (ProcessTable) { + return; + } + + ProcessTable = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +/* Create/Log a new tracked process */ +void +NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel +, void * privatedata, ProcTrack_ops* ops) +{ + ProcTrack* p = g_new(ProcTrack, 1); + + InitProcTable(); + p->pid = pid; + p->isapgrp = isapgrp; + p->loglevel = loglevel; + p->privatedata = privatedata; + p->ops = ops; + p->startticks = time_longclock(); + p->starttime = time(NULL); + p->timerid = 0; + p->timeoutseq = -1; + p->killinfo = NULL; + + g_hash_table_insert(ProcessTable, GINT_TO_POINTER(pid), p); + + /* Tell them that someone registered a process */ + if (p->ops->procregistered) { + p->ops->procregistered(p); + } +} + +static struct signal_info_s { + int signo; + const char * sigdefine; + const char* sigwords; +} signal_info [] = { + +#ifdef SIGHUP + {SIGHUP, "SIGHUP", "Hangup"}, +#endif +#ifdef SIGINT + {SIGINT, "SIGINT", "Interrupt"}, +#endif +#ifdef SIGQUIT + {SIGQUIT, "SIGQUIT", "Quit"}, +#endif +#ifdef SIGILL + {SIGILL, "SIGILL", "Illegal instruction"}, +#endif +#ifdef SIGTRAP + {SIGTRAP, "SIGTRAP", "Trace"}, +#endif +#ifdef SIGABRT + {SIGABRT, "SIGABRT", "Abort"}, +#endif +#ifdef SIGIOT + {SIGIOT, "SIGIOT", "IOT trap"}, +#endif +#ifdef SIGBUS + {SIGBUS, "SIGBUS", "BUS error"}, +#endif +#ifdef SIGFPE + {SIGFPE, "SIGFPE", "Floating-point exception"}, +#endif +#ifdef SIGKILL + {SIGKILL, "SIGKILL", "Kill, unblockable"}, +#endif +#ifdef SIGUSR1 + {SIGUSR1, "SIGUSR1", "User-defined signal 1"}, +#endif +#ifdef SIGSEGV + {SIGSEGV, "SIGSEGV", "Segmentation violation"}, +#endif +#ifdef SIGUSR2 + {SIGUSR2, "SIGUSR2", "User-defined signal 2"}, +#endif +#ifdef SIGPIPE + {SIGPIPE, "SIGPIPE", "Broken pipe (POSIX)"}, +#endif +#ifdef SIGALRM + {SIGALRM, "SIGALRM", "Alarm clock (POSIX)"}, +#endif +#ifdef SIGTERM + {SIGTERM, "SIGTERM", "Termination (ANSI)"}, +#endif +#ifdef SIGSTKFLT + {SIGSTKFLT, "SIGSTKFLT", "Stack fault"}, +#endif +#ifdef SIGCHLD + {SIGCHLD, "SIGCHLD", "Child status has changed"}, +#endif +#ifdef SIGCLD + {SIGCLD, "SIGCLD ", "Child status has changed"}, +#endif +#ifdef SIGCONT + {SIGCONT, "SIGCONT", "Continue"}, +#endif +#ifdef SIGSTOP + {SIGSTOP, "SIGSTOP", "Stop, unblockable"}, +#endif +#ifdef SIGTSTP + {SIGTSTP, "SIGTSTP", "Keyboard stop"}, +#endif +#ifdef SIGTTIN + {SIGTTIN, "SIGTTIN", "Background read from tty"}, +#endif +#ifdef SIGTTOU + {SIGTTOU, "SIGTTOU", "Background write to tty"}, +#endif +#ifdef SIGURG + {SIGURG, "SIGURG ", "Urgent condition on socket"}, +#endif +#ifdef SIGXCPU + {SIGXCPU, "SIGXCPU", "CPU limit exceeded"}, +#endif +#ifdef SIGXFSZ + {SIGXFSZ, "SIGXFSZ", "File size limit exceeded"}, +#endif +#ifdef SIGVTALRM + {SIGVTALRM, "SIGVTALRM", "Virtual alarm clock"}, +#endif +#ifdef SIGPROF + {SIGPROF, "SIGPROF", "Profiling alarm clock"}, +#endif +#ifdef SIGWINCH + {SIGWINCH, "SIGWINCH", "Window size change"}, +#endif +#ifdef SIGPOLL + {SIGPOLL, "SIGPOLL", "Pollable event occurred"}, +#endif +#ifdef SIGIO + {SIGIO, "SIGIO", "I/O now possible"}, +#endif +#ifdef SIGPWR + {SIGPWR, "SIGPWR", "Power failure restart"}, +#endif +#ifdef SIGSYS + {SIGSYS, "SIGSYS", "Bad system call"}, +#endif +}; +static const char * +signal_name(int signo, const char ** sigdescription) +{ + int j; + for (j=0; j < DIMOF(signal_info); ++j) { + if (signal_info[j].signo == signo) { + if (sigdescription) { + *sigdescription = signal_info[j].sigwords; + } + return signal_info[j].sigdefine; + } + } + if (sigdescription) { + *sigdescription = NULL; + } + return NULL; +} + +/* returns TRUE if 'pid' was registered */ +int +ReportProcHasDied(int pid, int status) +{ + ProcTrack* p; + int signo=0; + int deathbyexit=0; + int deathbysig=0; + int exitcode=0; + int doreport = 0; + int debugreporting = 0; + const char * type; + ProcTrackLogType level; +#ifdef WCOREDUMP + int didcoredump = 0; +#endif + if ((p = GetProcInfo(pid)) == NULL) { + if (DEBUGPROCTRACK) { + cl_log(LOG_DEBUG + , "Process %d died (%d) but is not tracked." + , pid, status); + } + type = "untracked process"; + level = PT_LOGNONE; + }else{ + type = p->ops->proctype(p); + level = p->loglevel; + } + + if (WIFEXITED(status)) { + deathbyexit=1; + exitcode = WEXITSTATUS(status); + }else if (WIFSIGNALED(status)) { + deathbysig=1; + signo = WTERMSIG(status); + doreport=1; + } + switch(level) { + case PT_LOGVERBOSE: doreport=1; + break; + + case PT_LOGNONE: doreport = 0; + break; + + case PT_LOGNORMAL: break; + } + + if (!LoggingIsEnabled) { + doreport = 0; + } +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + /* Force a report on all core dumping processes */ + didcoredump=1; + doreport=1; + } +#endif + if (DEBUGPROCTRACK && !doreport) { + doreport = 1; + debugreporting = 1; + } + + if (doreport) { + if (deathbyexit) { + cl_log((exitcode == 0 ? LOG_INFO : LOG_WARNING) + , "Managed %s process %d exited with return code %d." + , type, pid, exitcode); + }else if (deathbysig) { + const char * signame = NULL; + const char * sigwords = NULL; + int logtype; + signame = signal_name(signo, &sigwords); + logtype = (debugreporting ? LOG_INFO : LOG_WARNING); + /* + * Processes being killed isn't an error if + * we're only logging because of debugging. + */ + if (signame && sigwords) { + cl_log(logtype + , "Managed %s process %d killed by" + " signal %d [%s - %s]." + , type, pid, signo + , signame, sigwords); + }else{ + cl_log(logtype + , "Managed %s process %d killed by signal %d." + , type, pid, signo); + } + }else{ + cl_log(LOG_ERR, "Managed %s process %d went away" + " strangely (!)" + , type, pid); + } + } +#ifdef WCOREDUMP + if (didcoredump) { + /* We report ALL core dumps without exception */ + cl_log(LOG_ERR, "Managed %s process %d dumped core" + , type, pid); + } +#endif + + if (p) { + RemoveTrackedProcTimeouts(pid); + /* + * From clplumbing/proctrack.h: + * (ProcTrack* p, int status, int signo, int exitcode + * , int waslogged); + */ + p->ops->procdied(p, status, signo, exitcode, doreport); + if (p->privatedata) { + /* They may have forgotten to free something... */ + cl_log(LOG_ERR, "Managed %s process %d did not" + " clean up private data!" + , type, pid); + } + g_hash_table_remove(ProcessTable, GINT_TO_POINTER(pid)); + g_free(p); + } + + return doreport; +} + +/* Return information associated with the given PID (or NULL) */ +ProcTrack* +GetProcInfo(pid_t pid) +{ + return (ProcessTable + ? g_hash_table_lookup(ProcessTable, GINT_TO_POINTER(pid)) + : NULL); +} + +/* "info" is 0-terminated (terminated by a 0 signal) */ +int +SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info) +{ + long mstimeout; + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return 0; + } + + pinfo->timeoutseq = 0; + pinfo->killinfo = info; + mstimeout = pinfo->killinfo[0].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , GINT_TO_POINTER(pid)); + return pinfo->timerid; +} + +void +RemoveTrackedProcTimeouts(pid_t pid) +{ + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return; + } + + if (pinfo->killinfo && pinfo->timerid) { + Gmain_timeout_remove(pinfo->timerid); + } + pinfo->killinfo = NULL; + pinfo->timerid = 0; +} + +static gboolean +TrackedProcTimeoutFunction(gpointer p) +{ + /* This is safe - Pids are relatively small ints */ + pid_t pid = POINTER_TO_SIZE_T(p); /*pointer cast as int*/ + ProcTrack* pinfo; + int nsig; + long mstimeout; + int hadprivs; + + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + cl_log(LOG_ERR, "%s: bad pinfo in call (pid %d)", __FUNCTION__, pid); + return FALSE; + } + if (pinfo->timeoutseq < 0 || pinfo->killinfo == NULL) { + cl_log(LOG_ERR + , "%s: bad call (pid %d): killinfo (%d, 0x%lx)" + , __FUNCTION__, pid + , pinfo->timeoutseq + , (unsigned long)POINTER_TO_SIZE_T(pinfo->killinfo)); + return FALSE; + } + + pinfo->timerid = 0; + nsig = pinfo->killinfo[pinfo->timeoutseq].signalno; + + if (nsig == 0) { + if (CL_PID_EXISTS(pid)) { + cl_log(LOG_ERR + , "%s: %s process (PID %d) will not die!" + , __FUNCTION__ + , pinfo->ops->proctype(pinfo) + , (int)pid); + } + return FALSE; + } + pinfo->timeoutseq++; + cl_log(LOG_WARNING, "%s process (PID %d) timed out (try %d)" + ". Killing with signal %s (%d)." + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , signal_name(nsig, NULL) + , nsig); + + if (pinfo->isapgrp && nsig > 0) { + pid = -pid; + } + + if (!(hadprivs = cl_have_full_privs())) { + return_to_orig_privs(); + } + if (kill(pid, nsig) < 0) { + if (errno == ESRCH) { + /* Mission accomplished! */ + cl_log(LOG_INFO, "%s process (PID %d) died before killing (try %d)" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq); + return FALSE; + }else{ + cl_perror("%s: kill(%d,%d) failed" + , __FUNCTION__, pid, nsig); + } + } + if (!hadprivs) { + return_to_dropped_privs(); + } + mstimeout = pinfo->killinfo[pinfo->timeoutseq].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , p); + if (pinfo->timerid <= 0) { + cl_log(LOG_ERR, "%s: Could not add new kill timer [%u]" + , __FUNCTION__, pinfo->timerid); + kill(pid, SIGKILL); + } + if (debugproctrack) { + cl_log(LOG_DEBUG, "%s process (PID %d) scheduled to be killed again" + " (try %d) in %ld ms [timerid %u]" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , mstimeout + , pinfo->timerid); + } + return FALSE; +} + +/* Helper struct to allow us to stuff 3 args into one ;-) */ +struct prochelper { + ProcTrack_ops* type; + ProcTrackFun fun; + void* data; +}; + +/* Helper function to call user's function with right args... */ +static void +ForEachProcHelper(gpointer key, gpointer value, void * helper) +{ + ProcTrack* p = value; + struct prochelper* ph = helper; + + if (ph->type != NULL && ph->type != p->ops) { + return; + } + + ph->fun(p, ph->data); +} +/* + * Iterate over the set of tracked processes. + * If proctype is NULL, then walk through them all, otherwise only those + * of the given type. + */ +void +ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data) +{ + struct prochelper ph; + + InitProcTable(); + ph.fun = f; + ph.type = proctype; + ph.data = data; + g_hash_table_foreach(ProcessTable, ForEachProcHelper, &ph); +} + +void +DisableProcLogging() +{ + LoggingIsEnabled = 0; +} + +void +EnableProcLogging() +{ + LoggingIsEnabled = 1; +} diff --git a/lib/clplumbing/realtime.c b/lib/clplumbing/realtime.c new file mode 100644 index 0000000..9271204 --- /dev/null +++ b/lib/clplumbing/realtime.c @@ -0,0 +1,354 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stddef.h> +/* The BSD's do not use malloc.h directly. */ +/* They use stdlib.h instead */ +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include <malloc.h> +#endif +#endif +#include <unistd.h> +#ifdef _POSIX_MEMLOCK +# include <sys/mman.h> +# include <sys/time.h> +# include <sys/resource.h> +#endif +#ifdef _POSIX_PRIORITY_SCHEDULING +# include <sched.h> +#endif +#include <string.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <clplumbing/uids.h> +#include <time.h> +#include <errno.h> + +static gboolean cl_realtimepermitted = TRUE; +static void cl_rtmalloc_setup(void); + +#define HOGRET 0xff +/* + * Slightly wacko recursive function to touch requested amount + * of stack so we have it pre-allocated inside our realtime code + * as per suggestion from mlockall(2) + */ +#ifdef _POSIX_MEMLOCK +static unsigned char +cl_stack_hogger(unsigned char * inbuf, int kbytes) +{ + unsigned char buf[1024]; + + if (inbuf == NULL) { + memset(buf, HOGRET, sizeof(buf)); + }else{ + memcpy(buf, inbuf, sizeof(buf)); + } + + if (kbytes > 0) { + return cl_stack_hogger(buf, kbytes-1); + }else{ + return buf[sizeof(buf)-1]; + } +/* #else + return HOGRET; +*/ +} +#endif +/* + * We do things this way to hopefully defeat "smart" malloc code which + * handles large mallocs as special cases using mmap(). + */ +static void +cl_malloc_hogger(int kbytes) +{ + long size = kbytes * 1024; + int chunksize = 1024; + long nchunks = (int)(size / chunksize); + int chunkbytes = nchunks * sizeof(void *); + void** chunks; + int j; + +#ifdef HAVE_MALLOPT +# ifdef M_MMAP_MAX + /* Keep malloc from using mmap */ + mallopt(M_MMAP_MAX, 0); +#endif +# ifdef M_TRIM_THRESHOLD + /* Keep malloc from giving memory back to the system */ + mallopt(M_TRIM_THRESHOLD, -1); +# endif +#endif + chunks=malloc(chunkbytes); + if (chunks == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunkbytes); + return; + } + memset(chunks, 0, chunkbytes); + + for (j=0; j < nchunks; ++j) { + chunks[j] = malloc(chunksize); + if (chunks[j] == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunksize); + }else{ + memset(chunks[j], 0, chunksize); + } + } + for (j=0; j < nchunks; ++j) { + if (chunks[j]) { + free(chunks[j]); + chunks[j] = NULL; + } + } + free(chunks); + chunks = NULL; +} + +/* + * Make us behave like a soft real-time process. + * We need scheduling priority and being locked in memory. + * If you ask us nicely, we'll even grow the stack and heap + * for you before locking you into memory ;-). + */ +void +cl_make_realtime(int spolicy, int priority, int stackgrowK, int heapgrowK) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + int staticp; +#endif + + if (heapgrowK > 0) { + cl_malloc_hogger(heapgrowK); + } + +#ifdef _POSIX_MEMLOCK + if (stackgrowK > 0) { + unsigned char ret; + if ((ret=cl_stack_hogger(NULL, stackgrowK)) != HOGRET) { + cl_log(LOG_INFO, "Stack hogger failed 0x%x" + , ret); + } + } +#endif + cl_rtmalloc_setup(); + + if (!cl_realtimepermitted) { + cl_log(LOG_INFO + , "Request to set pid %ld to realtime ignored." + , (long)getpid()); + return; + } + +#ifdef DEFAULT_REALTIME_POLICY + if (spolicy < 0) { + spolicy = DEFAULT_REALTIME_POLICY; + } + + if (priority <= 0) { + priority = sched_get_priority_min(spolicy); + } + + if (priority > sched_get_priority_max(spolicy)) { + priority = sched_get_priority_max(spolicy); + } + + + if ((staticp=sched_getscheduler(0)) < 0) { + cl_perror("unable to get scheduler parameters."); + }else{ + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = priority; + + if (sched_setscheduler(0, spolicy, &sp) < 0) { + cl_perror("Unable to set scheduler parameters."); + } + } +#endif + +#if defined _POSIX_MEMLOCK +# ifdef RLIMIT_MEMLOCK +# define THRESHOLD(lim) (((lim))/2) + { + unsigned long growsize = ((stackgrowK+heapgrowK)*1024); + struct rlimit memlocklim; + + getrlimit(RLIMIT_MEMLOCK, &memlocklim); /* Allow for future added fields */ + memlocklim.rlim_max = RLIM_INFINITY; + memlocklim.rlim_cur = RLIM_INFINITY; + /* Try and remove memory locking limits -- if we can */ + if (setrlimit(RLIMIT_MEMLOCK, &memlocklim) < 0) { + /* Didn't work - get what we can */ + getrlimit(RLIMIT_MEMLOCK, &memlocklim); + memlocklim.rlim_cur = memlocklim.rlim_max; + setrlimit(RLIMIT_MEMLOCK, &memlocklim); + } + + /* Could we get 'enough' ? */ + /* (this is a guess - might not be right if we're not root) */ + if (getrlimit(RLIMIT_MEMLOCK, &memlocklim) >= 0 + && memlocklim.rlim_cur != RLIM_INFINITY + && (growsize >= THRESHOLD(memlocklim.rlim_cur))) { + cl_log(LOG_ERR + , "Cannot lock ourselves into memory: System limits" + " on locked-in memory are too small."); + return; + } + } +# endif /*RLIMIT_MEMLOCK*/ + if (mlockall(MCL_CURRENT|MCL_FUTURE) >= 0) { + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "pid %d locked in memory.", (int) getpid()); + } + + } else if(errno == ENOSYS) { + const char *err = strerror(errno); + cl_log(LOG_WARNING, "Unable to lock pid %d in memory: %s", + (int) getpid(), err); + + } else { + cl_perror("Unable to lock pid %d in memory", (int) getpid()); + } +#endif +} + +void +cl_make_normaltime(void) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = sched_get_priority_min(SCHED_OTHER); + if (sched_setscheduler(0, SCHED_OTHER, &sp) < 0) { + cl_perror("unable to (re)set scheduler parameters."); + } +#endif +#ifdef _POSIX_MEMLOCK + /* Not strictly necessary. */ + munlockall(); +#endif +} + +void +cl_disable_realtime(void) +{ + cl_realtimepermitted = FALSE; +} + +void +cl_enable_realtime(void) +{ + cl_realtimepermitted = TRUE; +} + +/* Give up the CPU for a little bit */ +/* This is similar to sched_yield() but allows lower prio processes to run */ +int +cl_shortsleep(void) +{ + static const struct timespec req = {0,2000001L}; + + return nanosleep(&req, NULL); +} + + +static int post_rt_morecore_count = 0; +static unsigned long init_malloc_arena = 0L; + +#ifdef HAVE_MALLINFO +# define MALLOC_TOTALSIZE() (((unsigned long)mallinfo().arena)+((unsigned long)mallinfo().hblkhd)) +#else +# define MALLOC_TOTALSIZE() (0L) +#endif + + + +/* Return the number of times we went after more core */ +int +cl_nonrealtime_malloc_count(void) +{ + return post_rt_morecore_count; +} +unsigned long +cl_nonrealtime_malloc_size(void) +{ + return (MALLOC_TOTALSIZE() - init_malloc_arena); +} +/* Log the number of times we went after more core */ +void +cl_realtime_malloc_check(void) +{ + static int lastcount = 0; + static unsigned long oldarena = 0UL; + + if (oldarena == 0UL) { + oldarena = init_malloc_arena; + } + + if (post_rt_morecore_count > lastcount) { + + if (MALLOC_TOTALSIZE() > oldarena) { + + cl_log(LOG_WARNING, + "Performed %d more non-realtime malloc calls.", + post_rt_morecore_count - lastcount); + + cl_log(LOG_INFO, + "Total non-realtime malloc bytes: %ld", + MALLOC_TOTALSIZE() - init_malloc_arena); + oldarena = MALLOC_TOTALSIZE(); + + } + + lastcount = post_rt_morecore_count; + } +} + +#ifdef HAVE___DEFAULT_MORECORE + +static void (*our_save_morecore_hook)(void) = NULL; +static void cl_rtmalloc_morecore_fun(void); + +static void +cl_rtmalloc_morecore_fun(void) +{ + post_rt_morecore_count++; + if (our_save_morecore_hook) { + our_save_morecore_hook(); + } +} +#endif + +static void +cl_rtmalloc_setup(void) +{ + static gboolean inityet = FALSE; + if (!inityet) { + init_malloc_arena = MALLOC_TOTALSIZE(); +#ifdef HAVE___DEFAULT_MORECORE + our_save_morecore_hook = __after_morecore_hook; + __after_morecore_hook = cl_rtmalloc_morecore_fun; + inityet = TRUE; +#endif + } + } diff --git a/lib/clplumbing/replytrack.c b/lib/clplumbing/replytrack.c new file mode 100644 index 0000000..8c7c38e --- /dev/null +++ b/lib/clplumbing/replytrack.c @@ -0,0 +1,643 @@ + +/* + * Reply tracking library. + * + * Copyright (c) 2007 Alan Robertson + * Author: Alan Robertson <alanr@unix.sh> + * + ****************************************************************** + * This library is useful for tracking replies to multicast messages + * sent to cluster members. It tracks incremental membership changes + * according to any desired criteria, and then keeps track of when + * the last expected reply is received according to the dynamically + * updated membership as of when the message was sent out. + ****************************************************************** + * + * 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 <stdlib.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <memory.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/replytrack.h> +#include <clplumbing/Gmain_timeout.h> + +/* + * These are the only data items that go in our GHashTables + */ +struct rt_node_info { + char * nodename; + cl_uuid_t nodeid; +}; + +struct node_tables { + + GHashTable* uuidmap; /* Keyed by uuid */ + int uuidcount; + GHashTable* namemap; /* Keyed by nodename*/ + int namecount; +}; +struct _nodetrack { + struct node_tables nt; + int refcount; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_callback_t extra_callback; + gpointer ext_data; +}; + +/* + * Things we use to track outstanding replies + * This is the structure used by the replytrack_t typedef + */ +struct _replytrack { + replytrack_callback_t callback; + gpointer user_data; + unsigned timerid; + struct node_tables tables; + gboolean expectingmore; + nodetrack_t* membership; +}; + +struct _nodetrack_intersection { + nodetrack_t** tables; + int ntables; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_t* intersection; +}; + +static cl_uuid_t nulluuid; +static int nodetrack_t_count = 0; +static int replytrack_t_count = 0; +static int replytrack_intersection_t_count = 0; + +static struct rt_node_info * +rt_node_info_new(const char * nodename, cl_uuid_t nodeid) +{ + struct rt_node_info* ret; + + if (!nodename) { + return NULL; + } + ret = MALLOCT(struct rt_node_info); + + if (!ret) { + return ret; + } + ret->nodename = strdup(nodename); + if (!ret->nodename) { + free(ret); + ret = NULL; + return ret; + } + ret->nodeid = nodeid; + return ret; +} + +static void +rt_node_info_del(struct rt_node_info * ni) +{ + if (ni != NULL) { + if (ni->nodename != NULL) { + free(ni->nodename); + } + memset(ni, 0, sizeof(*ni)); + free(ni); + } +} + +/* + * namehash cannot be NULL, idhash cannot be NULL, and nodename cannot be NULL + * + * 'id' can be a NULL uuid, in which case it goes into only the name table + * 'nodename' can change over time - in which case we update our tables. + * It is possible for one nodename to have more than one uuid. + * We allow for that. + * + * Changing the uuid but keeping the nodename the same is considered to be + * adding a new node with the same nodename. + * Exception: A node with a null uuid is presumed to have acquired a proper + * uuid if it is later seen with a non-null UUID + */ + +static gboolean +del_node_from_hashtables(struct node_tables *t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info * entry; + if (cl_uuid_is_null(&id)) { + if ((entry = g_hash_table_lookup(t->namemap,nodename))!=NULL){ + g_hash_table_remove(t->namemap, nodename); + rt_node_info_del(entry); + t->namecount--; + } + return TRUE; + } + if ((entry=g_hash_table_lookup(t->uuidmap, &id)) != NULL) { + g_hash_table_remove(t->uuidmap, &id); + rt_node_info_del(entry); + t->uuidcount--; + } + return TRUE; +} + + +static gboolean +add_node_to_hashtables(struct node_tables * t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info* idinfo = NULL; + + if (cl_uuid_is_null(&id)) { + /* Supplied uuid is the null UUID - insert in name table */ + struct rt_node_info* ninfo; + if (g_hash_table_lookup(t->namemap, nodename) == NULL) { + if (NULL == (ninfo = rt_node_info_new(nodename, id))){ + goto outofmem; + } + g_hash_table_insert(t->namemap,ninfo->nodename,ninfo); + t->namecount++; + } + return TRUE; + } + + /* Supplied uuid is not the null UUID */ + + if (g_hash_table_lookup(t->uuidmap,&id) == NULL) { + /* See if a corresponding name is in name map */ + /* If so, delete it - assume uuid was missing before */ + + if (g_hash_table_lookup(t->namemap, nodename) != NULL) { + del_node_from_hashtables(t, nodename, nulluuid); + } + /* Not yet in our uuid hash table */ + idinfo = rt_node_info_new(nodename, id); + if (idinfo == NULL) { + goto outofmem; + } + g_hash_table_insert(t->uuidmap, &idinfo->nodeid, idinfo); + t->uuidcount++; + } + return TRUE; +outofmem: + cl_log(LOG_ERR, "%s: out of memory", __FUNCTION__); + return FALSE; +} + +static gboolean +create_new_hashtables(struct node_tables*t) +{ + t->namemap = g_hash_table_new(g_str_hash, g_str_equal); + if (t->namemap == NULL) { + return FALSE; + } + t->uuidmap = g_hash_table_new(cl_uuid_g_hash, cl_uuid_g_equal); + if (t->uuidmap == NULL) { + g_hash_table_destroy(t->namemap); + t->namemap = NULL; + return FALSE; + } + return TRUE; +} + +static gboolean +hashtable_destroy_rt_node_info(gpointer key, gpointer rti, gpointer unused) +{ + rt_node_info_del(rti); + rti = key = NULL; + return TRUE; +} + +static void +destroy_map_hashtable(GHashTable*t) +{ + g_hash_table_foreach_remove(t, hashtable_destroy_rt_node_info,NULL); + g_hash_table_destroy(t); + t = NULL; +} + +struct tablehelp { + struct node_tables* t; + gboolean ret; +}; + +static void +copy_hashtables_helper(gpointer key_unused, gpointer value +, gpointer user_data) +{ + struct tablehelp * th = user_data; + struct rt_node_info* ni = value; + if (!add_node_to_hashtables(th->t, ni->nodename, ni->nodeid)) { + th->ret = FALSE; + } +} + +static gboolean +copy_hashtables(struct node_tables* tin, struct node_tables* tout) +{ + struct tablehelp newtables; + if (!create_new_hashtables(tout)){ + return FALSE; + } + newtables.t = tout; + newtables.ret = TRUE; + + g_hash_table_foreach(tout->namemap,copy_hashtables_helper,&newtables); + if (!newtables.ret) { + return FALSE; + } + g_hash_table_foreach(tout->uuidmap,copy_hashtables_helper,&newtables); + return newtables.ret; +} + +static gboolean mbr_inityet = FALSE; +static void +init_global_membership(void) +{ + if (mbr_inityet) { + return; + } + mbr_inityet = TRUE; + memset(&nulluuid, 0, sizeof(nulluuid)); +} + +gboolean /* Call us when an expected replier joins / comes up */ +nodetrack_nodeup(nodetrack_t * mbr, const char * node, cl_uuid_t uuid) +{ + gboolean ret; + ret = add_node_to_hashtables(&mbr->nt, node, uuid); + if (ret && mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_UP, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node, uuid, NODET_UP,mbr->ext_data); + } + return ret; +} + +gboolean /* Call us when an expected replier goes down / away */ +nodetrack_nodedown(nodetrack_t* mbr, const char* node, cl_uuid_t uuid) +{ + if (mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_DOWN, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node,uuid,NODET_DOWN,mbr->ext_data); + } + return del_node_from_hashtables(&mbr->nt, node, uuid); +} + +/* This function calls the user's timeout callback */ +static gboolean +replytrack_timeout_helper(gpointer rldata) +{ + replytrack_t* rl = rldata; + rl->expectingmore = FALSE; + rl->timerid = 0; + if (rl->callback) { + rl->callback(rl, rl->user_data, REPLYT_TIMEOUT); + } + return FALSE; +} + +replytrack_t* /* replytrack_t constructor */ +replytrack_new(nodetrack_t * membership +, replytrack_callback_t callback +, unsigned long timeout_ms +, gpointer user_data) +{ + replytrack_t* ret = MALLOCT(replytrack_t); + if (!ret) { + return ret; + } + if (!copy_hashtables(&membership->nt, &ret->tables)) { + free(ret); + ret = NULL; + return ret; + } + replytrack_t_count++; + ret->membership = membership; + ret->membership->refcount++; + ret->callback = callback; + ret->user_data = user_data; + ret->expectingmore = TRUE; + ret->timerid = 0; + if (timeout_ms != 0 && callback != NULL) { + ret->timerid = Gmain_timeout_add(timeout_ms + , replytrack_timeout_helper, ret); + } + return ret; +} + +void /* replytrack_t destructor */ +replytrack_del(replytrack_t * rl) +{ + rl->membership->refcount--; + replytrack_t_count++; + if (rl->expectingmore && rl->timerid > 0) { + cl_log(LOG_INFO + , "%s: destroying replytrack while still expecting" + " %d replies" + , __FUNCTION__ + , (rl->tables.namecount + rl->tables.uuidcount)); + } + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + destroy_map_hashtable(rl->tables.namemap); + rl->tables.namemap=NULL; + destroy_map_hashtable(rl->tables.uuidmap); + rl->tables.uuidmap=NULL; + memset(&rl, 0, sizeof(rl)); + free(rl); + rl=NULL; +} + +gboolean /* Call replytrack_gotreply when you receive an expected reply */ +replytrack_gotreply(replytrack_t*rl, const char * node, cl_uuid_t uuid) +{ + gboolean lastone; + del_node_from_hashtables(&rl->tables, node, uuid); + lastone = (rl->tables.namecount + rl->tables.uuidcount) == 0; + if (lastone) { + rl->expectingmore = FALSE; + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + if (rl->callback){ + rl->callback(rl, rl->user_data, REPLYT_ALLRCVD); + } + } + return lastone; +} + +struct replytrack_iterator_data { + replytrack_t* rlist; + replytrack_iterator_t f; + int count; + gpointer user_data; +}; + + +static void /* g_hash_table user-level iteration helper */ +replytrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct replytrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + + + +int /* iterate through the outstanding expected replies */ +replytrack_outstanding_iterate(replytrack_t* rl +, replytrack_iterator_t i, gpointer user_data) +{ + struct replytrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->tables.namemap, replytrack_iterator_helper + , &id); + g_hash_table_foreach(rl->tables.uuidmap, replytrack_iterator_helper + , &id); + if (id.count != (rl->tables.namecount + rl->tables.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->tables.namecount,rl->tables.uuidcount); + } + return id.count; +} +int /* return count of outstanding expected replies */ +replytrack_outstanding_count(replytrack_t* rl) +{ + return (rl->tables.namecount + rl->tables.uuidcount); +} + +nodetrack_t* +nodetrack_new(nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_t* ret = MALLOCT(nodetrack_t); + if (!mbr_inityet) { + init_global_membership(); + } + if (!ret) { + return ret; + } + nodetrack_t_count++; + ret->refcount = 0; + if (!create_new_hashtables(&ret->nt)) { + free(ret); + ret = NULL; + } + ret->user_data = user_data; + ret->callback = callback; + ret->extra_callback = NULL; + ret->ext_data = NULL; + return ret; +} +void +nodetrack_del(nodetrack_t * np) +{ + if (np->refcount) { + cl_log(LOG_ERR + , "%s: reply tracking reference count is %d" + , __FUNCTION__, np->refcount); + } + nodetrack_t_count--; + destroy_map_hashtable(np->nt.namemap); + np->nt.namemap=NULL; + destroy_map_hashtable(np->nt.uuidmap); + np->nt.uuidmap=NULL; + memset(np, 0, sizeof(*np)); + free(np); +} + +gboolean +nodetrack_ismember(nodetrack_t* mbr, const char * name, cl_uuid_t u) +{ + if (cl_uuid_is_null(&u)) { + return(g_hash_table_lookup(mbr->nt.namemap, name) != NULL); + } + return (g_hash_table_lookup(mbr->nt.uuidmap, &u) != NULL); +} + +struct nodetrack_iterator_data { + nodetrack_t* rlist; + nodetrack_iterator_t f; + int count; + gpointer user_data; +}; +static void /* g_hash_table user-level iteration helper */ +nodetrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct nodetrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + +int /* iterate through the outstanding expected replies */ +nodetrack_iterate(nodetrack_t* rl +, nodetrack_iterator_t i, gpointer user_data) +{ + struct nodetrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->nt.namemap, nodetrack_iterator_helper + , &id); + g_hash_table_foreach(rl->nt.uuidmap, nodetrack_iterator_helper + , &id); + if (id.count != (rl->nt.namecount + rl->nt.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->nt.namecount,rl->nt.uuidcount); + } + return id.count; +} +static void +intersection_callback +( nodetrack_t * mbr +, const char * node +, cl_uuid_t u +, nodetrack_change_t reason +, gpointer user_data) +{ + nodetrack_intersection_t* it = user_data; + int j; + gboolean allfound = TRUE; + + if (reason == NODET_DOWN) { + if (nodetrack_ismember(it->intersection, node, u)) { + nodetrack_nodedown(it->intersection,node,u); + } + return; + } + for (j=0; j < it->ntables && allfound; ++j) { + if (nodetrack_ismember(it->tables[j], node, u)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(it->intersection, node, u); + } +} + +struct li_helper { + nodetrack_intersection_t* i; + gboolean result; +}; + +static void +intersection_init_iterator(nodetrack_t* nt +, gpointer ghelp +, const char* node +, cl_uuid_t uuid) +{ + struct li_helper* help = ghelp; + gboolean allfound = TRUE; + int j; + + for (j=1; allfound && j < help->i->ntables; ++j) { + if (!nodetrack_ismember(help->i->tables[j] + , node, uuid)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(help->i->intersection, node, uuid); + } +} + +nodetrack_intersection_t* +nodetrack_intersection_new(nodetrack_t** tables, int ntables +, nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_intersection_t* ret; + int j; + ret = MALLOCT(nodetrack_intersection_t); + if (!ret) { + return ret; + } + ret->intersection = nodetrack_new(callback, user_data); + if (!ret->intersection) { + free(ret); + ret = NULL; + return ret; + } + ret->tables = tables; + ret->ntables = ntables; + ret->callback = callback; + ret->user_data = user_data; + for (j=0; j < ntables; ++j) { + tables[j]->refcount ++; + tables[j]->ext_data = ret; + tables[j]->extra_callback = intersection_callback; + } + /* Initialize the intersection membership list */ + nodetrack_iterate(tables[0], intersection_init_iterator, ret); + replytrack_intersection_t_count++; + return ret; +} +void +nodetrack_intersection_del(nodetrack_intersection_t* p) +{ + int j; + + for (j=0; j < p->ntables; ++j) { + p->tables[j]->refcount ++; + } + nodetrack_del(p->intersection); + p->intersection = NULL; + memset(p, 0, sizeof(*p)); + free(p); + p = NULL; + replytrack_intersection_t_count--; +} + +nodetrack_t* +nodetrack_intersection_table(nodetrack_intersection_t*p) +{ + return p->intersection; +} diff --git a/lib/clplumbing/setproctitle.c b/lib/clplumbing/setproctitle.c new file mode 100644 index 0000000..ffc5481 --- /dev/null +++ b/lib/clplumbing/setproctitle.c @@ -0,0 +1,235 @@ +/* + * setproctitle.c + * + * The code in this file, setproctitle.c is heavily based on code from + * proftpd, please see the licening information below. + * + * This file added to the heartbeat tree by Horms <horms@vergenet.net> + * + * Code to portably change the title of a programme as displayed + * by ps(1). + * + * heartbeat: Linux-HA heartbeat code + * + * Copyright (C) 1999,2000,2001 Alan Robertson <alanr@unix.sh> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu + * and other respective copyright holders give permission to link this program + * with OpenSSL, and distribute the resulting executable, without including + * the source code for OpenSSL in the source distribution. + */ + +#include <lha_internal.h> + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <stdarg.h> + +#define PF_ARGV_NONE 0 +#define PF_ARGV_NEW 1 +#define PF_ARGV_WRITEABLE 2 +#define PF_ARGV_PSTAT 3 +#define PF_ARGV_PSSTRINGS 4 + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT +# include <pstat.h> +#endif + +#include <clplumbing/setproctitle.h> + +#if PF_ARGV_TYPE != PF_ARGV_NONE +static char **Argv = NULL; +static char *LastArgv = NULL; +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ + +extern char **environ; + +#ifdef HAVE___PROGNAME +extern char *__progname; +extern char *__progname_full; +#endif /* HAVE___PROGNAME */ + +int +init_set_proc_title(int argc, char *argv[], char *envp[]) +{ +#if PF_ARGV_TYPE == PF_ARGV_NONE + return 0; +#else + int i; + int envpsize; + char **p; + + /* Move the environment so setproctitle can use the space. + */ + for(i = envpsize = 0; envp[i] != NULL; i++) { + envpsize += strlen(envp[i]) + 1; + } + + p = (char **) malloc((i + 1) * sizeof(char *)); + if (p == NULL) { + return -1; + } + + environ = p; + + for(i = 0; envp[i] != NULL; i++) { + environ[i] = strdup(envp[i]); + if(environ[i] == NULL) { + goto error_environ; + } + } + environ[i] = NULL; + + Argv = argv; + + for(i = 0; i < argc; i++) { + if(!i || (LastArgv + 1 == argv[i])) + LastArgv = argv[i] + strlen(argv[i]); + } + + for(i = 0; envp[i] != NULL; i++) { + if((LastArgv + 1) == envp[i]) { + LastArgv = envp[i] + strlen(envp[i]); + } + } + +#ifdef HAVE___PROGNAME + /* Set the __progname and __progname_full variables so glibc and + * company don't go nuts. - MacGyver + */ + + __progname = strdup("heartbeat"); + if (__progname == NULL) { + goto error_environ; + } + __progname_full = strdup(argv[0]); + if (__progname_full == NULL) { + goto error_environ; + } +#endif /* HAVE___PROGNAME */ + + return 0; + +error_environ: + for(i = 0; environ[i] != NULL; i++) { + free(environ[i]); + } + free(environ); + return -1; +#endif /* PF_ARGV_TYPE == PF_ARGV_NONE */ +} + +void set_proc_title(const char *fmt,...) +{ +#if PF_ARGV_TYPE != PF_ARGV_NONE + va_list msg; + static char statbuf[BUFSIZ]; + +#ifndef HAVE_SETPROCTITLE +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + union pstun pst; +#endif /* PF_ARGV_PSTAT */ + int i,maxlen = (LastArgv - Argv[0]) - 2; + char *p; +#endif /* HAVE_SETPROCTITLE */ + + va_start(msg,fmt); + + memset(statbuf, 0, sizeof(statbuf)); + + +#ifdef HAVE_SETPROCTITLE +# if __FreeBSD__ >= 4 && !defined(FREEBSD4_0) && !defined(FREEBSD4_1) + /* FreeBSD's setproctitle() automatically prepends the process name. */ + vsnprintf(statbuf, sizeof(statbuf), fmt, msg); + +# else /* FREEBSD4 */ + /* Manually append the process name for non-FreeBSD platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +# endif /* FREEBSD4 */ + setproctitle("%s", statbuf); + +#else /* HAVE_SETPROCTITLE */ + /* Manually append the process name for non-setproctitle() platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +#endif /* HAVE_SETPROCTITLE */ + + va_end(msg); + +#ifdef HAVE_SETPROCTITLE + return; +#else + i = strlen(statbuf); + +#if PF_ARGV_TYPE == PF_ARGV_NEW + /* We can just replace argv[] arguments. Nice and easy. + */ + Argv[0] = statbuf; + Argv[1] = NULL; +#endif /* PF_ARGV_NEW */ + +#if PF_ARGV_TYPE == PF_ARGV_WRITEABLE + /* We can overwrite individual argv[] arguments. Semi-nice. + */ + snprintf(Argv[0], maxlen, "%s", statbuf); + p = &Argv[0][i]; + + while(p < LastArgv) + *p++ = '\0'; + Argv[1] = NULL; +#endif /* PF_ARGV_WRITEABLE */ + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + pst.pst_command = statbuf; + pstat(PSTAT_SETCMD, pst, i, 0, 0); +#endif /* PF_ARGV_PSTAT */ + +#if PF_ARGV_TYPE == PF_ARGV_PSSTRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = statbuf; +#endif /* PF_ARGV_PSSTRINGS */ + +#endif /* HAVE_SETPROCTITLE */ + +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ +} diff --git a/lib/clplumbing/timers.c b/lib/clplumbing/timers.c new file mode 100644 index 0000000..c3e99da --- /dev/null +++ b/lib/clplumbing/timers.c @@ -0,0 +1,119 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <sys/time.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <clplumbing/timers.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/longclock.h> + +int +setmsrepeattimer(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {secs, usecs} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + +#if 0 + cl_log(LOG_DEBUG, "Setting repeating timer for %ld ms" + , ms); +#endif + + + /* Is this right??? */ + CL_IGNORE_SIG(SIGALRM); + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +setmsalarm(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +cancelmstimer(void) +{ + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {0L, 0L} /* Timer Value */ + }; + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + + +static int alarmpopped = 0; + +static void +st_timer_handler(int nsig) +{ + ++alarmpopped; +} + +/* + * Pretty simple: + * 1) Set up SIGALRM signal handler + * 2) set alarmpopped to FALSE; + * 2) Record current time + * 3) Call setmsalarm(ms) + * 4) Call pause(2) + * 5) Call cancelmstimer() + * 6) Reset signal handler + * 7) See if SIGALRM happened + * if so: return zero + * if not: get current time, and compute milliseconds left 'til signal + * should arrive, and return that... + */ +long +mssleep(long ms) +{ + struct sigaction saveaction; + longclock_t start; + longclock_t finish; + unsigned long elapsedms; + + memset(&saveaction, 0, sizeof(saveaction)); + + cl_signal_set_simple_handler(SIGALRM, st_timer_handler, &saveaction); + alarmpopped = 0; + start = time_longclock(); + setmsalarm(ms); + pause(); + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + if (alarmpopped) { + return 0; + } + + finish = time_longclock(); + elapsedms = longclockto_ms(sub_longclock(finish, start)); + return ms - elapsedms; +} diff --git a/lib/clplumbing/transient-test.sh b/lib/clplumbing/transient-test.sh new file mode 100755 index 0000000..7da88bf --- /dev/null +++ b/lib/clplumbing/transient-test.sh @@ -0,0 +1,120 @@ +#!/bin/sh +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +####FIXME +# Known problems within this testing: +# 1. Doesn't reflect "someone else's message" problems into error count. +# 2. Path to "ipctransient{client,server}" not flexible enough. + +no_logs=0 +exit_on_error=1 +num_servers=10 +num_clients=10 +client_time=2 +commpath_args="" + +cmd=`basename $0` +USAGE="Usage: $cmd [-c clients] [-s servers] [-t timetowait] [-C commpath]" + +while getopts c:s:C:t: c +do + case $c in + c) + num_clients=$OPTARG + ;; + s) + num_servers=$OPTARG + ;; + t) + client_time=$OPTARG + ;; + C) + commpath_args="-$c $OPTARG" + ;; + \?) + echo $USAGE + exit 2 + ;; + esac +done +shift `expr $OPTIND - 1` + +total_failed=0 + +server_failed=0 +server_loop_cnt=0 +while [ $server_loop_cnt != $num_servers ]; do + echo "############ DEBUG: Starting server iter $server_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientserver $commpath_args > /dev/null 2>&1 & + else + ./ipctransientserver $commpath_args & + fi + server_pid=$! + + sleep 5 + + client_failed=0 + client_loop_cnt=0 + while [ $client_loop_cnt != $num_clients ]; do + sleep 5 + echo "############ DEBUG: Starting client iter $client_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientclient $commpath_args > /dev/null 2>&1 & + else + ./ipctransientclient $commpath_args & + fi + client_pid=$! + sleep $client_time + if [ $exit_on_error = 1 ];then + kill -0 $client_pid > /dev/null 2>&1 + else + kill -9 $client_pid > /dev/null 2>&1 + fi + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Iter $client_loop_cnt failed to receive all messages" + client_failed=`expr $client_failed + 1` + if [ $exit_on_error = 1 ];then + echo "terminating after first error..." + exit 0 + fi + else + echo "############ INFO: Iter $client_loop_cnt passed" + fi + + client_loop_cnt=`expr $client_loop_cnt + 1`; + done + server_loop_cnt=`expr $server_loop_cnt + 1`; + total_failed=`expr $total_failed + $client_failed` + kill -9 $server_pid > /dev/null 2>&1 + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Server was already dead" + server_failed=`expr $server_failed + 1` + fi +done + +total_failed=`expr $total_failed + $server_failed` + +if [ $total_failed = 0 ]; then + echo "INFO: All tests passed" +else + echo "ERROR: $total_failed tests failed" +fi + +exit $total_failed diff --git a/lib/clplumbing/uids.c b/lib/clplumbing/uids.c new file mode 100644 index 0000000..0727e1d --- /dev/null +++ b/lib/clplumbing/uids.c @@ -0,0 +1,140 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <pwd.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <clplumbing/uids.h> +#include <clplumbing/coredumps.h> +#define NOBODY "nobody" + +#if defined(HAVE_SETEUID) && defined(HAVE_SETEGID) && \ + defined(_POSIX_SAVED_IDS) +# define CAN_DROP_PRIVS 1 + +#endif + + + + +#ifndef CAN_DROP_PRIVS + int drop_privs(uid_t uid, gid_t gid) { return 0; } + int return_to_orig_privs(void) { return 0; } + int return_to_dropped_privs(void) { return 0; } + int cl_have_full_privs(void) { return 0; } +#else + +static int anysaveduid = 0; +static uid_t nobodyuid=-1; +static gid_t nobodygid=-1; +static uid_t poweruid=-1; +static gid_t powergid=-1; +static int privileged_state = 1; + +/* WARNING: uids are unsigned! */ +#define WANT_NOBODY(uid) ((uid) == 0) + +int /* Become nobody - and remember our original privileges */ +drop_privs(uid_t uid, gid_t gid) +{ + int rc; + gid_t curgid = getgid(); + + if (!anysaveduid) { + poweruid=getuid(); + powergid=curgid; + } + + if (WANT_NOBODY(uid)) { + struct passwd* p; + + p = getpwnam(NOBODY); + + if (p == NULL) { + return -1; + } + uid = p->pw_uid; + gid = p->pw_gid; + } + if (setegid(gid) < 0) { + return -1; + } + rc = seteuid(uid); + + if (rc >= 0) { + anysaveduid = 1; + nobodyuid=uid; + nobodygid=gid; + privileged_state = 0; + }else{ + /* Attempt to recover original privileges */ + int err = errno; + setegid(curgid); + errno = err; + } + cl_untaint_coredumps(); + return rc; +} + +int /* Return to our original privileges (if any) */ +return_to_orig_privs(void) +{ + int rc; + if (!anysaveduid) { + return 0; + } + if (seteuid(poweruid) < 0) { + return -1; + } + privileged_state = 1; + rc = setegid(powergid); + /* + * Sad but true, for security reasons we can't call + * cl_untaint_coredumps() here - because it might cause an + * leak of confidential information for some applications. + * So, the applications need to use either cl_untaint_coredumps() + * when they change privileges, or they need to call + * cl_set_all_coredump_signal_handlers() to handle core dump + * signals and set their privileges to maximum before core + * dumping. See the comments in coredumps.c for more details. + */ + return rc; +} + +int /* Return to "nobody" level of privs (if any) */ +return_to_dropped_privs(void) +{ + int rc; + + if (!anysaveduid) { + return 0; + } + setegid(nobodygid); + privileged_state = 0; + rc = seteuid(nobodyuid); + /* See note above about dumping core */ + return rc; +} + +/* Return TRUE if we have full privileges at the moment */ +int +cl_have_full_privs(void) +{ + return privileged_state != 0; +} +#endif diff --git a/lib/lrm/Makefile.am b/lib/lrm/Makefile.am new file mode 100644 index 0000000..815f92f --- /dev/null +++ b/lib/lrm/Makefile.am @@ -0,0 +1,36 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +lrmdir = $(localstatedir)/lib/heartbeat/lrm +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(GLIBLIB) + +lib_LTLIBRARIES = liblrm.la +liblrm_la_SOURCES = lrm_msg.c clientlib.c racommon.c +liblrm_la_LDFLAGS = -version-info 2:0:0 $(COMMONLIBS) +liblrm_la_CFLAGS = $(INCLUDES) + +install-exec-local: + $(mkinstalldirs) $(DESTDIR)$(lrmdir) + -chgrp $(GLUE_DAEMON_GROUP) $(DESTDIR)/$(lrmdir) + chmod 770 $(DESTDIR)/$(lrmdir) diff --git a/lib/lrm/clientlib.c b/lib/lrm/clientlib.c new file mode 100644 index 0000000..78dcdc8 --- /dev/null +++ b/lib/lrm/clientlib.c @@ -0,0 +1,1612 @@ +/* + * Client Library for Local Resource Manager API. + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * 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 <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include <glib.h> +#include <clplumbing/ipc.h> +#include <ha_msg.h> +#include <lrm/lrm_api.h> + +#include <lrm/lrm_msg.h> + +/* FIXME: Notice: this define should be replaced when merge to the whole pkg*/ +#define LRM_MAXPIDLEN 256 +#define LRM_ID "lrm" + +#define LOG_FAIL_create_lrm_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_create_lrm_rsc_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_rsc_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_receive_reply(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to receive a reply message of %s." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_SEND_MSG(msg_type, chan_name) \ + cl_log(LOG_ERR, "%s(%d): failed to send a %s message to lrmd " \ + "via %s channel." \ + , __FUNCTION__, __LINE__, msg_type, chan_name) + +#define LOG_GOT_FAIL_RET(priority, msg_type) \ + cl_log(priority, "%s(%d): got a return code HA_FAIL from " \ + "a reply message of %s with function get_ret_from_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed." \ + , __FUNCTION__, __LINE__, apiname) + +#define LOG_FAIL_GET_MSG_FIELD(priority, field_name, msg) \ + {cl_log(priority, "%s(%d): failed to get the value " \ + "of field %s from a ha_msg" \ + , __FUNCTION__, __LINE__, field_name); \ + cl_log(LOG_INFO, "%s: Message follows:", __FUNCTION__); \ + cl_log_message(LOG_INFO, (msg)); \ + } + +/* declare the functions used by the lrm_ops structure*/ +static int lrm_signon (ll_lrm_t* lrm, const char * app_name); +static int lrm_signoff (ll_lrm_t*); +static int lrm_delete (ll_lrm_t*); +static int lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func); +static GList* lrm_get_rsc_class_supported (ll_lrm_t* lrm); +static GList* lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* class); +static GList* lrm_get_rsc_provider_supported (ll_lrm_t* lrm + ,const char* class, const char* type); +static char* lrm_get_rsc_type_metadata(ll_lrm_t* lrm, const char* class + ,const char* type, const char* provider); +static GHashTable* lrm_get_all_type_metadata(ll_lrm_t*, const char* class); +static GList* lrm_get_all_rscs (ll_lrm_t* lrm); +static lrm_rsc_t* lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id); +static int lrm_add_rsc (ll_lrm_t*, const char* id, const char* class + ,const char* type, const char* provider + ,GHashTable* parameter); +static int lrm_delete_rsc (ll_lrm_t*, const char* id); +static int lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc + ,const char* fail_reason); +static int lrm_set_lrmd_param (ll_lrm_t* lrm, const char* name, const char *value); +static char* lrm_get_lrmd_param (ll_lrm_t* lrm, const char* name); +static IPC_Channel* lrm_ipcchan (ll_lrm_t*); +static int lrm_msgready (ll_lrm_t*); +static int lrm_rcvmsg (ll_lrm_t*, int blocking); +static struct lrm_ops lrm_ops_instance = +{ + lrm_signon, + lrm_signoff, + lrm_delete, + lrm_set_lrm_callback, + lrm_set_lrmd_param, + lrm_get_lrmd_param, + lrm_get_rsc_class_supported, + lrm_get_rsc_type_supported, + lrm_get_rsc_provider_supported, + lrm_get_rsc_type_metadata, + lrm_get_all_type_metadata, + lrm_get_all_rscs, + lrm_get_rsc, + lrm_add_rsc, + lrm_delete_rsc, + lrm_fail_rsc, + lrm_ipcchan, + lrm_msgready, + lrm_rcvmsg +}; +/* declare the functions used by the lrm_rsc_ops structure*/ +static int rsc_perform_op (lrm_rsc_t*, lrm_op_t* op); +static int rsc_cancel_op (lrm_rsc_t*, int call_id); +static int rsc_flush_ops (lrm_rsc_t*); +static GList* rsc_get_cur_state (lrm_rsc_t*, state_flag_t* cur_state); +static lrm_op_t* rsc_get_last_result (lrm_rsc_t*, const char* op_type); +static gint compare_call_id(gconstpointer a, gconstpointer b); + +static struct rsc_ops rsc_ops_instance = +{ + rsc_perform_op, + rsc_cancel_op, + rsc_flush_ops, + rsc_get_cur_state, + rsc_get_last_result +}; + + +/* define the internal data used by the client library*/ +static int is_signed_on = FALSE; +static IPC_Channel* ch_cmd = NULL; +static IPC_Channel* ch_cbk = NULL; +static lrm_op_done_callback_t op_done_callback = NULL; + +/* define some utility functions*/ +static int get_ret_from_ch(IPC_Channel* ch); +static int get_ret_from_msg(struct ha_msg* msg); +static struct ha_msg* op_to_msg (lrm_op_t* op); +static lrm_op_t* msg_to_op(struct ha_msg* msg); +static void free_op (lrm_op_t* op); + +/* define of the api functions*/ +ll_lrm_t* +ll_lrm_new (const char * llctype) +{ + ll_lrm_t* lrm; + + /* check the parameter*/ + if (0 != STRNCMP_CONST(llctype, LRM_ID)) { + cl_log(LOG_ERR, "ll_lrm_new: wrong parameter"); + return NULL; + } + + /* alloc memory for lrm*/ + if (NULL == (lrm = (ll_lrm_t*) g_new(ll_lrm_t,1))) { + cl_log(LOG_ERR, "ll_lrm_new: can not allocate memory"); + return NULL; + } + /* assign the ops*/ + lrm->lrm_ops = &lrm_ops_instance; + + return lrm; +} + +static int +lrm_signon (ll_lrm_t* lrm, const char * app_name) +{ + + GHashTable* ch_cmd_attrs; + GHashTable* ch_cbk_attrs; + + struct ha_msg* msg; + + char path[] = IPC_PATH_ATTR; + char cmd_path[] = LRM_CMDPATH; + char callback_path[] = LRM_CALLBACKPATH; + + /* check parameters*/ + if (NULL == lrm || NULL == app_name) { + cl_log(LOG_ERR, "lrm_signon: wrong parameter"); + return HA_FAIL; + } + + /* if already signed on, sign off first*/ + if (is_signed_on) { + cl_log(LOG_WARNING, + "lrm_signon: the client is alreay signed on, re-sign"); + lrm_signoff(lrm); + } + + /* create the command ipc channel to lrmd*/ + ch_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cmd_attrs, path, cmd_path); + ch_cmd = ipc_channel_constructor(IPC_ANYTYPE, ch_cmd_attrs); + g_hash_table_destroy(ch_cmd_attrs); + + if (NULL == ch_cmd){ + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not connect to lrmd for cmd channel"); + return HA_FAIL; + } + + if (IPC_OK != ch_cmd->ops->initiate_connection(ch_cmd)) { + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not initiate connection"); + return HA_FAIL; + } + + /* construct the reg msg*/ + if (NULL == (msg = create_lrm_reg_msg(app_name))) { + lrm_signoff(lrm); + cl_log(LOG_ERR,"lrm_signon: failed to create a register message"); + return HA_FAIL; + } + + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cmd"); + return HA_FAIL; + } + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + ha_msg_del(msg); + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + + /* create the callback ipc channel to lrmd*/ + ch_cbk_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cbk_attrs, path, callback_path); + ch_cbk = ipc_channel_constructor(IPC_ANYTYPE,ch_cbk_attrs); + g_hash_table_destroy(ch_cbk_attrs); + + if (NULL == ch_cbk) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, "lrm_signon: failed to construct a callback " + "channel to lrmd"); + return HA_FAIL; + } + + if (IPC_OK != ch_cbk->ops->initiate_connection(ch_cbk)) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, + "lrm_signon: failed to initiate the callback channel."); + return HA_FAIL; + } + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cbk)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cbk"); + return HA_FAIL; + } + ha_msg_del(msg); + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cbk)) { + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + /* ok, we sign on sucessfully now*/ + is_signed_on = TRUE; + return HA_OK; +} + +static int +lrm_signoff (ll_lrm_t* lrm) +{ + /* close channels */ + if (NULL != ch_cmd) { + if (IPC_ISWCONN(ch_cmd)) { + ch_cmd->ops->destroy(ch_cmd); + } + ch_cmd = NULL; + } + if (NULL != ch_cbk) { + if (IPC_ISWCONN(ch_cbk)) { + ch_cbk->ops->destroy(ch_cbk); + } + ch_cbk = NULL; + } + is_signed_on = FALSE; + + return HA_OK; +} + +static int +lrm_delete (ll_lrm_t* lrm) +{ + /* check the parameter */ + if (NULL == lrm) { + cl_log(LOG_ERR,"lrm_delete: the parameter is a null pointer."); + return HA_FAIL; + } + g_free(lrm); + + return HA_OK; +} + +static int +lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func) + +{ + op_done_callback = op_done_callback_func; + + return HA_OK; +} + +static GList* +lrm_get_rsc_class_supported (ll_lrm_t* lrm) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* class_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_class_supported: ch_cmd is a null pointer."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCCLASSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCCLASSES); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCCLASSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCCLASSES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_WARNING, GETRSCCLASSES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + class_list = ha_msg_value_str_list(ret,F_LRM_RCLASS); + + ha_msg_del(ret); + + return class_list; +} +static GList* +lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* rclass) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* type_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, "%s(%d): ch_cmd is null." + , __FUNCTION__, __LINE__); + + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCTYPES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCTYPES); + return NULL; + } + if ( HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCTYPES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCTYPES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCTYPES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + type_list = ha_msg_value_str_list(ret,F_LRM_RTYPES); + + ha_msg_del(ret); + + return type_list; +} +static GList* +lrm_get_rsc_provider_supported (ll_lrm_t* lrm, const char* class, const char* type) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* provider_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_provider_supported: ch_mod is null."); + return NULL; + } + /* create the get ra providers message */ + msg = create_lrm_msg(GETPROVIDERS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETPROVIDERS); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETPROVIDERS, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETPROVIDERS); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETPROVIDERS); + ha_msg_del(ret); + return NULL; + } + /* get the ra provider list from message */ + provider_list = ha_msg_value_str_list(ret,F_LRM_RPROVIDERS); + + ha_msg_del(ret); + + return provider_list; +} + +/* + * lrm_get_all_type_metadatas(): + * The key of the hash table is in the format "type:provider" + * The value of the hash table is the metadata. + */ +static GHashTable* +lrm_get_all_type_metadata (ll_lrm_t* lrm, const char* rclass) +{ + GHashTable* metas = g_hash_table_new_full(g_str_hash, g_str_equal + , g_free, g_free); + GList* types = lrm_get_rsc_type_supported (lrm, rclass); + GList* providers = NULL; + GList* cur_type = NULL; + GList* cur_provider = NULL; + + cur_type = g_list_first(types); + while (cur_type != NULL) + { + const char* type; + char key[MAXLENGTH]; + type = (const char*) cur_type->data; + providers = lrm_get_rsc_provider_supported(lrm, rclass, type); + cur_provider = g_list_first(providers); + while (cur_provider != NULL) { + const char* meta; + const char* provider; + provider = (const char*) cur_provider->data; + meta = lrm_get_rsc_type_metadata(lrm,rclass,type,provider); + if (NULL == meta) { + cur_provider = g_list_next(cur_provider); + continue; + } + snprintf(key,MAXLENGTH, "%s:%s",type,provider); + key[MAXLENGTH-1]='\0'; + g_hash_table_insert(metas,g_strdup(key),g_strdup(meta)); + cur_provider = g_list_next(cur_provider); + } + lrm_free_str_list(providers); + cur_type=g_list_next(cur_type); + } + lrm_free_str_list(types); + return metas; +} + +static char* +lrm_get_rsc_type_metadata (ll_lrm_t* lrm, const char* rclass, const char* rtype, + const char* provider) +{ + struct ha_msg* msg; + struct ha_msg* ret; + const char* tmp = NULL; + char* metadata = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_type_metadata: ch_mod is null."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCMETA); + if (NULL == msg ) { + LOG_FAIL_create_lrm_msg(GETRSCMETA); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, rtype)){ + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCMETA, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCMETA); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCMETA); + ha_msg_del(ret); + return NULL; + } + + /* get the metadata from message */ + tmp = cl_get_string(ret, F_LRM_METADATA); + if (NULL!=tmp) { + metadata = g_strdup(tmp); + } + ha_msg_del(ret); + + return metadata; +} + +static GList* +lrm_get_all_rscs (ll_lrm_t* lrm) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + GList* rid_list = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_all_rscs: ch_mod is null."); + return NULL; + } + /* create the msg of get all resource */ + msg = create_lrm_msg(GETALLRCSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETALLRCSES); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETALLRCSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETALLRCSES); + return NULL; + } + /* get the return code of msg */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETALLRCSES); + ha_msg_del(ret); + return NULL; + } + /* get the rsc_id list from msg */ + rid_list = ha_msg_value_str_list(ret,F_LRM_RID); + + ha_msg_del(ret); + /* return the id list */ + return rid_list; + +} + +static lrm_rsc_t* +lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_rsc_t* rsc = NULL; + + /* check whether the rsc_id is available */ + if (strlen(rsc_id) >= RID_LEN) { + cl_log(LOG_ERR, "lrm_get_rsc: rsc_id is too long."); + return NULL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_rsc_msg(rsc_id, GETRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSC); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSC, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSC); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + /* create a new resource structure */ + rsc = g_new(lrm_rsc_t, 1); + + /* fill the field of resource with the data from msg */ + rsc->id = g_strdup(ha_msg_value(ret, F_LRM_RID)); + rsc->type = g_strdup(ha_msg_value(ret, F_LRM_RTYPE)); + rsc->class = g_strdup(ha_msg_value(ret, F_LRM_RCLASS)); + rsc->provider = g_strdup(ha_msg_value(ret, F_LRM_RPROVIDER)); + rsc->params = ha_msg_value_str_table(ret,F_LRM_PARAM); + + rsc->ops = &rsc_ops_instance; + ha_msg_del(ret); + /* return the new resource */ + return rsc; +} + +static int +lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc +, const char* fail_reason) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "%s: wrong parameter rsc_id.", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_rsc_msg(rsc_id,FAILRSC); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(FAILRSC); + return HA_FAIL; + } + if ((fail_reason && HA_OK != ha_msg_add(msg,F_LRM_FAIL_REASON,fail_reason)) + || HA_OK != ha_msg_add_int(msg, F_LRM_ASYNCMON_RC, fail_rc) + ) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_set_lrmd_param(ll_lrm_t* lrm, const char* name, const char *value) +{ + struct ha_msg* msg; + + if (!name || !value) { + cl_log(LOG_ERR, "%s: no parameter name or value", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_msg(SETLRMDPARAM); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(SETLRMDPARAM); + return HA_FAIL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name) + || HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_VAL,value)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static char* +lrm_get_lrmd_param (ll_lrm_t* lrm, const char *name) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + const char* value = NULL; + char* v2; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_msg(GETLRMDPARAM); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETLRMDPARAM); + return NULL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLRMDPARAM, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLRMDPARAM); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + value = ha_msg_value(ret,F_LRM_LRMD_PARAM_VAL); + if (!value) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_LRMD_PARAM_VAL, ret); + ha_msg_del(ret); + return NULL; + } + v2 = g_strdup(value); + ha_msg_del(ret); + return v2; +} + +static int +lrm_add_rsc (ll_lrm_t* lrm, const char* rsc_id, const char* class +, const char* type, const char* provider, GHashTable* parameter) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_add_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_add_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the message of add resource */ + msg = create_lrm_addrsc_msg(rsc_id, class, type, provider, parameter); + if ( NULL == msg) { + cl_log(LOG_ERR, "%s(%d): failed to create a ADDSRC message " + "with function create_lrm_addrsc_msg" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(ADDRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, ADDRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_delete_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + int rc; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_delete_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_delete_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the msg of del resource */ + msg = create_lrm_rsc_msg(rsc_id, DELRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(DELRSC); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(DELRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the response of the msg */ + rc = get_ret_from_ch(ch_cmd); + if (rc != HA_OK && rc != HA_RSCBUSY) { + LOG_GOT_FAIL_RET(LOG_ERR, DELRSC); + return HA_FAIL; + } + + return rc; +} + +static IPC_Channel* +lrm_ipcchan (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_inputfd: callback channel is null."); + return NULL; + } + + return ch_cbk; +} + +static gboolean +lrm_msgready (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_msgready: callback channel is null."); + return FALSE; + } + return ch_cbk->ops->is_message_pending(ch_cbk); +} + +static int +lrm_rcvmsg (ll_lrm_t* lrm, int blocking) +{ + struct ha_msg* msg = NULL; + lrm_op_t* op = NULL; + int msg_count = 0; + + /* if it is not blocking mode and no message in the channel, return */ + if ((!lrm_msgready(lrm)) && (!blocking)) { + cl_log(LOG_DEBUG, + "lrm_rcvmsg: no message and non-block."); + return msg_count; + } + /* wait until message ready */ + if (!lrm_msgready(lrm)) { + ch_cbk->ops->waitin(ch_cbk); + } + while (lrm_msgready(lrm)) { + if (ch_cbk->ch_status == IPC_DISCONNECT) { + return msg_count; + } + /* get the message */ + msg = msgfromIPC(ch_cbk, MSG_ALLOWINTR); + if (msg == NULL) { + cl_log(LOG_WARNING, + "%s(%d): receive a null message with msgfromIPC." + , __FUNCTION__, __LINE__); + return msg_count; + } + msg_count++; + + op = msg_to_op(msg); + if (NULL!=op && NULL!=op_done_callback) { + (*op_done_callback)(op); + } + free_op(op); + ha_msg_del(msg); + } + + return msg_count; +} + +/* following are the functions for rsc_ops */ +static int +rsc_perform_op (lrm_rsc_t* rsc, lrm_op_t* op) +{ + int rc = 0; + struct ha_msg* msg = NULL; + char* rsc_id; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd + || NULL == rsc + || NULL == rsc->id + || NULL == op + || NULL == op->op_type) { + cl_log(LOG_ERR, + "rsc_perform_op: wrong parameters."); + return HA_FAIL; + } + /* create the msg of perform op */ + rsc_id = op->rsc_id; + op->rsc_id = rsc->id; + msg = op_to_msg(op); + op->rsc_id = rsc_id; + if ( NULL == msg) { + cl_log(LOG_ERR, "rsc_perform_op: failed to create a message " + "with function op_to_msg"); + return HA_FAIL; + } + /* send it to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(PERFORMOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + /* check return code, the return code is the call_id of the op */ + rc = get_ret_from_ch(ch_cmd); + return rc; +} + +static int +rsc_cancel_op (lrm_rsc_t* rsc, int call_id) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_cancel_op: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_cancel_op: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,CANCELOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + if (HA_OK != ha_msg_add_int(msg, F_LRM_CALLID, call_id)) { + LOG_BASIC_ERROR("ha_msg_add_int"); + ha_msg_del(msg); + return HA_FAIL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(CANCELOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc; +} + +static int +rsc_flush_ops (lrm_rsc_t* rsc) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_flush_ops: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_flush_ops: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,FLUSHOPS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FLUSHOPS, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc>0?rc:HA_FAIL; +} +static gint +compare_call_id(gconstpointer a, gconstpointer b) +{ + const lrm_op_t* opa = (const lrm_op_t*)a; + const lrm_op_t* opb = (const lrm_op_t*)b; + return opa->call_id - opb->call_id; +} +static GList* +rsc_get_cur_state (lrm_rsc_t* rsc, state_flag_t* cur_state) +{ + GList* op_list = NULL, * tmplist = NULL; + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + struct ha_msg* op_msg = NULL; + lrm_op_t* op = NULL; + int state; + int op_count, i; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_cur_state: ch_mod is null."); + return NULL; + } + /* check paramter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_cur_state: parameter rsc is null."); + return NULL; + } + /* create the msg of get current state of resource */ + msg = create_lrm_rsc_msg(rsc->id,GETRSCSTATE); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSCSTATE); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCSTATE, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCSTATE); + return NULL; + } + + /* get the state of the resource from the message */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_STATE, &state)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_STATE, ret); + ha_msg_del(ret); + return NULL; + } + *cur_state = (state_flag_t)state; + /* the first msg includes the count of pending ops. */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_OPCNT, &op_count)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPCNT, ret); + ha_msg_del(ret); + return NULL; + } + ha_msg_del(ret); + for (i = 0; i < op_count; i++) { + /* one msg for one op */ + op_msg = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + + if (NULL == op_msg) { + cl_log(LOG_WARNING, "%s(%d): failed to receive a " + "(pending operation) message from lrmd." + , __FUNCTION__, __LINE__); + continue; + } + op = msg_to_op(op_msg); + /* add msg to the return list */ + + if (NULL != op) { + op_list = g_list_append(op_list, op); + } + else { + cl_log(LOG_WARNING, "%s(%d): failed to make a operation " + "from a message with function msg_to_op" + , __FUNCTION__, __LINE__); + } + ha_msg_del(op_msg); + } + op_list = g_list_sort(op_list, compare_call_id); + + /* Delete the duplicate op for call_id */ +#if 0 + cl_log(LOG_WARNING, "Before uniquing"); + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + if (NULL != g_list_previous(tmplist)) { + if (((lrm_op_t*)(g_list_previous(tmplist)->data))->call_id + == ((lrm_op_t*)(tmplist->data))->call_id) { + op_list = g_list_remove_link (op_list, tmplist); + free_op((lrm_op_t *)tmplist->data); + g_list_free_1(tmplist); + tmplist = g_list_first(op_list); + } + } + tmplist = g_list_next(tmplist); + } + +#if 0 + cl_log(LOG_WARNING, "After uniquing"); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + return op_list; +} + +static lrm_op_t* +rsc_get_last_result (lrm_rsc_t* rsc, const char* op_type) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_op_t* op = NULL; + int opcount = 0; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_last_result: ch_mod is null."); + return NULL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_last_result: parameter rsc is null."); + return NULL; + } + /* create the msg of get last op */ + msg = create_lrm_rsc_msg(rsc->id,GETLASTOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETLASTOP); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RID, rsc->id)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_OP, op_type)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLASTOP, "ch_cmd"); + return NULL; + } + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLASTOP); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_value_int(ret,F_LRM_OPCNT, &opcount)) { + op = NULL; + } + else if ( 1 == opcount ) { + op = msg_to_op(ret); + } + ha_msg_del(msg); + ha_msg_del(ret); + return op; +} +/* + * following are the implements of the utility functions + */ +lrm_op_t* +lrm_op_new(void) +{ + lrm_op_t* op; + + op = g_new0(lrm_op_t, 1); + op->op_status = LRM_OP_PENDING; + return op; +} + +static lrm_op_t* +msg_to_op(struct ha_msg* msg) +{ + lrm_op_t* op; + const char* op_type; + const char* app_name; + const char* rsc_id; + const char* fail_reason; + const char* output; + const void* user_data; + + op = lrm_op_new(); + + /* op->timeout, op->interval, op->target_rc, op->call_id*/ + if (HA_OK != ha_msg_value_int(msg,F_LRM_TIMEOUT, &op->timeout) + || HA_OK != ha_msg_value_int(msg,F_LRM_INTERVAL, &op->interval) + || HA_OK != ha_msg_value_int(msg,F_LRM_TARGETRC, &op->target_rc) + || HA_OK != ha_msg_value_int(msg,F_LRM_DELAY, &op->start_delay) + || HA_OK != ha_msg_value_int(msg,F_LRM_CALLID, &op->call_id)) { + LOG_BASIC_ERROR("ha_msg_value_int"); + free_op(op); + return NULL; + } + + /* op->op_status */ + if (HA_OK != + ha_msg_value_int(msg, F_LRM_OPSTATUS, (int*)&op->op_status)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPSTATUS, msg); + op->op_status = LRM_OP_PENDING; + } + + /* if it finished successfully */ + if (LRM_OP_DONE == op->op_status ) { + /* op->rc */ + if (HA_OK != ha_msg_value_int(msg, F_LRM_RC, &op->rc)) { + free_op(op); + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RC, msg); + return NULL; + } + /* op->output */ + output = cl_get_string(msg, F_LRM_DATA); + if (NULL != output){ + op->output = g_strdup(output); + } + else { + op->output = NULL; + } + } else if(op->op_status == LRM_OP_PENDING) { + op->rc = EXECRA_STATUS_UNKNOWN; + + } else { + op->rc = EXECRA_EXEC_UNKNOWN_ERROR; + } + + + /* op->app_name */ + app_name = ha_msg_value(msg, F_LRM_APP); + if (NULL == app_name) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_APP, msg); + free_op(op); + return NULL; + } + op->app_name = g_strdup(app_name); + + + /* op->op_type */ + op_type = ha_msg_value(msg, F_LRM_OP); + if (NULL == op_type) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_OP, msg); + free_op(op); + return NULL; + } + op->op_type = g_strdup(op_type); + + /* op->rsc_id */ + rsc_id = ha_msg_value(msg, F_LRM_RID); + if (NULL == rsc_id) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RID, msg); + free_op(op); + return NULL; + } + op->rsc_id = g_strdup(rsc_id); + + /* op->fail_reason present only on async failures */ + fail_reason = ha_msg_value(msg, F_LRM_FAIL_REASON); + if (fail_reason) { + op->fail_reason = g_strdup(fail_reason); + } + + /* op->user_data */ + user_data = cl_get_string(msg, F_LRM_USERDATA); + + if (NULL != user_data) { + op->user_data = g_strdup(user_data); + } + + /* time_stamps */ + if (ha_msg_value_ul(msg, F_LRM_T_RUN, &op->t_run) != HA_OK + || ha_msg_value_ul(msg, F_LRM_T_RCCHANGE, &op->t_rcchange) != HA_OK + || ha_msg_value_ul(msg, F_LRM_EXEC_TIME, &op->exec_time) != HA_OK + || ha_msg_value_ul(msg, F_LRM_QUEUE_TIME, &op->queue_time) != HA_OK) { + /* cl_log(LOG_WARNING + , "%s:%d: failed to get the timing information" + , __FUNCTION__, __LINE__); + */ + } + + /* op->params */ + op->params = ha_msg_value_str_table(msg, F_LRM_PARAM); + + ha_msg_value_int(msg, F_LRM_RSCDELETED, &op->rsc_deleted); + + return op; +} + +static struct ha_msg* +op_to_msg (lrm_op_t* op) +{ + struct ha_msg* msg = ha_msg_new(15); + if (!msg) { + LOG_BASIC_ERROR("ha_msg_new"); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_TYPE, PERFORMOP) + || HA_OK != ha_msg_add(msg, F_LRM_RID, op->rsc_id) + || HA_OK != ha_msg_add(msg, F_LRM_OP, op->op_type) + || HA_OK != ha_msg_add_int(msg, F_LRM_TIMEOUT, op->timeout) + || HA_OK != ha_msg_add_int(msg, F_LRM_INTERVAL, op->interval) + || HA_OK != ha_msg_add_int(msg, F_LRM_DELAY, op->start_delay) + || HA_OK != ha_msg_add_int(msg, F_LRM_COPYPARAMS, op->copyparams) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RUN,op->t_run) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RCCHANGE, op->t_rcchange) + || HA_OK != ha_msg_add_ul(msg, F_LRM_EXEC_TIME, op->exec_time) + || HA_OK != ha_msg_add_ul(msg, F_LRM_QUEUE_TIME, op->queue_time) + || HA_OK != ha_msg_add_int(msg, F_LRM_TARGETRC, op->target_rc) + || ( op->app_name && (HA_OK != ha_msg_add(msg, F_LRM_APP, op->app_name))) + || ( op->user_data && (HA_OK != ha_msg_add(msg,F_LRM_USERDATA,op->user_data))) + || ( op->params && (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,op->params)))) { + LOG_BASIC_ERROR("op_to_msg conversion failed"); + ha_msg_del(msg); + return NULL; + } + + return msg; +} + +static int +get_ret_from_ch(IPC_Channel* ch) +{ + int ret; + struct ha_msg* msg; + + msg = msgfromIPC(ch, MSG_ALLOWINTR); + + if (NULL == msg) { + cl_log(LOG_ERR + , "%s(%d): failed to receive message with function msgfromIPC" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + ha_msg_del(msg); + return HA_FAIL; + } + ha_msg_del(msg); + return ret; +} + +static int +get_ret_from_msg(struct ha_msg* msg) +{ + int ret; + + if (NULL == msg) { + cl_log(LOG_ERR, "%s(%d): the parameter is a NULL pointer." + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + return HA_FAIL; + } + return ret; +} +static void +free_op (lrm_op_t* op) +{ + if (NULL == op) { + return; + } + if (NULL != op->op_type) { + g_free(op->op_type); + } + if (NULL != op->output) { + g_free(op->output); + } + if (NULL != op->rsc_id) { + g_free(op->rsc_id); + } + if (NULL != op->app_name) { + g_free(op->app_name); + } + if (NULL != op->user_data) { + g_free(op->user_data); + } + if (NULL != op->params) { + free_str_table(op->params); + } + g_free(op); +} + +void lrm_free_op(lrm_op_t* op) { + free_op(op); +} +void lrm_free_rsc(lrm_rsc_t* rsc) { + if (NULL == rsc) { + return; + } + if (NULL != rsc->id) { + g_free(rsc->id); + } + if (NULL != rsc->type) { + g_free(rsc->type); + } + if (NULL != rsc->class) { + g_free(rsc->class); + } + if (NULL != rsc->provider) { + g_free(rsc->provider); + } + if (NULL != rsc->params) { + free_str_table(rsc->params); + } + g_free(rsc); +} +void lrm_free_str_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + g_free(item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_op_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + free_op((lrm_op_t*)item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_str_table(GHashTable* table) { + if (NULL != table) { + free_str_table(table); + } +} + +const char * +execra_code2string(uniform_ret_execra_t code) +{ + switch(code) { + case EXECRA_EXEC_UNKNOWN_ERROR: + return "unknown exec error"; + case EXECRA_NO_RA: + return "no RA"; + case EXECRA_OK: + return "ok"; + case EXECRA_UNKNOWN_ERROR: + return "unknown error"; + case EXECRA_INVALID_PARAM: + return "invalid parameter"; + case EXECRA_UNIMPLEMENT_FEATURE: + return "unimplemented feature"; + case EXECRA_INSUFFICIENT_PRIV: + return "insufficient privileges"; + case EXECRA_NOT_INSTALLED: + return "not installed"; + case EXECRA_NOT_CONFIGURED: + return "not configured"; + case EXECRA_NOT_RUNNING: + return "not running"; + /* For status command only */ + case EXECRA_RUNNING_MASTER: + return "master"; + case EXECRA_FAILED_MASTER: + return "master (failed)"; + case EXECRA_RA_DEAMON_DEAD1: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_DEAD2: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_STOPPED: + return "status: daemon stopped"; + case EXECRA_STATUS_UNKNOWN: + return "status: unknown"; + default: + break; + } + + return "<unknown>"; +} diff --git a/lib/lrm/lrm_msg.c b/lib/lrm/lrm_msg.c new file mode 100644 index 0000000..fdd3b3f --- /dev/null +++ b/lib/lrm/lrm_msg.c @@ -0,0 +1,212 @@ +/* + * Message Functions For Local Resource Manager + * + * 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 + * + */ + +/* + * By Huang Zhen <zhenh@cn.ibm.com> 2004/2/13 + * + */ +#include <lha_internal.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <ha_msg.h> +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed.", __FUNCTION__, __LINE__, apiname) + +const lrm_op_t lrm_zero_op; /* Default initialized to zeros */ + +static void +copy_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable* taget_table = (GHashTable*)user_data; + g_hash_table_insert(taget_table, g_strdup(key), g_strdup(value)); +} + +GHashTable* +copy_str_table(GHashTable* src_table) +{ + GHashTable* target_table = NULL; + + if ( NULL == src_table) { + return NULL; + } + target_table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_foreach(src_table, copy_pair, target_table); + return target_table; +} + +static void +merge_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *merged = (GHashTable*)user_data; + + if (g_hash_table_lookup(merged, key)) { + return; + } + + g_hash_table_insert(merged, g_strdup(key), g_strdup(value)); +} + +GHashTable* +merge_str_tables(GHashTable* old, GHashTable* new) +{ + GHashTable* merged = NULL; + if ( NULL == old ) { + return copy_str_table(new); + } + if ( NULL == new ) { + return copy_str_table(old); + } + merged = copy_str_table(new); + g_hash_table_foreach(old, merge_pair, merged); + return merged; +} + +static gboolean +free_pair(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +void +free_str_table(GHashTable* hash_table) +{ + g_hash_table_foreach_remove(hash_table, free_pair, NULL); + g_hash_table_destroy(hash_table); +} + + + +struct ha_msg* +create_lrm_msg (const char* msg) +{ + struct ha_msg* ret; + if ((NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(1); + if (HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_reg_msg(const char* app_name) +{ + struct ha_msg* ret; + if ((NULL == app_name) || (0 == strlen(app_name))) { + return NULL; + } + + ret = ha_msg_new(5); + + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, REGISTER) + || HA_OK != ha_msg_add(ret, F_LRM_APP, app_name) + || HA_OK != ha_msg_add_int(ret, F_LRM_PID, getpid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_GID, getegid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_UID, getuid())) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_addrsc_msg(const char* rid, const char* class, const char* type, + const char* provider, GHashTable* params) +{ + struct ha_msg* msg; + if (NULL==rid||NULL==class||NULL==type) { + return NULL; + } + + msg = ha_msg_new(5); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, ADDRSC) + || HA_OK != ha_msg_add(msg, F_LRM_RID, rid) + || HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + + if ( params ) { + if (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,params)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + return msg; +} + + +struct ha_msg* +create_lrm_rsc_msg(const char* rid, const char* msg) +{ + struct ha_msg* ret; + if ((NULL == rid) ||(NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(2); + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg) + || HA_OK != ha_msg_add(ret, F_LRM_RID, rid)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return ret; +} + + + +struct ha_msg* +create_lrm_ret(int ret, int fields) +{ + struct ha_msg* msg = ha_msg_new(fields); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, RETURN) + || HA_OK != ha_msg_add_int(msg, F_LRM_RET, ret)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return msg; +} + diff --git a/lib/lrm/racommon.c b/lib/lrm/racommon.c new file mode 100644 index 0000000..2670f05 --- /dev/null +++ b/lib/lrm/racommon.c @@ -0,0 +1,178 @@ +/* + * Common functions for LRM interface to resource agents + * + * 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 + * + * File: racommon.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + */ + + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <sys/stat.h> +#include <clplumbing/cl_log.h> +#include <lrm/raexec.h> +#include <lrm/racommon.h> + +void +get_ra_pathname(const char* class_path, const char* type, const char* provider, + char pathname[]) +{ + char* type_dup; + char* base_name; + + type_dup = g_strndup(type, RA_MAX_NAME_LENGTH); + if (type_dup == NULL) { + cl_log(LOG_ERR, "No enough memory to allocate."); + pathname[0] = '\0'; + return; + } + + base_name = basename(type_dup); + + if ( strncmp(type, base_name, RA_MAX_NAME_LENGTH) == 0 ) { + /*the type does not include path*/ + if (provider) { + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s/%s", + class_path, provider, type); + }else{ + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s", + class_path,type); + } + }else{ + /*the type includes path, just copy it to pathname*/ + if ( *type == '/' ) { + g_strlcpy(pathname, type, RA_MAX_NAME_LENGTH); + } else { + *pathname = '\0'; + cl_log(LOG_ERR, "%s: relative paths not allowed: %s", + __FUNCTION__, type); + } + } + + g_free(type_dup); +} + +/* + * Description: Filter a file. + * Return Value: + * TRUE: the file is qualified. + * FALSE: the file is unqualified. + * Notes: A qualifed file is a regular file with execute bits + * which does not start with '.' + */ +gboolean +filtered(char * file_name) +{ + struct stat buf; + char *s; + + if ( stat(file_name, &buf) != 0 ) { + return FALSE; + } + if ( ((s = strrchr(file_name,'/')) && *(s+1) == '.') + || *file_name == '.' ) { + return FALSE; + } + + if ( S_ISREG(buf.st_mode) + && ( ( buf.st_mode & S_IXUSR ) || ( buf.st_mode & S_IXGRP ) + || ( buf.st_mode & S_IXOTH ) ) ) { + return TRUE; + } + return FALSE; +} + +int +get_runnable_list(const char* class_path, GList ** rsc_info) +{ + struct dirent **namelist; + int file_num; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + + file_num = scandir(class_path, &namelist, NULL, alphasort); + if (file_num < 0) { + cl_log(LOG_ERR, "scandir failed in RA plugin"); + return -2; + } else{ + while (file_num--) { + char tmp_buffer[FILENAME_MAX+1]; + + tmp_buffer[0] = '\0'; + tmp_buffer[FILENAME_MAX] = '\0'; + snprintf(tmp_buffer, FILENAME_MAX, "%s/%s", + class_path, namelist[file_num]->d_name ); + if ( filtered(tmp_buffer) == TRUE ) { + *rsc_info = g_list_append(*rsc_info, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*rsc_info); +} + +int +get_failed_exec_rc(void) +{ + int rc; + + switch (errno) { /* see execve(2) */ + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + rc = EXECRA_NOT_INSTALLED; + break; + case EACCES: /* permission denied (various errors) */ + rc = EXECRA_INSUFFICIENT_PRIV; + break; + default: + rc = EXECRA_EXEC_UNKNOWN_ERROR; + break; + } + return rc; +} + +void +closefiles(void) +{ + int fd; + + /* close all descriptors except stdin/out/err and channels to logd */ + for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) { + /*if (!cl_log_is_logd_fd(fd))*/ + close(fd); + } +} diff --git a/lib/pils/Makefile.am b/lib/pils/Makefile.am new file mode 100644 index 0000000..d47c6c7 --- /dev/null +++ b/lib/pils/Makefile.am @@ -0,0 +1,57 @@ +# +# pils: Linux-HA heartbeat code +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @CFLAGS@ + +## include files +#pkginclude_HEADERS = $(top_srcdir)/include/pils/plugin.h \ +# $(top_srcdir)/include/pils/interface.h + +## binaries +#sbin_PROGRAMS = main + + +#main_SOURCES = main.c + +#main_LDADD = libpils.la @LIBLTDL@ \ +# $(GLIBLIB) \ +# $(top_builddir)/replace/libreplace.la +#main_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ + + +## libraries + +lib_LTLIBRARIES = libpils.la + +plugindir = $(libdir)/@HB_PKG@/plugins/test +plugin_LTLIBRARIES = test.la + +libpils_la_SOURCES = pils.c +libpils_la_LDFLAGS = -version-info 2:0:0 +libpils_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + @LIBLTDL@ $(GLIBLIB) +test_la_SOURCES = test.c +test_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/pils/main.c b/lib/pils/main.c new file mode 100644 index 0000000..32faceb --- /dev/null +++ b/lib/pils/main.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * 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 <stdio.h> +#include <pils/generic.h> + +#define MOD "/home/alanr/modules" + +GHashTable* test1functions = NULL; + +long one = 1; +long two = 2; +long three = 3; +long four = 4; + +static int TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr +); + +static PILGenericIfMgmtRqst RegRqsts [] = + { {"test", &test1functions, &one, TestCallBack, &two}, + {NULL, NULL, NULL, NULL, NULL} +}; + +int +main(int argc, char ** argv) +{ + PILPluginUniv * u; + PIL_rc rc; + int j; + + + u = NewPILPluginUniv(MOD); + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + PILLogMemStats(); + + + if ((rc = PILLoadPlugin(u, "InterfaceMgr", "generic", &RegRqsts)) + != PIL_OK) { + fprintf(stderr, "generic plugin load Error = [%s]\n" + , lt_dlerror()); + /*exit(1);*/ + } + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + + for (j=0; j < 10; ++j) { + PILLogMemStats(); + fprintf(stderr, "****Loading plugin test/test\n"); + if ((rc = PILLoadPlugin(u, "test", "test", NULL)) != PIL_OK) { + printf("ERROR: test plugin load error = [%d/%s]\n" + , rc, lt_dlerror()); + } + PILLogMemStats(); + fprintf(stderr, "****UN-loading plugin test/test\n"); + if ((rc = PILIncrIFRefCount(u, "test", "test", -1))!= PIL_OK){ + printf("ERROR: test plugin UNload error = [%d/%s]\n" + , rc, lt_dlerror()); + } + } + PILLogMemStats(); + DelPILPluginUniv(u); u = NULL; + PILLogMemStats(); + + return 0; +} + + +static int +TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr) +{ + char cbbuf[32]; + + switch(t) { + case PIL_REGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_REGISTER"); + break; + + case PIL_UNREGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_UNREGISTER"); + break; + + default: + snprintf(cbbuf, sizeof(cbbuf), "type [%d?]", t); + break; + } + + fprintf(stderr, "Callback: (%s, univ: 0x%lx, module: %s/%s, user ptr: 0x%lx (%ld))\n" + , cbbuf + , (unsigned long) univ + , iftype, ifname + , (unsigned long)userptr + , (*((long *)userptr))); + return PIL_OK; +} + diff --git a/lib/pils/pils.c b/lib/pils/pils.c new file mode 100644 index 0000000..4243b22 --- /dev/null +++ b/lib/pils/pils.c @@ -0,0 +1,2152 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <stdarg.h> +#include <sys/stat.h> + +/* Dumbness... */ +#define time FooTimeParameter +#define index FooIndexParameter +# include <glib.h> +#undef time +#undef index + + +#define ENABLE_PIL_DEFS_PRIVATE +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#ifndef STRLEN_CONST +# define STRLEN_CONST(con) (sizeof(con)-1) +#endif + +#include <pils/interface.h> + +#define NEW(type) (g_new(type,1)) +#define ZAP(obj) memset(obj, 0, sizeof(*obj)) +#define DELETE(obj) {g_free(obj); obj = NULL;} + +#ifdef LTDL_SHLIB_EXT +# define PLUGINSUFFIX LTDL_SHLIB_EXT +#else +# define PLUGINSUFFIX ".so" +#endif + +static int PluginDebugLevel = 0; + +#define DEBUGPLUGIN (PluginDebugLevel > 0) + + + +static PIL_rc InterfaceManager_plugin_init(PILPluginUniv* univ); + +static char** PILPluginTypeListPlugins(PILPluginType* pitype, int* picount); +static PILInterface* FindIF(PILPluginUniv* universe, const char *iftype +, const char * ifname); +static PIL_rc PluginExists(const char * PluginPath); +static char * PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname); + + +void DelPILPluginUniv(PILPluginUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * functions, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * + * They all follow the same calling sequence though. It is: + * String name"*" type object + * "*" type object with the name given by 1st argument + * NULL + * + * For example: + * RmAPILPluginType takes + * string name + * PILPluginType* object with the given name. + */ +static gboolean RmAPILPluginType +( gpointer pitname /* Name of this plugin type */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +); +static void RemoveAPILPluginType(PILPluginType*); + +static PILPluginType* NewPILPluginType +( PILPluginUniv* pluginuniv +, const char * plugintype +); +static void DelPILPluginType(PILPluginType*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILPlugin +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +); +static void RemoveAPILPlugin(PILPlugin*); + + +static PILPlugin* NewPILPlugin(PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym); +static void DelPILPlugin(PILPlugin*); + +struct MemStat { + unsigned long news; + unsigned long frees; +}; + +static struct PluginStats { + struct MemStat plugin; + struct MemStat pitype; + struct MemStat piuniv; + struct MemStat interface; + struct MemStat interfacetype; + struct MemStat interfaceuniv; +}PILstats; + +#define STATNEW(t) {PILstats.t.news ++; } +#define STATFREE(t) {PILstats.t.frees ++; } + + + +static PILInterfaceUniv* NewPILInterfaceUniv(PILPluginUniv*); +static void DelPILInterfaceUniv(PILInterfaceUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, but + * not necessarily, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILInterfaceType +( gpointer iftypename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +); +static void RemoveAPILInterfaceType(PILInterfaceType*, PILInterfaceType*); + +static PILInterfaceType* NewPILInterfaceType +( PILInterfaceUniv* +, const char * typename +, void* ifexports, void* user_data +); +static void DelPILInterfaceType(PILInterfaceType*); +/* + * These RmA* functions are designed to be called from + * hash_table_foreach, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * They can be called from other places safely also. + */ +static gboolean RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer plugin /* PILInterface* */ +, gpointer notused +); +static PIL_rc RemoveAPILInterface(PILInterface*); +static void DelPILPluginType(PILPluginType*); + +static PILInterface* NewPILInterface +( PILInterfaceType* interfacetype +, const char* interfacename +, void * exports +, PILInterfaceFun closefun +, void* ud_interface +, PILPlugin* loading_plugin /* The plugin that loaded us */ +); +static void DelPILInterface(PILInterface*); +static PIL_rc close_ifmgr_interface(PILInterface*, void*); + + + + +/* + * For consistency, we show up as a plugin in our our system. + * + * Here are our exports as a plugin. + * + */ +static const char * PIL_PILPluginVersion(void); +static void PIL_PILPluginClose (PILPlugin*); +void PILpisysSetDebugLevel (int level); +int PILpisysGetDebugLevel(void); +static const char * PIL_PILPluginLicense (void); +static const char * PIL_PILPluginLicenseUrl (void); + +static const PILPluginOps PluginExports = +{ PIL_PILPluginVersion +, PILpisysGetDebugLevel +, PILpisysSetDebugLevel +, PIL_PILPluginLicense +, PIL_PILPluginLicenseUrl +, PIL_PILPluginClose +}; + +/* Prototypes for the functions that we export to every plugin */ +static PIL_rc PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* mops); +static PIL_rc PILunregister_plugin(PILPlugin* piinfo); +static PIL_rc +PILRegisterInterface +( PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Ops exported by this interface */ +, PILInterfaceFun closefunc /* Ops exported by this interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by */ + /* this interface (OP) */ +, void* ud_interface /* interface user data */ +); +static PIL_rc PILunregister_interface(PILInterface* interfaceid); +static void PILLog(PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + + +/* + * This is the set of functions that we export to every plugin + * + * That also makes it the set of functions that every plugin imports. + * + */ + +static PILPluginImports PILPluginImportSet = +{ PILregister_plugin /* register_plugin */ +, PILunregister_plugin /* unregister_plugin */ +, PILRegisterInterface /* register_interface */ +, RemoveAPILInterface /* unregister_interface */ +, PILLoadPlugin /* load_plugin */ +, PILLog /* Logging function */ +, g_malloc /* Malloc function */ +, g_realloc /* realloc function */ +, g_free /* Free function */ +, g_strdup /* Strdup function */ +}; + +static PIL_rc ifmgr_register_interface(PILInterface* newif + , void** imports); +static PIL_rc ifmgr_unregister_interface(PILInterface* interface); + +/* + * For consistency, the master interface manager is a interface in the + * system. Below is our set of exported Interface functions. + * + * Food for thought: This is the interface manager whose name is + * interface. This makes it the Interface Interface interface ;-) + * (or the Interface/Interface interface if you prefer) + */ + +static PILInterfaceOps IfExports = +{ ifmgr_register_interface +, ifmgr_unregister_interface +}; + + + +/* + * Below is the set of functions we export to every interface manager. + */ + +static int IfRefCount(PILInterface * ifh); +static int IfIncrRefCount(PILInterface*eifinfo,int plusminus); +static int PluginIncrRefCount(PILPlugin*eifinfo,int plusminus); +#if 0 +static int PluginRefCount(PILPlugin * ifh); +#endif +static void IfForceUnregister(PILInterface *eifinfo); +static void IfForEachClientRemove(PILInterface* manangerif + , gboolean(*f)(PILInterface* clientif, void * other) + , void* other); + +static PILInterfaceImports IFManagerImports = +{ IfRefCount +, IfIncrRefCount +, IfForceUnregister +, IfForEachClientRemove +}; +static void PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype); +static void PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv); +static void PILValidatePluginUniv(gpointer key, gpointer pitype, gpointer); +static void PILValidateInterface(gpointer key, gpointer interface, gpointer iftype); +static void PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv); +static void PILValidateInterfaceUniv(gpointer key, gpointer puniv, gpointer); + +/***************************************************************************** + * + * This code is for managing plugins, and interacting with them... + * + ****************************************************************************/ + +static PILPlugin* +NewPILPlugin( PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym) +{ + PILPlugin* ret = NEW(PILPlugin); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugin(0x%lx)", (unsigned long)ret); + } + + STATNEW(plugin); + ret->MagicNum = PIL_MAGIC_PLUGIN; + ret->plugin_name = g_strdup(plugin_name); + ret->plugintype = pitype; + ret->refcnt = 0; + ret->dlhandle = dlhand; + ret->dlinitfun = PluginSym; + PILValidatePlugin(ret->plugin_name, ret, pitype); + return ret; +} +static void +DelPILPlugin(PILPlugin*pi) +{ + + if (pi->refcnt > 0) { + PILLog(PIL_INFO, "DelPILPlugin: Non-zero refcnt"); + } + + if (pi->dlhandle) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Closing dlhandle for (%s/%s)" + , pi->plugintype->plugintype, pi->plugin_name); + } + lt_dlclose(pi->dlhandle); + }else if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NO dlhandle for (%s/%s)!" + , pi->plugintype->plugintype, pi->plugin_name); + } + DELETE(pi->plugin_name); + ZAP(pi); + DELETE(pi); + STATFREE(plugin); +} + + +static PILPluginType dummymlpitype = +{ PIL_MAGIC_PLUGINTYPE +, NULL /*plugintype*/ +, NULL /*piuniv*/ +, NULL /*Plugins*/ +, PILPluginTypeListPlugins /* listplugins */ +}; + +static PILPluginType* +NewPILPluginType(PILPluginUniv* pluginuniv + , const char * plugintype +) +{ + PILPluginType* ret = NEW(PILPluginType); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugintype(0x%lx)", (unsigned long)ret); + } + STATNEW(pitype); + + *ret = dummymlpitype; + + ret->plugintype = g_strdup(plugintype); + ret->piuniv = pluginuniv; + ret->Plugins = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(pluginuniv->PluginTypes + , g_strdup(ret->plugintype), ret); + PILValidatePluginType(ret->plugintype, ret, pluginuniv); + return ret; +} +static void +DelPILPluginType(PILPluginType*pitype) +{ + PILValidatePluginType(NULL, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginType(%s)", pitype->plugintype); + } + + STATFREE(pitype); + g_hash_table_foreach_remove(pitype->Plugins, RmAPILPlugin, NULL); + g_hash_table_destroy(pitype->Plugins); + DELETE(pitype->plugintype); + ZAP(pitype); + DELETE(pitype); +} +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean +RmAPILPlugin /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +) +{ + PILPlugin* Plugin = plugin; + PILPluginType* Pitype = Plugin->plugintype; + + PILValidatePlugin(piname, plugin, NULL); + PILValidatePluginType(NULL, Pitype, NULL); + g_assert(IS_PILPLUGIN(Plugin)); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPlugin(%s/%s)", Pitype->plugintype + , Plugin->plugin_name); + } + /* Normally called from g_hash_table_foreachremove or equivalent */ + + DelPILPlugin(plugin); + DELETE(piname); + return TRUE; +} + +static void +RemoveAPILPlugin(PILPlugin*Plugin) +{ + PILPluginType* Pitype = Plugin->plugintype; + gpointer key; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILPlugin(%s/%s)" + , Pitype->plugintype + , Plugin->plugin_name); + } + if (g_hash_table_lookup_extended(Pitype->Plugins + , Plugin->plugin_name, &key, (void*)&Plugin)) { + + g_hash_table_remove(Pitype->Plugins, key); + RmAPILPlugin(key, Plugin, NULL); + key = NULL; + Plugin = NULL; + + }else{ + g_assert_not_reached(); + } + if (g_hash_table_size(Pitype->Plugins) == 0) { + RemoveAPILPluginType(Pitype); + /* Pitype is now invalid */ + Pitype = NULL; + } +} + +PILPluginUniv* +NewPILPluginUniv(const char * basepluginpath) +{ + PILPluginUniv* ret = NEW(PILPluginUniv); + + /* The delimiter separating search path components */ + const char* path_delim = G_SEARCHPATH_SEPARATOR_S; + char * fullpath; + + STATNEW(piuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPluginUniv(0x%lx)" + , (unsigned long)ret); + } + if (!g_path_is_absolute(basepluginpath)) { + DELETE(ret); + return(ret); + } + ret->MagicNum = PIL_MAGIC_PLUGINUNIV; + fullpath = g_strdup_printf("%s%s%s", basepluginpath + , path_delim, PILS_BASE_PLUGINDIR); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Plugin path = %s", fullpath); + } + + /* Separate the root directory PATH into components */ + ret->rootdirlist = g_strsplit(fullpath, path_delim, 100); + g_free(fullpath); + + ret->PluginTypes = g_hash_table_new(g_str_hash, g_str_equal); + ret->imports = &PILPluginImportSet; + ret->ifuniv = NewPILInterfaceUniv(ret); + PILValidatePluginUniv(NULL, ret, NULL); + return ret; +} + +/* Change memory allocation functions immediately after creating universe */ +void +PilPluginUnivSetMemalloc(PILPluginUniv* u +, gpointer (*allocfun)(glib_size_t size) +, gpointer (*reallocfun)(gpointer ptr, glib_size_t size) +, void (*freefun)(void* space) +, char* (*strdupfun)(const char *s)) +{ + u->imports->alloc = allocfun; + u->imports->mrealloc = reallocfun; + u->imports->mfree = freefun; + u->imports->mstrdup = strdupfun; +} + + +/* Change logging functions - preferably right after creating universe */ +void +PilPluginUnivSetLog(PILPluginUniv* u +, void (*logfun) (PILLogLevel priority, const char * fmt, ...)) +{ + u->imports->log = logfun; +} + +void +DelPILPluginUniv(PILPluginUniv* piuniv) +{ + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginUniv(0x%lx)" + , (unsigned long)piuniv); + } + STATFREE(piuniv); + PILValidatePluginUniv(NULL, piuniv, NULL); + DelPILInterfaceUniv(piuniv->ifuniv); + piuniv->ifuniv = NULL; + g_hash_table_foreach_remove(piuniv->PluginTypes + , RmAPILPluginType, NULL); + g_hash_table_destroy(piuniv->PluginTypes); + g_strfreev(piuniv->rootdirlist); + ZAP(piuniv); + DELETE(piuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILPluginType +( gpointer pitname /* Name of this plugin type "real" key */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +) +{ + PILPluginType* Plugintype = pitype; + + g_assert(IS_PILPLUGINTYPE(Plugintype)); + PILValidatePluginType(pitname, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPluginType(%s)" + , Plugintype->plugintype); + } + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + DelPILPluginType(pitype); + DELETE(pitname); + return TRUE; +} +static void +RemoveAPILPluginType(PILPluginType*Plugintype) +{ + PILPluginUniv* Pluginuniv = Plugintype->piuniv; + gpointer key; + if (g_hash_table_lookup_extended(Pluginuniv->PluginTypes + , Plugintype->plugintype, &key, (void*)&Plugintype)) { + + g_hash_table_remove(Pluginuniv->PluginTypes, key); + RmAPILPluginType(key, Plugintype, NULL); + }else{ + g_assert_not_reached(); + } +} + +/* + * InterfaceManager_plugin_init: Initialize the handling of + * "Interface Manager" interfaces. + * + * There are a few potential bootstrapping problems here ;-) + * + */ +static PIL_rc +InterfaceManager_plugin_init(PILPluginUniv* univ) +{ + PILPluginImports* imports = univ->imports; + PILPluginType* pitype; + PILInterface* ifinfo; + PILInterfaceType* iftype; + void* dontcare; + PILPlugin* ifmgr_plugin; + PIL_rc rc; + + + iftype = NewPILInterfaceType(univ->ifuniv, PI_IFMANAGER, &IfExports + , NULL); + + g_hash_table_insert(univ->ifuniv->iftypes + , g_strdup(PI_IFMANAGER), iftype); + + pitype = NewPILPluginType(univ, PI_IFMANAGER); + + g_hash_table_insert(univ->PluginTypes + , g_strdup(PI_IFMANAGER), pitype); + + ifmgr_plugin= NewPILPlugin(pitype, PI_IFMANAGER, NULL, NULL); + + g_hash_table_insert(pitype->Plugins + , g_strdup(PI_IFMANAGER), ifmgr_plugin); + + /* We can call register_plugin, since it doesn't depend on us... */ + rc = imports->register_plugin(ifmgr_plugin, &PluginExports); + if (rc != PIL_OK) { + PILLog(PIL_CRIT, "register_plugin() failed in init: %s" + , PIL_strerror(rc)); + return(rc); + } + /* + * Now, we're registering interfaces, and are into some deep + * Catch-22 if do it the "easy" way, since our code is + * needed in order to support interface loading for the type of + * interface we are (a Interface interface). + * + * So, instead of calling imports->register_interface(), we have to + * do the work ourselves here... + * + * Since no one should yet be registered to handle Interface + * interfaces, we need to bypass the hash table handler lookup + * that register_interface would do and call the function that + * register_interface would call... + * + */ + + /* The first argument is the PILInterfaceType* */ + ifinfo = NewPILInterface(iftype, PI_IFMANAGER, &IfExports + , close_ifmgr_interface, NULL, NULL); + ifinfo->ifmanager = iftype->ifmgr_ref = ifinfo; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "InterfaceManager_plugin_init(0x%lx/%s)" + , (unsigned long)ifinfo, ifinfo->interfacename); + } + PILValidatePluginUniv(NULL, univ, NULL); + ifmgr_register_interface(ifinfo, &dontcare); + PILValidatePluginUniv(NULL, univ, NULL); + + return(PIL_OK); +}/*InterfaceManager_plugin_init*/ + + +/* Return current IfIf "plugin" version (not very interesting for us) */ +static const char * +PIL_PILPluginVersion(void) +{ + return("1.0"); +} + +/* Return current IfIf debug level */ +int +PILpisysGetDebugLevel(void) +{ + return(PluginDebugLevel); +} + +/* Set current IfIf debug level */ +void +PILpisysSetDebugLevel (int level) +{ + PluginDebugLevel = level; +} +struct set_debug_helper { + const char * pitype; + const char * piname; + int level; +}; + +static void +PILSetDebugLeveltoPlugin(gpointer key, gpointer plugin, gpointer Helper) +{ + PILPlugin* p = plugin; + struct set_debug_helper* helper = Helper; + + p->pluginops->setdebuglevel(helper->level); +} + +static void +PILSetDebugLevelbyType(const void * key, gpointer plugintype, gpointer Helper) +{ + struct set_debug_helper* helper = Helper; + + + PILPluginType* t = plugintype; + + if (helper->piname == NULL) { + g_hash_table_foreach(t->Plugins, PILSetDebugLeveltoPlugin + , helper); + }else{ + PILPlugin* p = g_hash_table_lookup(t->Plugins + , helper->piname); + if (p != NULL) { + p->pluginops->setdebuglevel(helper->level); + } + } +} + +void +PILSetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname +, int level) +{ + struct set_debug_helper helper = {pitype, piname, level}; + + if (u == NULL) { + return; + } + + if (pitype == NULL) { + g_hash_table_foreach(u->PluginTypes + /* + * Reason for this next cast: + * SetDebugLevelbyType takes const gpointer + * arguments, unlike a GHFunc which doesn't. + */ + , (GHFunc)PILSetDebugLevelbyType + , &helper); + }else{ + PILPluginType* t = g_hash_table_lookup(u->PluginTypes + , pitype); + if (t != NULL) { + PILSetDebugLevelbyType(pitype, t, &helper); + } + } +} + + +int +PILGetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname) +{ + PILPluginType* t; + PILPlugin* p; + if ( u == NULL + || pitype == NULL + || (t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL + || (p = g_hash_table_lookup(t->Plugins, piname)) == NULL) { + return -1; + } + return p->pluginops->getdebuglevel(); +} + +/* Close/shutdown our PILPlugin (the interface manager interface plugin) */ +/* All our interfaces will have already been shut down and unregistered */ +static void +PIL_PILPluginClose (PILPlugin* plugin) +{ +} +static const char * +PIL_PILPluginLicense (void) +{ + return LICENSE_LGPL; +} +static const char * +PIL_PILPluginLicenseUrl (void) +{ + return URL_LGPL; +} + +/***************************************************************************** + * + * This code is for managing interfaces, and interacting with them... + * + ****************************************************************************/ + + +static PILInterface* +NewPILInterface(PILInterfaceType* interfacetype + , const char* interfacename + , void * exports + , PILInterfaceFun closefun + , void* ud_interface + , PILPlugin* loading_plugin) +{ + PILInterface* ret = NULL; + PILInterface* look = NULL; + + + if ((look = g_hash_table_lookup(interfacetype->interfaces + , interfacename)) != NULL) { + PILLog(PIL_DEBUG, "Deleting PILInterface!"); + DelPILInterface(look); + } + ret = NEW(PILInterface); + STATNEW(interface); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx)", (unsigned long)ret); + } + + if (ret) { + ret->MagicNum = PIL_MAGIC_INTERFACE; + ret->interfacetype = interfacetype; + ret->exports = exports; + ret->ud_interface = ud_interface; + ret->interfacename = g_strdup(interfacename); + ret->ifmanager = interfacetype->ifmgr_ref; + ret->loadingpi = loading_plugin; + g_hash_table_insert(interfacetype->interfaces + , g_strdup(ret->interfacename), ret); + + ret->if_close = closefun; + ret->refcnt = 1; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx:%s/%s)*** user_data: 0x%p *******" + , (unsigned long)ret + , interfacetype->typename + , ret->interfacename + , ud_interface); + } + } + return ret; +} +static void +DelPILInterface(PILInterface* intf) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterface(0x%lx/%s)" + , (unsigned long)intf, intf->interfacename); + } + STATFREE(interface); + DELETE(intf->interfacename); + ZAP(intf); + DELETE(intf); +} + +static PILInterfaceType* +NewPILInterfaceType(PILInterfaceUniv*univ, const char * typename +, void* ifeports, void* user_data) +{ + PILInterfaceType* ifmgr_types; + PILInterface* ifmgr_ref; + PILInterfaceType* ret = NEW(PILInterfaceType); + + + STATNEW(interfacetype); + ret->MagicNum = PIL_MAGIC_INTERFACETYPE; + ret->typename = g_strdup(typename); + ret->interfaces = g_hash_table_new(g_str_hash, g_str_equal); + ret->ud_if_type = user_data; + ret->universe = univ; + ret->ifmgr_ref = NULL; + + /* Now find the pointer to our if type in the Interface Universe */ + if ((ifmgr_types = g_hash_table_lookup(univ->iftypes, PI_IFMANAGER)) + != NULL) { + if ((ifmgr_ref=g_hash_table_lookup(ifmgr_types->interfaces + , typename)) != NULL) { + ret->ifmgr_ref = ifmgr_ref; + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + + /* Insert ourselves into our parent's table */ + g_hash_table_insert(univ->iftypes, g_strdup(typename), ret); + return ret; +} +static void +DelPILInterfaceType(PILInterfaceType*ift) +{ + PILInterfaceUniv* u = ift->universe; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceType(%s)" + , ift->typename); + } + STATFREE(interfacetype); + + PILValidateInterfaceUniv(NULL, u, NULL); + + /* + * RmAPILInterface refuses to remove the interface for the + * Interface manager, because it must be removed last. + * + * Otherwise we won't be able to unregister interfaces + * for other types of objects, and we'll be very confused. + */ + + g_hash_table_foreach_remove(ift->interfaces, RmAPILInterface, NULL); + + PILValidateInterfaceUniv(NULL, u, NULL); + + if (g_hash_table_size(ift->interfaces) > 0) { + gpointer key, iftype; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "DelPILInterfaceType(%s): table size (%d)" + , ift->typename, g_hash_table_size(ift->interfaces)); + } + if (g_hash_table_lookup_extended(ift->interfaces + , PI_IFMANAGER, &key, &iftype)) { + DelPILInterface((PILInterface*)iftype); + DELETE(key); + } + } + DELETE(ift->typename); + g_hash_table_destroy(ift->interfaces); + ZAP(ift); + DELETE(ift); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsAGHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer intf /* PILInterface* */ +, gpointer notused +) +{ + PILInterface* If = intf; + PILInterfaceType* Iftype = If->interfacetype; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface(0x%lx/%s)" + , (unsigned long)If, If->interfacename); + } + g_assert(IS_PILINTERFACE(If)); + + /* + * Don't remove the master interface manager this way, or + * Somebody will have a cow... + */ + if (If == If->ifmanager) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface: skipping (%s)" + , If->interfacename); + } + return FALSE; + } + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + PILunregister_interface(If); + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + DELETE(ifname); + DelPILInterface(If); + return TRUE; +} +static PIL_rc +RemoveAPILInterface(PILInterface* pif) +{ + PILInterfaceType* Iftype = pif->interfacetype; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterface(0x%lx/%s)" + , (unsigned long)pif, pif->interfacename); + } + if (g_hash_table_lookup_extended(Iftype->interfaces + , pif->interfacename, &key, (void*)&pif)) { + g_assert(IS_PILINTERFACE(pif)); + g_hash_table_remove(Iftype->interfaces, key); + RmAPILInterface(key, pif, NULL); + }else{ + g_assert_not_reached(); + } + + if (g_hash_table_size(Iftype->interfaces) == 0) { + /* The generic plugin handler doesn't want us to + * delete it's types... + */ + if (Iftype->ifmgr_ref->refcnt <= 1) { + RemoveAPILInterfaceType(Iftype, NULL); + } + } + return PIL_OK; +} + + +/* Register a Interface Interface (Interface manager) */ +static PIL_rc +ifmgr_register_interface(PILInterface* intf +, void** imports) +{ + PILInterfaceType* ift = intf->interfacetype; + PILInterfaceUniv* ifuniv = ift->universe; + PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Registering Implementation manager for" + " Interface type '%s'" + , intf->interfacename); + } + + ifops = intf->exports; + if (ifops->RegisterInterface == NULL + || ifops->UnRegisterInterface == NULL) { + PILLog(PIL_DEBUG, "ifmgr_register_interface(%s)" + ": NULL exported function pointer" + , intf->interfacename); + return PIL_INVAL; + } + + *imports = &IFManagerImports; + + if(g_hash_table_lookup(ifuniv->iftypes, intf->interfacename) == NULL){ + /* It registers itself into ifuniv automatically */ + NewPILInterfaceType(ifuniv,intf->interfacename, &IfExports + , NULL); + } + return PIL_OK; +} + +static gboolean +RemoveAllClients(PILInterface*interface, void * managerif) +{ + /* + * Careful! We can't remove ourselves this way... + * This gets taken care of as a special case in DelPILInterfaceUniv... + */ + if (managerif == interface) { + return FALSE; + } + PILunregister_interface(interface); + return TRUE; +} + +/* Unconditionally unregister a interface manager (InterfaceMgr Interface) */ +static PIL_rc +ifmgr_unregister_interface(PILInterface* interface) +{ + /* + * We need to unregister every interface we manage + */ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "ifmgr_unregister_interface(%s)" + , interface->interfacename); + } + + IfForEachClientRemove(interface, RemoveAllClients, interface); + return PIL_OK; +} + +/* Called to close the Interface manager for type Interface */ +static PIL_rc +close_ifmgr_interface(PILInterface* us, void* ud_interface) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "close_ifmgr_interface(%s)" + , us->interfacename); + } + /* Nothing much to do */ + return PIL_OK; +} + +/* Return the reference count for this interface */ +static int +IfRefCount(PILInterface * eifinfo) +{ + return eifinfo->refcnt; +} + +/* Modify the reference count for this interface */ +static int +IfIncrRefCount(PILInterface*eifinfo, int plusminus) +{ + if(DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfIncrRefCount(%d + %d )" + , eifinfo->refcnt, plusminus); + } + eifinfo->refcnt += plusminus; + if (eifinfo->refcnt <= 0) { + eifinfo->refcnt = 0; + /* Unregister this interface. */ + RemoveAPILInterface(eifinfo); + return 0; + } + return eifinfo->refcnt; +} + +#if 0 +static int +PluginRefCount(PILPlugin * pi) +{ + return pi->refcnt; +} +#endif + +static int +PluginIncrRefCount(PILPlugin*pi, int plusminus) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginIncrRefCount(%d + %d )" + , pi->refcnt, plusminus); + } + pi->refcnt += plusminus; + if (pi->refcnt <= 0) { + pi->refcnt = 0; + RemoveAPILPlugin(pi); + return 0; + } + return pi->refcnt; +} + +static PILInterface* +FindIF(PILPluginUniv* universe, const char *iftype, const char * ifname) +{ + PILInterfaceUniv* puniv; + PILInterfaceType* ptype; + + if (universe == NULL || (puniv = universe->ifuniv) == NULL + || (ptype=g_hash_table_lookup(puniv->iftypes, iftype))==NULL){ + return NULL; + } + return g_hash_table_lookup(ptype->interfaces, ifname); +} + +PIL_rc +PILIncrIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename +, int plusminus) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + if (intf) { + g_assert(IS_PILINTERFACE(intf)); + IfIncrRefCount(intf, plusminus); + return PIL_OK; + } + return PIL_NOPLUGIN; +} + +int +PILGetIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + return IfRefCount(intf); +} + +static void +IfForceUnregister(PILInterface *id) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForceUnRegister(%s)" + , id->interfacename); + } + RemoveAPILInterface(id); +} + +struct f_e_c_helper { + gboolean(*fun)(PILInterface* clientif, void * passalong); + void* passalong; +}; + +static gboolean IfForEachClientHelper(gpointer key +, gpointer iftype, gpointer helper_v); + +static gboolean +IfForEachClientHelper(gpointer unused, gpointer iftype, gpointer v) +{ + struct f_e_c_helper* s = (struct f_e_c_helper*)v; + + g_assert(IS_PILINTERFACE((PILInterface*)iftype)); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientHelper(%s)" + , ((PILInterface*)iftype)->interfacename); + } + + return s->fun((PILInterface*)iftype, s->passalong); +} + + +static void +IfForEachClientRemove +( PILInterface* mgrif +, gboolean(*f)(PILInterface* clientif, void * passalong) +, void* passalong /* usually PILInterface* */ +) +{ + PILInterfaceType* mgrt; + PILInterfaceUniv* u; + const char * ifname; + PILInterfaceType* clientt; + + struct f_e_c_helper h = {f, passalong}; + + + if (mgrif == NULL || (mgrt = mgrif->interfacetype) == NULL + || (u = mgrt->universe) == NULL + || (ifname = mgrif->interfacename) == NULL) { + PILLog(PIL_WARN, "bad parameters to IfForEachClientRemove"); + return; + } + + if ((clientt = g_hash_table_lookup(u->iftypes, ifname)) == NULL) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Interface manager [%s/%s] has no clients" + , PI_IFMANAGER, ifname); + } + return; + }; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientRemove(%s:%s)" + , mgrt->typename, clientt->typename); + } + if (clientt->ifmgr_ref != mgrif) { + PILLog(PIL_WARN, "Bad ifmgr_ref ptr in PILInterfaceType"); + return; + } + + g_hash_table_foreach_remove(clientt->interfaces, IfForEachClientHelper + , &h); +} + +static PIL_rc +PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* commonops) +{ + piinfo->pluginops = commonops; + + return PIL_OK; +} + +static PIL_rc +PILunregister_plugin(PILPlugin* piinfo) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_plugin(%s)" + , piinfo->plugin_name); + } + RemoveAPILPlugin(piinfo); + return PIL_OK; +} + +/* General logging function (not really UPPILS-specific) */ +static void +PILLog(PILLogLevel priority, const char * format, ...) +{ + va_list args; + GLogLevelFlags flags; + + switch(priority) { + case PIL_FATAL: flags = G_LOG_LEVEL_ERROR; + break; + case PIL_CRIT: flags = G_LOG_LEVEL_CRITICAL; + break; + + default: /* FALL THROUGH... */ + case PIL_WARN: flags = G_LOG_LEVEL_WARNING; + break; + + case PIL_INFO: flags = G_LOG_LEVEL_INFO; + break; + case PIL_DEBUG: flags = G_LOG_LEVEL_DEBUG; + break; + }; + va_start (args, format); + g_logv (G_LOG_DOMAIN, flags, format, args); + va_end (args); +} + +static const char * PIL_strerrmsgs [] = +{ "Success" +, "Invalid Parameters" +, "Bad plugin/interface type" +, "Duplicate entry (plugin/interface name/type)" +, "Oops happens" +, "No such plugin/interface/interface type" +}; + +const char * +PIL_strerror(PIL_rc rc) +{ + int irc = (int) rc; + static char buf[128]; + + if (irc < 0 || irc >= DIMOF(PIL_strerrmsgs)) { + snprintf(buf, sizeof(buf), "return code %d (?)", irc); + return buf; + } + return PIL_strerrmsgs[irc]; +} + +/* + * Returns the PATHname of the file containing the requested plugin + * This file handles PATH-like semantics from the rootdirlist. + * It is also might be the right place to put alias handing in the future... + */ +static char * +PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname) +{ + char * PluginPath = NULL; + char ** spath_component; + + for (spath_component = universe->rootdirlist; *spath_component + ; ++ spath_component) { + + if (PluginPath) { + g_free(PluginPath); PluginPath=NULL; + } + + PluginPath = g_strdup_printf("%s%s%s%s%s%s" + , *spath_component + , G_DIR_SEPARATOR_S + , plugintype + , G_DIR_SEPARATOR_S + , pluginname + , PLUGINSUFFIX); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Looking for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + + if (PluginExists(PluginPath) == PIL_OK) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Plugin path for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + return PluginPath; + } + /* FIXME: Put alias file processing here... */ + } + + /* Can't find 'em all... */ + return PluginPath; +} + +static PIL_rc +PluginExists(const char * PluginPath) +{ + /* Make sure we can read and execute the plugin file */ + /* This test is nice, because dlopen reasons aren't return codes */ + + if (access(PluginPath, R_OK) != 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin file %s does not exist" + , PluginPath); + } + return PIL_NOPLUGIN; + } + return PIL_OK; +} + +/* Return PIL_OK if the given plugin exists */ +PIL_rc +PILPluginExists(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname) +{ + PIL_rc rc; + char * path = PILPluginPath(piuniv, plugintype, pluginname); + + if (path == NULL) { + return PIL_INVAL; + } + rc = PluginExists(path); + DELETE(path); + return rc; +} + +/* + * PILLoadPlugin() - loads a plugin into memory and calls the + * initial() entry point in the plugin. + * + * + * Method: + * + * Construct file name of plugin. + * See if plugin exists. If not, fail with PIL_NOPLUGIN. + * + * Search Universe for plugin type + * If found, search plugin type for pluginname + * if found, fail with PIL_EXIST. + * Otherwise, + * Create new Plugin type structure + * Use lt_dlopen() on plugin to get lt_dlhandle for it. + * + * Construct the symbol name of the initialization function. + * + * Use lt_dlsym() to find the pointer to the init function. + * + * Call the initialization function. + */ +PIL_rc +PILLoadPlugin(PILPluginUniv* universe, const char * plugintype +, const char * pluginname +, void* plugin_user_data) +{ + PIL_rc rc; + char * PluginPath; + char * PluginSym; + PILPluginType* pitype; + PILPlugin* piinfo; + lt_dlhandle dlhand; + PILPluginInitFun initfun; + + PluginPath = PILPluginPath(universe, plugintype, pluginname); + + if ((rc=PluginExists(PluginPath)) != PIL_OK) { + DELETE(PluginPath); + return rc; + } + + if((pitype=g_hash_table_lookup(universe->PluginTypes, plugintype)) + != NULL) { + if ((piinfo = g_hash_table_lookup + ( pitype->Plugins, pluginname)) != NULL) { + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s already loaded" + , PluginPath); + } + DELETE(PluginPath); + return PIL_EXIST; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginType %s already present" + , plugintype); + } + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Creating PluginType for %s" + , plugintype); + } + /* Create a new PILPluginType object */ + pitype = NewPILPluginType(universe, plugintype); + } + + g_assert(pitype != NULL); + + /* + * At this point, we have a PILPluginType object and our + * plugin name is not listed in it. + */ + + dlhand = lt_dlopen(PluginPath); + + if (!dlhand) { + PILLog(PIL_WARN + , "lt_dlopen() failure on plugin %s/%s [%s]." + " Reason: [%s]" + , plugintype, pluginname + , PluginPath + , lt_dlerror()); + DELETE(PluginPath); + return PIL_NOPLUGIN; + } + DELETE(PluginPath); + /* Construct the magic init function symbol name */ + PluginSym = g_strdup_printf(PIL_FUNC_FMT + , plugintype, pluginname); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s init function: %s" + , plugintype, pluginname + , PluginSym); + } + + initfun = lt_dlsym(dlhand, PluginSym); + + if (initfun == NULL) { + PILLog(PIL_WARN + , "Plugin %s/%s init function (%s) not found" + , plugintype, pluginname, PluginSym); + DELETE(PluginSym); + lt_dlclose(dlhand); dlhand=NULL; + DelPILPluginType(pitype); + return PIL_NOPLUGIN; + } + DELETE(PluginSym); + /* + * Construct the new PILPlugin object + */ + piinfo = NewPILPlugin(pitype, pluginname, dlhand, initfun); + g_assert(piinfo != NULL); + g_hash_table_insert(pitype->Plugins, g_strdup(piinfo->plugin_name), piinfo); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s loaded and constructed." + , plugintype, pluginname); + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling init function in plugin %s/%s." + , plugintype, pluginname); + } + /* Save away the user_data for later */ + piinfo->ud_plugin = plugin_user_data; + /* initfun is allowed to change ud_plugin if they want */ + initfun(piinfo, universe->imports, plugin_user_data); + + return PIL_OK; +}/*PILLoadPlugin*/ + + + +#define REPORTERR(msg) PILLog(PIL_CRIT, "%s", msg) + +/* + * Register an interface. + * + * This function is exported to plugins for their use. + */ +static PIL_rc +PILRegisterInterface(PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Info (functions) exported + by this interface */ +, PILInterfaceFun close_func /* Close function for interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by + this interface (OP) */ +, void* ud_interface /* Optional user_data */ +) +{ + PILPluginUniv* piuniv; /* Universe this plugin is in */ + PILPluginType* pitype; /* Type of this plugin */ + PILInterfaceUniv* ifuniv; /* Universe this interface is in */ + PILInterfaceType*iftype; /* Type of this interface */ + PILInterface* ifinfo; /* Info about this Interface */ + + PILInterfaceType*ifmgrtype; /* PILInterfaceType for PI_IFMANAGER */ + PILInterface* ifmgrinfo; /* Interf info for "interfacetype" */ + const PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + /* of type "interfacetype" */ + PIL_rc rc; + + if ( piinfo == NULL + || (pitype = piinfo->plugintype) == NULL + || (piuniv = pitype->piuniv) == NULL + || (ifuniv = piuniv->ifuniv) == NULL + || ifuniv->iftypes == NULL + ) { + REPORTERR("bad parameters to PILRegisterInterface"); + return PIL_INVAL; + } + + /* Now we have lots of info, but not quite enough... */ + + if ((iftype = g_hash_table_lookup(ifuniv->iftypes, interfacetype)) + == NULL) { + + /* Try to autoload the needed interface handler */ + rc = PILLoadPlugin(piuniv, PI_IFMANAGER, interfacetype, NULL); + + /* See if the interface handler loaded like we expect */ + if ((iftype = g_hash_table_lookup(ifuniv->iftypes + , interfacetype)) == NULL) { + return PIL_BADTYPE; + } + } + if ((ifinfo = g_hash_table_lookup(iftype->interfaces, interfacename)) + != NULL) { + g_warning("Attempt to register duplicate interface: %s/%s" + , interfacetype, interfacename); + return PIL_EXIST; + } + /* + * OK... Now we know it is valid, and isn't registered... + * Let's locate the InterfaceManager registrar for this type + */ + if ((ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER)) + == NULL) { + REPORTERR("No " PI_IFMANAGER " type!"); + return PIL_OOPS; + } + if ((ifmgrinfo = g_hash_table_lookup(ifmgrtype->interfaces + , interfacetype)) == NULL) { + PILLog(PIL_CRIT + , "No interface manager for given type (%s) !" + , interfacetype); + return PIL_BADTYPE; + } + + ifops = ifmgrinfo->exports; + + /* Now we have all the information anyone could possibly want ;-) */ + + ifinfo = NewPILInterface(iftype, interfacename, Ops + , close_func, ud_interface, piinfo); + + g_assert(ifmgrinfo == ifinfo->ifmanager); + *interfaceid = ifinfo; + + /* Call the registration function for our interface type */ + rc = ifops->RegisterInterface(ifinfo, Imports); + + + /* Increment reference count of interface manager */ + IfIncrRefCount(ifmgrinfo, 1); + + /* Increment the ref count of the plugin that loaded us */ + PluginIncrRefCount(piinfo, 1); + + if (rc != PIL_OK) { + RemoveAPILInterface(ifinfo); + } + return rc; +} + +/* + * Method: + * + * Verify interface is valid. + * + * Call interface close function. + * + * Call interface manager unregister function + * + * Call RmAPILInterface to remove from InterfaceType table, and + * free interface object. + * + */ + +static PIL_rc +PILunregister_interface(PILInterface* id) +{ + PILInterfaceType* t; + PILInterfaceUniv* u; + PIL_rc rc; + PILInterface* ifmgr_info; /* Pointer to our interface handler */ + const PILInterfaceOps* exports; /* InterfaceManager operations for + * the type of interface we are + */ + + if ( id == NULL + || (t = id->interfacetype) == NULL + || (u = t->universe) == NULL + || id->interfacename == NULL) { + PILLog(PIL_WARN, "PILunregister_interface: bad interfaceid"); + return PIL_INVAL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_interface(%s/%s)" + , t->typename, id->interfacename); + } + PILValidateInterface(NULL, id, t); + PILValidateInterfaceType(NULL, t, u); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling InterfaceClose on %s/%s" + , t->typename, id->interfacename); + } + + /* Call the close function supplied by the interface */ + + if ((id->if_close != NULL) + && ((rc=id->if_close(id, id->ud_interface)) != PIL_OK)) { + PILLog(PIL_WARN, "InterfaceClose on %s/%s returned %s" + , t->typename, id->interfacename + , PIL_strerror(rc)); + } else { + rc = PIL_OK; + } + + /* Find the InterfaceManager that manages us */ + ifmgr_info = t->ifmgr_ref; + + g_assert(ifmgr_info != NULL); + + /* Find the exported functions from that IFIF */ + exports = ifmgr_info->exports; + + g_assert(exports != NULL && exports->UnRegisterInterface != NULL); + + /* Call the interface manager unregister function */ + exports->UnRegisterInterface(id); + + /* Decrement reference count of interface manager */ + IfIncrRefCount(ifmgr_info, -1); + /* This may make ifmgr_info invalid */ + ifmgr_info = NULL; + + /* Decrement the reference count of the plugin that loaded us */ + PluginIncrRefCount(id->loadingpi, -1); + + return rc; +} + +static PILInterfaceUniv* +NewPILInterfaceUniv(PILPluginUniv* piuniv) +{ + PILInterfaceUniv* ret = NEW(PILInterfaceUniv); + static int ltinityet = 0; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterfaceUniv(0x%lx)" + , (unsigned long)ret); + } + if (!ltinityet) { + ltinityet=1; + lt_dlinit(); + } + STATNEW(interfaceuniv); + ret->MagicNum = PIL_MAGIC_INTERFACEUNIV; + /* Make the two universes point at each other */ + ret->piuniv = piuniv; + piuniv->ifuniv = ret; + + ret->iftypes = g_hash_table_new(g_str_hash, g_str_equal); + + InterfaceManager_plugin_init(piuniv); + return ret; +} + +static void +DelPILInterfaceUniv(PILInterfaceUniv* ifuniv) +{ + PILInterfaceType* ifmgrtype; + g_assert(ifuniv!= NULL && ifuniv->iftypes != NULL); + PILValidateInterfaceUniv(NULL, ifuniv, NULL); + + STATFREE(interfaceuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv(0x%lx)" + , (unsigned long) ifuniv); + } + g_hash_table_foreach_remove(ifuniv->iftypes, RmAPILInterfaceType, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv: final cleanup"); + } + ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER); + RemoveAPILInterfaceType(ifmgrtype, ifmgrtype); + /* + * FIXME! need to delete the interface for PI_IFMANAGER last + * Right now, it seems to happen last, but I think that's + * coincidence... + */ + g_hash_table_destroy(ifuniv->iftypes); + ZAP(ifuniv); + DELETE(ifuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterfaceType +( gpointer typename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +) +{ + PILInterfaceType* Iftype = iftype; + PILInterfaceUniv* Ifuniv = Iftype->universe; + + /* + * We are not always called by g_hash_table_foreach_remove() + */ + + g_assert(IS_PILINTERFACETYPE(Iftype)); + PILValidateInterfaceUniv(NULL, Ifuniv, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType(%s)" + , (char*)typename); + } + if (iftype != notused + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType: skipping (%s)" + , (char*)typename); + } + return FALSE; + } + + DelPILInterfaceType(iftype); + DELETE(typename); + + return TRUE; +} + +static void +RemoveAPILInterfaceType(PILInterfaceType*Iftype, PILInterfaceType* t2) +{ + PILInterfaceUniv* Ifuniv = Iftype->universe; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType(%s)" + , Iftype->typename); + } + if (t2 != Iftype + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType: skipping (%s)" + , Iftype->typename); + return; + } + if (g_hash_table_lookup_extended(Ifuniv->iftypes + , Iftype->typename, &key, (gpointer)&Iftype)) { + + g_hash_table_remove(Ifuniv->iftypes, key); + RmAPILInterfaceType(key, Iftype, t2); + }else{ + g_assert_not_reached(); + } +} + +/* + * We need to write more functions: These include... + * + * Plugin functions: + * + * PILPluginPath() - returns path name for a given plugin + * + * PILPluginTypeList() - returns list of plugins of a given type + * + */ +static void free_dirlist(struct dirent** dlist, int n); + +static int qsort_string_cmp(const void *a, const void *b); + + +static void +free_dirlist(struct dirent** dlist, int n) +{ + int j; + for (j=0; j < n; ++j) { + if (dlist[j]) { + free(dlist[j]); + dlist[j] = NULL; + } + } + free(dlist); +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +#define FREE_DIRLIST(dlist, n) {free_dirlist(dlist, n); dlist = NULL;} + +static int +so_select (const struct dirent *dire) +{ + + const char obj_end [] = PLUGINSUFFIX; + const char *end = &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]; + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "In so_select: %s.", dire->d_name); + } + if (end < dire->d_name) { + return 0; + } + if (strcmp(end, obj_end) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "FILE %s looks like a plugin name." + , dire->d_name); + } + return 1; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "FILE %s Doesn't look like a plugin name [%s] " + "%zd %zd %s." + , dire->d_name, end + , sizeof(obj_end), strlen(dire->d_name) + , &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]); + } + + return 0; +} + +/* Return (sorted) list of available plugin names */ +static char** +PILPluginTypeListPlugins(PILPluginType* pitype +, int * picount /* Can be NULL ... */) +{ + const char * piclass = pitype->plugintype; + unsigned plugincount = 0; + char ** result = NULL; + int initoff = 0; + char ** pelem; + + /* Return all the plugins in all the directories in the PATH */ + + for (pelem=pitype->piuniv->rootdirlist; *pelem; ++pelem) { + int j; + GString* path; + int dircount; + struct dirent** files; + + + path = g_string_new(*pelem); + g_assert(piclass != NULL); + if (piclass) { + if (g_string_append_c(path, G_DIR_SEPARATOR) == NULL + || g_string_append(path, piclass) == NULL) { + g_string_free(path, 1); path = NULL; + return(NULL); + } + } + + files = NULL; + errno = 0; + dircount = scandir(path->str, &files + , SCANSEL_CAST so_select, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: Examining directory [%s]" + ": [%d] files matching [%s] suffix found." + , path->str, dircount, PLUGINSUFFIX); + } + g_string_free(path, 1); path=NULL; + + if (dircount <= 0) { + if (files != NULL) { + FREE_DIRLIST(files, dircount); + files = NULL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: skipping empty directory" + " in PILPluginTypeListPlugins()"); + } + continue; + } + + initoff = plugincount; + plugincount += dircount; + if (result == NULL) { + result = (char **) g_malloc((plugincount+1)*sizeof(char *)); + }else{ + result = (char **) g_realloc(result + , (plugincount+1)*sizeof(char *)); + } + + for (j=0; j < dircount; ++j) { + char* s; + unsigned slen = strlen(files[j]->d_name) + - STRLEN_CONST(PLUGINSUFFIX); + + s = g_malloc(slen+1); + strncpy(s, files[j]->d_name, slen); + s[slen] = EOS; + result[initoff+j] = s; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: plugin [%s] found" + , s); + } + } + FREE_DIRLIST(files, dircount); + files = NULL; + } + + if (picount != NULL) { + *picount = plugincount; + } + if (result) { + result[plugincount] = NULL; + /* Return them in sorted order... */ + qsort(result, plugincount, sizeof(char *), qsort_string_cmp); + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: NULL return" + " from PILPluginTypeListPlugins()"); + } + } + + + return result; +} +/* Return (sorted) list of available plugin names */ +char** +PILListPlugins(PILPluginUniv* u, const char * pitype +, int * picount /* Can be NULL ... */) +{ + PILPluginType* t; + if ((t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL) { + if (picount) { + *picount = 0; + } + t = NewPILPluginType(u, pitype); + if (!t) { + return NULL; + } + } + return PILPluginTypeListPlugins(t, picount); +} + +void +PILFreePluginList(char ** pluginlist) +{ + char ** ml = pluginlist; + + if (!ml) { + return; + } + + while (*ml != NULL) { + DELETE(*ml); + } + DELETE(pluginlist); +} + + +static void +PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype) +{ + const char * Key = key; + const PILPlugin * Plugin = plugin; + + g_assert(IS_PILPLUGIN(Plugin)); + + g_assert(Key == NULL || strcmp(Key, Plugin->plugin_name) == 0); + + g_assert (Plugin->refcnt >= 0 ); + + /* g_assert (Plugin->pluginops != NULL ); */ + g_assert (strcmp(Key, PI_IFMANAGER) == 0 || Plugin->dlinitfun != NULL ); + g_assert (strcmp(Plugin->plugin_name, PI_IFMANAGER) == 0 + || Plugin->dlhandle != NULL); + g_assert(Plugin->plugintype != NULL); + g_assert(IS_PILPLUGINTYPE(Plugin->plugintype)); + g_assert(pitype == NULL || pitype == Plugin->plugintype); +} + +static void +PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv) +{ + char * Key = key; + PILPluginType * Pitype = pitype; + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINTYPE(Pitype)); + g_assert(Muniv == NULL || IS_PILPLUGINUNIV(Muniv)); + g_assert(Key == NULL || strcmp(Key, Pitype->plugintype) == 0); + g_assert(IS_PILPLUGINUNIV(Pitype->piuniv)); + g_assert(piuniv == NULL || piuniv == Pitype->piuniv); + g_assert(Pitype->Plugins != NULL); + g_hash_table_foreach(Pitype->Plugins, PILValidatePlugin, Pitype); +} +static void +PILValidatePluginUniv(gpointer key, gpointer piuniv, gpointer dummy) +{ + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINUNIV(Muniv)); + g_assert(Muniv->rootdirlist != NULL); + g_assert(Muniv->imports != NULL); + g_hash_table_foreach(Muniv->PluginTypes, PILValidatePluginType, piuniv); + PILValidateInterfaceUniv(NULL, Muniv->ifuniv, piuniv); +} +static void +PILValidateInterface(gpointer key, gpointer interface, gpointer iftype) +{ + char * Key = key; + PILInterface* Interface = interface; + g_assert(IS_PILINTERFACE(Interface)); + g_assert(Key == NULL || strcmp(Key, Interface->interfacename) == 0); + g_assert(IS_PILINTERFACETYPE(Interface->interfacetype)); + g_assert(iftype == NULL || iftype == Interface->interfacetype); + g_assert(Interface->ifmanager!= NULL); + g_assert(IS_PILINTERFACE(Interface->ifmanager)); + g_assert(strcmp(Interface->interfacetype->typename + , Interface->ifmanager->interfacename)== 0); + g_assert(Interface->exports != NULL); +} +static void +PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv) +{ + char * Key = key; + PILInterfaceType* Iftype = iftype; + g_assert(IS_PILINTERFACETYPE(Iftype)); + g_assert(Key == NULL || strcmp(Key, Iftype->typename) == 0); + g_assert(ifuniv == NULL || Iftype->universe == ifuniv); + g_assert(Iftype->interfaces != NULL); + g_assert(Iftype->ifmgr_ref != NULL); + g_assert(IS_PILINTERFACE(Iftype->ifmgr_ref)); + g_assert(Key == NULL || strcmp(Key, Iftype->ifmgr_ref->interfacename) == 0); + + g_hash_table_foreach(Iftype->interfaces, PILValidateInterface, iftype); +} +static void +PILValidateInterfaceUniv(gpointer key, gpointer ifuniv, gpointer piuniv) +{ + PILInterfaceUniv* Ifuniv = ifuniv; + PILPluginUniv* Pluginuniv = piuniv; + g_assert(IS_PILINTERFACEUNIV(Ifuniv)); + g_assert(Pluginuniv == NULL || IS_PILPLUGINUNIV(Pluginuniv)); + g_assert(piuniv == NULL || piuniv == Ifuniv->piuniv); + g_hash_table_foreach(Ifuniv->iftypes, PILValidateInterfaceType, ifuniv); +} + +#define PRSTAT(type) { \ + PILLog(PIL_INFO, "Plugin system objects (" #type "): " \ + "\tnew %ld free \%ld current %ld" \ + , PILstats.type.news \ + , PILstats.type.frees \ + , PILstats.type.news - PILstats.type.frees); \ +} +void +PILLogMemStats(void) +{ + PRSTAT(plugin); + PRSTAT(pitype); + PRSTAT(piuniv); + PRSTAT(interface); + PRSTAT(interfacetype); + PRSTAT(interfaceuniv); +} + +/* + * Function for logging with the given logging function + * The reason why it's here is so we can get printf arg checking + * You can't get that when you call a function pointer directly. + */ +void +PILCallLog(PILLogFun logfun, PILLogLevel priority, const char * fmt, ...) +{ + va_list args; + char * str; + int err = errno; + + va_start (args, fmt); + str = g_strdup_vprintf(fmt, args); + va_end (args); + logfun(priority, "%s", str); + g_free(str); + errno = err; +} diff --git a/lib/pils/test.c b/lib/pils/test.c new file mode 100644 index 0000000..c2cdb26 --- /dev/null +++ b/lib/pils/test.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * 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 + * + */ +/* + * Sample Interface manager. + */ +#define PIL_PLUGINTYPE test +#define PIL_PLUGINTYPENAME "test" +#define PIL_PLUGIN test +#define PIL_PLUGINNAME "test" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include <pils/interface.h> + +PIL_PLUGIN_BOILERPLATE("1.0", DebugFlag, Ourclose) + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* OurPIImports; /* Imported plugin funs */ +static PILPlugin* OurPlugin; /* Our plugin info */ +static PILInterfaceImports* OurIfImports; /* Interface imported funs */ +static PILInterface* OurIf; /* Pointer to interface info */ + +static void +Ourclose (PILPlugin* us) +{ +} + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least the interface management universe ;-). + * + */ +static PILInterfaceOps OurIfOps = { + /* FIXME -- put some in here !! */ +}; + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +static PIL_rc +IfClose(PILInterface*intf, void* ud_interface) +{ + OurPIImports->log(PIL_INFO, "In Ifclose (test plugin)"); + return PIL_OK; +} + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + OurPIImports = imports; + OurPlugin = us; + + imports->log(PIL_INFO, "Plugin %s: user_ptr = %lx" + , PIL_PLUGINNAME, (unsigned long)user_ptr); + + imports->log(PIL_INFO, "Registering ourselves as a plugin"); + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + imports->log(PIL_INFO, "Registering our interfaces"); + + /* Register our interfaces */ + ret = imports->register_interface + ( us + , PIL_PLUGINTYPENAME + , PIL_PLUGINNAME + , &OurIfOps /* Exported interface operations */ + , IfClose /* Interface Close function */ + , &OurIf + , (void*)&OurIfImports + , NULL); + imports->log(PIL_INFO, "test init function: returning %d" + , ret); + + return ret; +} diff --git a/lib/plugins/InterfaceMgr/HBauth.c b/lib/plugins/InterfaceMgr/HBauth.c new file mode 100644 index 0000000..eae22cf --- /dev/null +++ b/lib/plugins/InterfaceMgr/HBauth.c @@ -0,0 +1,171 @@ +/* + * Heartbeat authentication interface manager + * + * Copyright 2001 Alan Robertson <alanr@unix.sh> + * Licensed under the GNU Lesser General Public License + * + * 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 + * + */ + */ +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGIN HBauth + +#define PIN(f) #f +#define PIN2(f) PIN(f) +#define PIN3 PIN2(PIL_PLUGIN) +#define PIT PIN2(PIL_PLUGINTYPE) + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include <lha_internal.h> +#include <pils/interface.h> +#include <stdio.h> + +PIL_PLUGIN_BOILERPLATE2("1.0", AuthDebugFlag) + + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* AuthPIImports; /* Imported plugin fcns */ +static PILPlugin* AuthPlugin; /* Our plugin info */ +static PILInterfaceImports* AuthIfImports; /* Interface imported fcns */ +static PILInterface* AuthIf; /* Our Auth Interface info */ + +/* Our exported auth interface management functions */ +static PIL_rc RegisterAuthIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterAuthIF(PILInterface*iifinfo); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client authentication interfaces + * + */ +static PILInterfaceOps AuthIfOps = +{ RegisterAuthIF +, UnregisterAuthIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point at a GHashTable for us + * to put plugin into when they show up, and drop from when + * they disappear. + * + * We need to think more carefully about the way for us to get + * the user_ptr from the global environment. + * + * We need to think more carefully about how interface registration + * etc. interact with plugin loading, reference counts, etc. and how + * the application that uses us (i.e., heartbeat) interacts with us. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * I think the global system should handle thread-safety. + */ + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + if (user_ptr == NULL) { + imports->log(PIL_CRIT + , "Interface Manager %s requires non-NULL " + " user pointer (to GHashTable) at initialization" + , PIN3); + return PIL_INVAL; + } + + AuthPIImports = imports; + AuthPlugin = us; + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + + /* Register our interfaces */ + ret = imports->register_interface(us + , PIT + , PIN3 + , &AuthIfOps + , NULL + , &AuthIf /* Our interface object pointer */ + , (void**)&AuthIfImports /* Interface-imported functions */ + , user_ptr); + return ret; +} + +/* + * We get called for every authentication interface that gets registered. + * + * It's our job to make the authentication interface that's + * registering with us available to the system. + * + * We do that by adding it to a g_hash_table of authentication + * plugin. The rest of the system takes it from there... + * The key is the authentication method, and the data + * is a pointer to the functions the method exports. + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterAuthIF(PILInterface* intf, void** imports) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + + g_assert(authtbl != NULL); + + /* Reference count should now be one */ + g_assert(intf->refcnt == 1); + g_hash_table_insert(authtbl, intf->interfacename, intf->exports); + + return PIL_OK; +} + +/* Unregister a client authentication interface - + * We get called from the interface mgmt sys when someone requests that + * a interface be unregistered. + */ +static PIL_rc +UnregisterAuthIF(PILInterface*intf) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + g_assert(authtbl != NULL); + + intf->refcnt--; + g_assert(intf->refcnt >= 0); + if (intf->refcnt <= 0) { + g_hash_table_remove(authtbl, intf->interfacetype); + } + return PIL_OK; +} + diff --git a/lib/plugins/InterfaceMgr/Makefile.am b/lib/plugins/InterfaceMgr/Makefile.am new file mode 100644 index 0000000..86b88d1 --- /dev/null +++ b/lib/plugins/InterfaceMgr/Makefile.am @@ -0,0 +1,33 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +## libraries + +plugindir = $(libdir)/@HB_PKG@/plugins/InterfaceMgr +plugin_LTLIBRARIES = generic.la + +generic_la_SOURCES = generic.c +generic_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/plugins/InterfaceMgr/generic.c b/lib/plugins/InterfaceMgr/generic.c new file mode 100644 index 0000000..6ddad3b --- /dev/null +++ b/lib/plugins/InterfaceMgr/generic.c @@ -0,0 +1,452 @@ +/* + * + * Generic interface (implementation) manager + * + * Copyright 2001 Alan Robertson <alanr@unix.sh> + * Licensed under the GNU Lesser General Public License + * + * This manager will manage any number of types of interfaces. + * + * This means that when any implementations of our client interfaces register + * or unregister, it is us that makes their interfaces show up in the outside + * world. + * + * And, of course, we have to do this in a very generic way, since we have + * no idea about the client programs or interface types, or anything else. + * + * We do that by getting a parameter passed to us which tell us the names + * of the interface types we want to manage, and the address of a GHashTable + * for each type that we put the implementation in when they register + * themselves. + * + * So, each type of interface that we manage gets its own private + * GHashTable of the implementations of that type that are currently + * registered. + * + * For example, if we manage communication modules, their exported + * interfaces will be registered in a hash table. If we manage + * authentication modules, they'll have their (separate) hash table that + * their exported interfaces are registered in. + * + * + * 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 + * + */ + +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGINTYPE_S "InterfaceMgr" +#define PIL_PLUGIN generic +#define PIL_PLUGIN_S "generic" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are an interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE +#define ENABLE_PIL_DEFS_PRIVATE + +#include <lha_internal.h> +#include <pils/generic.h> + +#include <stdio.h> + +PIL_PLUGIN_BOILERPLATE("1.0", GenDebugFlag, CloseGeneralPluginManager) + +/* + * Key is interface type, value is a PILGenericIfMgmtRqst. + * The key is g_strdup()ed, but the struct is not copied. + */ + +static gboolean FreeAKey(gpointer key, gpointer value, gpointer data); + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* GenPIImports; /* Imported plugin fcns */ +static PILPlugin* GenPlugin; /* Our plugin info */ +static PILInterfaceImports* GenIfImports; /* Interface imported fcns */ + +/* Our exported generic interface management functions */ +static PIL_rc RegisterGenIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterGenIF(PILInterface*iifinfo); + +static PIL_rc CloseGenInterfaceManager(PILInterface*, void* info); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client implementations + */ +static PILInterfaceOps GenIfOps = +{ RegisterGenIF +, UnregisterGenIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point to NULL-terminated array of + * PILGenericIfMgmtRqst structs. + * + * These requests have pointers to GHashTables for us + * to put plugins into when they show up, and drop from when + * they disappear. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * IMHO the global system should handle thread-safety. + */ +static PIL_rc AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + PILGenericIfMgmtRqst* user_req; + PILGenericIfMgmtRqst* curreq; + GHashTable* MasterTable = NULL; + /* + * Force the compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + GenPIImports = imports; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: initializing.", PIL_PLUGIN_S); + } + + if (user_ptr == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "%s Interface Manager requires non-NULL " + " PILGenericIfMgmtRqst user pointer at initialization." + , PIL_PLUGIN_S); + return PIL_INVAL; + } + + GenPlugin = us; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering as a plugin." + , PIL_PLUGIN_S); + } + + user_req = user_ptr; + MasterTable = g_hash_table_new(g_str_hash, g_str_equal); + us->ud_plugin = MasterTable; /* Override passed value */ + + /* Register ourselves as a plugin */ + + if ((ret = imports->register_plugin(us, &OurPIExports)) != PIL_OK) { + PILCallLog(imports->log, PIL_CRIT + , "IF manager %s unable to register as plugin (%s)" + , PIL_PLUGIN_S, PIL_strerror(ret)); + + return ret; + } + + /* + * Register to manage implementations + * for all the interface types we've been asked to manage. + */ + + for(curreq = user_req; curreq->iftype != NULL; ++curreq) { + PIL_rc newret; + + newret = AddAnInterfaceType(us, MasterTable, curreq); + + if (newret != PIL_OK) { + ret = newret; + } + } + + /* + * Our plugin and all our registered plugin types + * have ud_plugin pointing at MasterTable. + */ + + return ret; +} + +static PIL_rc +AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req) +{ + PIL_rc rc; + PILInterface* GenIf; /* Our Generic Interface info*/ + + g_assert(MasterTable != NULL); + g_hash_table_insert(MasterTable, g_strdup(req->iftype), req); + + if (req->ifmap == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s has NULL" + " ifmap pointer address." + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + if ((*req->ifmap) != NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s GHashTable pointer" + " was not initialized to NULL" + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering ourselves" + " to manage interface type %s" + , PIL_PLUGIN_S, req->iftype); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: ifmap: 0x%lx callback: 0x%lx" + " imports: 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)req->ifmap + , (unsigned long)req->callback + , (unsigned long)req->importfuns); + } + + /* Create the hash table to communicate with this client */ + *(req->ifmap) = g_hash_table_new(g_str_hash, g_str_equal); + + rc = GenPIImports->register_interface(us + , PIL_PLUGINTYPE_S + , req->iftype /* the iftype we're managing here */ + , &GenIfOps + , CloseGenInterfaceManager + , &GenIf + , (void*)&GenIfImports + , MasterTable); /* Point ud_interface to MasterTable */ + + /* We don't ever want to be unloaded... */ + GenIfImports->ModRefCount(GenIf, +100); + + if (rc != PIL_OK) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "Generic interface manager %s: unable to register" + " to manage interface type %s: %s" + , PIL_PLUGIN_S, req->iftype + , PIL_strerror(rc)); + } + return rc; +} + +static void +CloseGeneralPluginManager(PILPlugin* us) +{ + + GHashTable* MasterTable = us->ud_plugin; + int count; + + g_assert(MasterTable != NULL); + + /* + * All our clients have already been shut down automatically + * This is the final shutdown for us... + */ + + + /* There *shouldn't* be any keys in there ;-) */ + + if ((count=g_hash_table_size(MasterTable)) > 0) { + + /* But just in case there are... */ + g_hash_table_foreach_remove(MasterTable, FreeAKey, NULL); + } + g_hash_table_destroy(MasterTable); + us->ud_plugin = NULL; + return; +} + +/* + * We get called for every time an implementation registers itself as + * implementing one of the kinds of interfaces we manage. + * + * It's our job to make the implementation that's + * registering with us available to the system. + * + * We do that by adding it to a GHashTable for its interface type + * Our users in the rest of the system takes it from there... + * + * The key to the GHashTable is the implementation name, and the data is + * a pointer to the information the implementation exports. + * + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterGenIF(PILInterface* intf, void** imports) +{ + PILGenericIfMgmtRqst* ifinfo; + GHashTable* MasterTable = intf->ifmanager->ud_interface; + + g_assert(MasterTable != NULL); + + /* Reference count should now be one */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: interface %s/%s registering." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + g_assert(intf->refcnt == 1); + /* + * We need to add it to the table that goes with this particular + * type of interface. + */ + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + GHashTable* ifmap = *(ifinfo->ifmap); + + g_hash_table_insert(ifmap, intf->interfacename,intf->exports); + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Inserted interface [%s] in hash" + " table @ 0x%08lx" + , PIL_PLUGIN_S, intf->interfacename + , (unsigned long)ifmap); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Exports are here: 0x%08x" + , PIL_PLUGIN_S + , GPOINTER_TO_UINT(intf->exports)); + } + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_REGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + *imports = ifinfo->importfuns; + + return PIL_OK; + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "RegisterGenIF: interface type %s not found" + , intf->interfacename); + } + return PIL_INVAL; +} + +/* Unregister an implementation - + * We get called from the interface management system when someone + * has requested that an implementation of a client interface be + * unregistered. + */ +static PIL_rc +UnregisterGenIF(PILInterface*intf) +{ + GHashTable* MasterTable = intf->ifmanager->ud_interface; + PILGenericIfMgmtRqst* ifinfo; + + g_assert(MasterTable != NULL); + g_assert(intf->refcnt >= 0); + /* + * Go through the "master table" and find client table, + * notify client we're about to remove this entry, then + * then remove this entry from it. + */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: unregistering interface %s/%s." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + + GHashTable* ifmap = *(ifinfo->ifmap); + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_UNREGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + /* Remove the client entry from master table */ + g_hash_table_remove(ifmap, intf->interfacename); + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "UnregisterGenIF: interface type %s not found" + , intf->interfacename); + return PIL_INVAL; + } + return PIL_OK; +} + +/* + * Close down the generic interface manager. + */ +static PIL_rc +CloseGenInterfaceManager(PILInterface*intf, void* info) +{ + void* key; + void* data; + GHashTable* MasterTable = intf->ud_interface; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_INFO + , "In CloseGenInterFaceManager on %s/%s (MasterTable: 0x%08lx)" + , intf->interfacetype->typename, intf->interfacename + , (unsigned long)MasterTable); + } + + + g_assert(MasterTable != NULL); + if (g_hash_table_lookup_extended(MasterTable + , intf->interfacename, &key, &data)) { + PILGenericIfMgmtRqst* ifinfo = data; + g_hash_table_destroy(*(ifinfo->ifmap)); + *(ifinfo->ifmap) = NULL; + g_hash_table_remove(MasterTable, key); + g_free(key); + }else{ + g_assert_not_reached(); + } + return PIL_OK; +} + +static gboolean +FreeAKey(gpointer key, gpointer value, gpointer data) +{ + g_free(key); + return TRUE; +} diff --git a/lib/plugins/Makefile.am b/lib/plugins/Makefile.am new file mode 100644 index 0000000..21827cd --- /dev/null +++ b/lib/plugins/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = InterfaceMgr stonith lrm compress diff --git a/lib/plugins/compress/Makefile.am b/lib/plugins/compress/Makefile.am new file mode 100644 index 0000000..3a3193a --- /dev/null +++ b/lib/plugins/compress/Makefile.am @@ -0,0 +1,52 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +if BUILD_ZLIB_COMPRESS_MODULE +zlibmodule = zlib.la +endif + +if BUILD_BZ2_COMPRESS_MODULE +bz2module = bz2.la +endif + +SUBDIRS = + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +AM_CFLAGS = @CFLAGS@ + +## libraries + +halibdir = $(libdir)/@HB_PKG@ +plugindir = $(halibdir)/plugins/compress +plugin_LTLIBRARIES = $(zlibmodule) $(bz2module) + +zlib_la_SOURCES = zlib.c +zlib_la_LDFLAGS = -export-dynamic -module -avoid-version -lz +zlib_la_LIBADD = $(top_builddir)/replace/libreplace.la + +bz2_la_SOURCES = bz2.c +bz2_la_LDFLAGS = -export-dynamic -module -avoid-version -lbz2 +bz2_la_LIBADD = $(top_builddir)/replace/libreplace.la + diff --git a/lib/plugins/compress/bz2.c b/lib/plugins/compress/bz2.c new file mode 100644 index 0000000..2eab116 --- /dev/null +++ b/lib/plugins/compress/bz2.c @@ -0,0 +1,142 @@ + /* bz2.c: compression module using bz2 for heartbeat. + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * 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 + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN bz2 +#define PIL_PLUGIN_S "bz2" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <lha_internal.h> +#include <stdio.h> +#include <pils/plugin.h> +#include <compress.h> +#include <bzlib.h> +#include <clplumbing/cl_log.h> +#include <string.h> + + +static struct hb_compress_fns bz2Ops; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +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 + , &bz2Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +bz2_compress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffCompress(dest, &tmpdestlen, src, srclen, 1, 0, 30); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static int +bz2_decompress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffDecompress(dest, &tmpdestlen, src, srclen, 1, 0); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static const char* +bz2_getname(void) +{ + return "bz2"; +} + +static struct hb_compress_fns bz2Ops ={ + bz2_compress, + bz2_decompress, + bz2_getname, +}; diff --git a/lib/plugins/compress/zlib.c b/lib/plugins/compress/zlib.c new file mode 100644 index 0000000..5958966 --- /dev/null +++ b/lib/plugins/compress/zlib.c @@ -0,0 +1,135 @@ + /* zlib.c: compression module using zlib for heartbeat. + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * 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 + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN zlib +#define PIL_PLUGIN_S "zlib" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <lha_internal.h> +#include <pils/plugin.h> +#include <compress.h> +#include <zlib.h> +#include <clplumbing/cl_log.h> +#include <string.h> + + +static struct hb_compress_fns zlibOps; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +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 + , &zlibOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +zlib_compress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = compress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + return HA_OK; + +} + +static int +zlib_decompress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = uncompress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + + return HA_OK; +} + +static const char* +zlib_getname(void) +{ + return "zlib"; +} + +static struct hb_compress_fns zlibOps ={ + zlib_compress, + zlib_decompress, + zlib_getname, +}; diff --git a/lib/plugins/lrm/Makefile.am b/lib/plugins/lrm/Makefile.am new file mode 100644 index 0000000..fd24579 --- /dev/null +++ b/lib/plugins/lrm/Makefile.am @@ -0,0 +1,58 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in +if UPSTART +SUBDIRS = dbus +endif + +LRM_DIR = lrm +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl +if UPSTART +INCLUDES += $(DBUS_CFLAGS) +endif + +halibdir = $(libdir)/@HB_PKG@ +havarlibdir = $(localstatedir)/lib/@HB_PKG@ +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/lrm/liblrm.la \ + $(GLIBLIB) + +plugindir = $(halibdir)/plugins/RAExec + +plugin_LTLIBRARIES = lsb.la ocf.la heartbeat.la +if UPSTART +plugin_LTLIBRARIES += upstart.la +endif + +lsb_la_SOURCES = raexeclsb.c +lsb_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +ocf_la_SOURCES = raexecocf.c +ocf_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +heartbeat_la_SOURCES = raexechb.c +heartbeat_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +if UPSTART +upstart_la_SOURCES = raexecupstart.c upstart-dbus.c +upstart_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version \ + $(DBUS_LIBS) +endif diff --git a/lib/plugins/lrm/dbus/Makefile.am b/lib/plugins/lrm/dbus/Makefile.am new file mode 100644 index 0000000..ec93436 --- /dev/null +++ b/lib/plugins/lrm/dbus/Makefile.am @@ -0,0 +1,16 @@ +if UPSTART +BINDINGS=Upstart_Instance.h \ + Upstart_Job.h \ + Upstart.h + +all-local: + for header in $(BINDINGS); do \ + input=com.ubuntu.`echo $$header | sed 's/\.h//' | tr _ .`.xml; \ + $(DBUS_BINDING_TOOL) --mode=glib-client $$input > $$header; \ + done + +clean-local: + rm -f $(BINDINGS) + +EXTRA_DIST = *.xml +endif diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml new file mode 100644 index 0000000..d4f7ab2 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.Instance.xml - interface definition for interface + objects + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node> + <interface name="com.ubuntu.Upstart0_6.Instance"> + <!-- Methods to directly control instances. Unlike the equivalent methods + for a Job, these are not permitted to pass or set environment. --> + <method name="Start"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Stop"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Restart"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + + <!-- Basic information about an Instance --> + <property name="name" type="s" access="read" /> + <property name="goal" type="s" access="read" /> + <property name="state" type="s" access="read" /> + <property name="processes" type="a(si)" access="read" /> + </interface> +</node> diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml new file mode 100644 index 0000000..27f47a1 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.Job.xml - interface definition for job objects + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node> + <interface name="com.ubuntu.Upstart0_6.Job"> + <!-- Get object paths for instances, while you can figure these out too, + it's still better form to use these --> + <method name="GetInstance"> + <arg name="env" type="as" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="GetInstanceByName"> + <arg name="name" type="s" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="GetAllInstances"> + <arg name="instances" type="ao" direction="out" /> + </method> + + <!-- Signals for changes to the instance list for a job --> + <signal name="InstanceAdded"> + <arg name="instance" type="o" /> + </signal> + <signal name="InstanceRemoved"> + <arg name="instance" type="o" /> + </signal> + + <!-- Job control; the environment arguments are used for both instance + selection and for passing environment to the processes of the job. --> + <method name="Start"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="Stop"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Restart"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + + <!-- Basic information about a Job --> + <property name="name" type="s" access="read" /> + <property name="description" type="s" access="read" /> + <property name="author" type="s" access="read" /> + <property name="version" type="s" access="read" /> + </interface> +</node> diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml new file mode 100644 index 0000000..a4331cd --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.xml - interface definition for manager object + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node name="/com/ubuntu/Upstart"> + <interface name="com.ubuntu.Upstart0_6"> + <!-- Reload all configuration sources --> + <method name="ReloadConfiguration"> + </method> + + <!-- Get object paths for jobs, while you can figure them out, it's + better form to use these --> + <method name="GetJobByName"> + <arg name="name" type="s" direction="in" /> + <arg name="job" type="o" direction="out" /> + </method> + <method name="GetAllJobs"> + <arg name="jobs" type="ao" direction="out" /> + </method> + + <!-- Signals for changes to the job list --> + <signal name="JobAdded"> + <arg name="job" type="o" /> + </signal> + <signal name="JobRemoved"> + <arg name="job" type="o" /> + </signal> + + <!-- Event emission --> + <method name="EmitEvent"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="name" type="s" direction="in" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + </method> + + <!-- Basic information about Upstart --> + <property name="version" type="s" access="read" /> + <property name="log_priority" type="s" access="readwrite" /> + </interface> +</node> diff --git a/lib/plugins/lrm/raexechb.c b/lib/plugins/lrm/raexechb.c new file mode 100644 index 0000000..f9f1eb9 --- /dev/null +++ b/lib/plugins/lrm/raexechb.c @@ -0,0 +1,416 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: raexechb.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN heartbeat +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "heartbeat" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +static const char * RA_PATH = HB_RA_DIR; + +static const char meta_data_template[] = +"<?xml version=\"1.0\"?>\n" +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" +"<resource-agent name=\"%s\">\n" +"<version>1.0</version>\n" +"<longdesc lang=\"en\">\n" +"%s" +"</longdesc>\n" +"<shortdesc lang=\"en\">%s</shortdesc>\n" +"<parameters>\n" +"<parameter name=\"1\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the first argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[1]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"2\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the second argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[2]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"3\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the third argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[3]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"4\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the fourth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[4]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"5\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the fifth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[5]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"</parameters>\n" +"<actions>\n" +"<action name=\"start\" timeout=\"15\" />\n" +"<action name=\"stop\" timeout=\"15\" />\n" +"<action name=\"status\" timeout=\"15\" />\n" +"<action name=\"monitor\" timeout=\"15\" interval=\"15\" start-delay=\"15\" />\n" +"<action name=\"meta-data\" timeout=\"5\" />\n" +"</actions>\n" +"<special tag=\"heartbeart\">\n" +"</special>\n" +"</resource-agent>\n"; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; +static int idebuglevel = 0; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +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); + + if (getenv(HADEBUGVAL) != NULL && atoi(getenv(HADEBUGVAL)) > 0 ) { + idebuglevel = atoi(getenv(HADEBUGVAL)); + cl_log(LOG_DEBUG, "LRM debug level set to %d", idebuglevel); + } + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + uniform_ret_execra_t exit_value; + GString * debug_info; + char * optype_tmp = NULL; + int index_tmp = 0; + + /* How to generate the meta-data? There is nearly no value + * information in meta-data build up in current way. + * Should directly add meta-data to the script itself? + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data") ) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for heartbeat scripts. + */ + if ( 0 == STRNCMP_CONST(op_type, "monitor") ) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if (0 > prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv)) { + cl_log(LOG_ERR, "HB RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + if (idebuglevel > 1) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: heartbeat::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + + switch (errno) { + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + exit_value = EXECRA_NOT_INSTALLED; + break; + default: + exit_value = EXECRA_EXEC_UNKNOWN_ERROR; + } + exit(exit_value); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len, index; + int ht_size = 0; + int param_num = 0; + char buf_tmp[20]; + void * value_tmp; + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + /* Now suppose the parameter format stored in Hashtabe is as like as + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + /* Notice: if ht_size==0, no actual arguments except op_type */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { +/* cl_log(LOG_WARNING, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + cl_log(LOG_WARNING, "search key=%s.", buf_tmp); +*/ continue; + } + param_num ++; + params_argv[param_num] = g_strdup((char *)value_tmp); + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the last argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[param_num+1] = g_strndup(op_type, tmp_len); + /* Add the teminating NULL pointer */ + params_argv[param_num+2] = NULL; + return 0; +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + + /* Now there is no formal related specification for Heartbeat RA + * scripts. Temporarily deal as LSB init script. + */ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + with LSB standard. + */ + const char * stop_pattern1 = "*stopped*", + * stop_pattern2 = "*not*running*", + * running_pattern1 = "*running*", + * running_pattern2 = "*OK*"; + char * lower_std_output = NULL; + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (std_output == NULL ) { + cl_log(LOG_WARNING, "No status output from the (hb) resource agent."); + return EXECRA_NOT_RUNNING; + } + + if (idebuglevel) { + cl_log(LOG_DEBUG, "RA output was: [%s]", std_output); + } + + lower_std_output = g_ascii_strdown(std_output, -1); + + if ( TRUE == g_pattern_match_simple(stop_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(stop_pattern2 + , lower_std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched stopped pattern" + " [%s] or [%s]" + , std_output + , stop_pattern1 + , stop_pattern2); + } + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } else if ( TRUE == g_pattern_match_simple(running_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(running_pattern2 + , std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched running" + " pattern [%s] or [%s]" + , std_output, running_pattern1 + , running_pattern2); + } + ret_execra = EXECRA_OK; /* running */ + } else { + /* It didn't say it was running - must be stopped */ + cl_log(LOG_DEBUG, "RA output [%s] didn't match any pattern" + , std_output); + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } + g_free(lower_std_output); + } + /* For non-status operation return code */ + if (ret_execra < 0) { + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + return get_runnable_list(RA_PATH, rsc_info); +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + GString * meta_data; + + meta_data = g_string_new(""); + g_string_sprintf( meta_data, meta_data_template, rsc_type + , rsc_type, rsc_type); + return meta_data->str; +} +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexeclsb.c b/lib/plugins/lrm/raexeclsb.c new file mode 100644 index 0000000..46d7546 --- /dev/null +++ b/lib/plugins/lrm/raexeclsb.c @@ -0,0 +1,609 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: raexeclsb.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ +/* + * Todo + * 1) Use flex&bison to make the analysis functions for lsb compliant comment? + * 2) Support multiple paths which contain lsb compliant RAs. + * 3) Optional and additional actions analysis? + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> +#include <libgen.h> + +#include <libxml/entities.h> + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN lsb +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "lsb" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + + +/* meta-data template for lsb scripts */ +/* Note: As for optional actions -- extracted from lsb standard. + * The reload and the try-restart options are optional. Other init script + * actions may be defined by the init script. + */ +#define meta_data_template \ +"<?xml version=\"1.0\"?>\n"\ +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"\ +"<resource-agent name=\"%s\" version=\"0.1\">\n"\ +" <version>1.0</version>\n"\ +" <longdesc lang=\"en\">\n"\ +" %s"\ +" </longdesc>\n"\ +" <shortdesc lang=\"en\">%s</shortdesc>\n"\ +" <parameters>\n"\ +" </parameters>\n"\ +" <actions>\n"\ +" <action name=\"start\" timeout=\"15\" />\n"\ +" <action name=\"stop\" timeout=\"15\" />\n"\ +" <action name=\"status\" timeout=\"15\" />\n"\ +" <action name=\"restart\" timeout=\"15\" />\n"\ +" <action name=\"force-reload\" timeout=\"15\" />\n"\ +" <action name=\"monitor\" timeout=\"15\" interval=\"15\" />\n"\ +" <action name=\"meta-data\" timeout=\"5\" />\n"\ +" </actions>\n"\ +" <special tag=\"LSB\">\n"\ +" <Provides>%s</Provides>\n"\ +" <Required-Start>%s</Required-Start>\n"\ +" <Required-Stop>%s</Required-Stop>\n"\ +" <Should-Start>%s</Should-Start>\n"\ +" <Should-Stop>%s</Should-Stop>\n"\ +" <Default-Start>%s</Default-Start>\n"\ +" <Default-Stop>%s</Default-Stop>\n"\ +" </special>\n"\ +"</resource-agent>\n" + +/* The keywords for lsb-compliant comment */ +#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO" +#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO" +#define PROVIDES "# Provides:" +#define REQ_START "# Required-Start:" +#define REQ_STOP "# Required-Stop:" +#define SHLD_START "# Should-Start:" +#define SHLD_STOP "# Should-Stop:" +#define DFLT_START "# Default-Start:" +#define DFLT_STOP "# Default-Stop:" +#define SHORT_DSCR "# Short-Description:" +#define DESCRIPTION "# Description:" + +#define ZAPXMLOBJ(m) \ + if ( (m) != NULL ) { \ + xmlFree(m); \ + (m) = NULL; \ + } + +#define RALSB_GET_VALUE(ptr, keyword) \ + if ( (ptr == NULL) & (0 == strncasecmp(buffer, keyword, strlen(keyword))) ) { \ + (ptr) = (char *)xmlEncodeEntitiesReentrant(NULL,BAD_CAST buffer+strlen(keyword)); \ + continue; \ + } +/* + * Are there multiple paths? Now according to LSB init scripts, the answer + * is 'no', but should be 'yes' for lsb none-init scripts? + */ +static const char * RA_PATH = LSB_RA_DIR; +/* Map to the return code of the 'monitor' operation defined in the OCF RA + * specification. + */ +static const int status_op_exitcode_map[] = { + EXECRA_OK, /* LSB_STATUS_OK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_PID */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_LOCK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_STOPPED */ + EXECRA_UNKNOWN_ERROR /* LSB_STATUS_UNKNOWN */ +}; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +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 interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + GString * debug_info; + char * inherit_debuglevel = NULL; + char * optype_tmp = NULL; + int index_tmp = 0; + int save_errno; + + /* Specially handle the operation "metameta-data". To build up its + * output from templet, dummy data and its comment head. + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for LSB scripts. + */ + if (0 == STRNCMP_CONST(op_type, "monitor")) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if ( prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv) + != 0) { + cl_log(LOG_ERR, "lsb RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: lsb::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + * with the LSB standard. + */ + if (ret_execra < 0) { + return EXECRA_UNKNOWN_ERROR; + } + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (ret_execra < DIMOF(status_op_exitcode_map)) { + ret_execra = status_op_exitcode_map[ret_execra]; + } + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue, found_begin_tag, is_lsb_script; + int rc = 0; + GList *cur, *tmp; + const size_t BUFLEN = 80; + char buffer[BUFLEN]; + + if ((rc = get_runnable_list(RA_PATH, rsc_info)) <= 0) { + return rc; + } + + /* Use the following comment line as the filter patterns to choose + * the real LSB-compliant scripts. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + cur = g_list_first(*rsc_info); + while ( cur != NULL ) { + get_ra_pathname(RA_PATH, cur->data, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + tmp = g_list_next(cur); + *rsc_info = g_list_remove(*rsc_info, cur->data); + if (cur->data) + g_free(cur->data); + cur = tmp; + continue; + } + is_lsb_script = FALSE; + next_continue = FALSE; + found_begin_tag = FALSE; + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + /* Shorten the search time */ + if (buffer[0] != '#' && buffer[0] != ' ' + && buffer[0] != '\n') { + break; /* donnot find */ + } + + if (found_begin_tag == TRUE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + is_lsb_script = TRUE; + break; + } + if (found_begin_tag == FALSE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + found_begin_tag = TRUE; + } + } + fclose(fp); + tmp = g_list_next(cur); + +/* + * Temporarily remove the filter to the initscript, or many initscripts on + * many distros, such as RHEL4 and fedora5, cannot be used by management GUI. + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ + +#if 0 + if ( is_lsb_script != TRUE ) { + *rsc_info = g_list_remove(*rsc_info, cur->data); + g_free(cur->data); + } +#else + (void) is_lsb_script; +#endif + cur = tmp; + } + + return g_list_length(*rsc_info); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len; + int ht_size = 0; +#if 0 + /* Reserve it for possible furture use */ + int index; + void * value_tmp = NULL; + char buf_tmp[20]; +#endif + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + + /* Need 3 additonal spaces for accomodating: + * argv[0] = RA_file_name(RA_TYPE) + * argv[1] = operation + * a terminal NULL + */ + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the first argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[1] = g_strndup(op_type, tmp_len); + + /* + * No actual arguments needed except op_type. + * Add the teminating NULL pointer. + */ + params_argv[2] = NULL; + if ( (ht_size != 0) && (0 != STRNCMP_CONST(op_type, "status")) ) { + cl_log(LOG_WARNING, "For LSB init script, no additional " + "parameters are needed."); + } + +/* Actually comment the following code, but I still think it may be used + * in the future for LSB none-initial scripts, so reserver it. + */ +#if 0 + /* Now suppose the parameter formate stored in Hashtabe is like + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { + cl_log(LOG_ERR, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + return -1; + } + params_argv[index+1] = g_strdup((char *)value_tmp); + } +#endif + + return 0; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue; + GString * meta_data; + const size_t BUFLEN = 132; + char buffer[BUFLEN]; + char * provides = NULL, + * req_start = NULL, + * req_stop = NULL, + * shld_start = NULL, + * shld_stop = NULL, + * dflt_start = NULL, + * dflt_stop = NULL, + * s_dscrpt = NULL, + * xml_l_dscrpt = NULL; + GString * l_dscrpt = NULL; + + /* + * Use the following tags to find the LSb-compliant comment block. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + cl_log(LOG_ERR, "Failed to open lsb RA %s. No meta-data gotten." + , rsc_type); + return NULL; + } + meta_data = g_string_new(""); + + next_continue = FALSE; + +/* + * Is not stick to the rule that the description should be located in the + * comment block between "### BEGIN INIT INFO" and "### END INIT INFO". + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ +#if 0 + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + + if ( 0 == strncasecmp(buffer , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + break; + } + } +#else + (void) next_continue; +#endif + + /* Enter into the lsb-compliant comment block */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + /* Now suppose each of the following eight arguments contain + * only one line + */ + RALSB_GET_VALUE(provides, PROVIDES) + RALSB_GET_VALUE(req_start, REQ_START) + RALSB_GET_VALUE(req_stop, REQ_STOP) + RALSB_GET_VALUE(shld_start, SHLD_START) + RALSB_GET_VALUE(shld_stop, SHLD_STOP) + RALSB_GET_VALUE(dflt_start, DFLT_START) + RALSB_GET_VALUE(dflt_stop, DFLT_STOP) + RALSB_GET_VALUE(s_dscrpt, SHORT_DSCR) + + /* Long description may cross multiple lines */ + if ( (l_dscrpt == NULL) && (0 == strncasecmp(buffer, DESCRIPTION + , strlen(DESCRIPTION))) ) { + l_dscrpt = g_string_new(buffer+strlen(DESCRIPTION)); + /* Between # and keyword, more than one space, or a tab + * character, indicates the continuation line. + * Extracted from LSB init script standard + */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + if ( (0 == strncmp(buffer, "# ", 3)) + || (0 == strncmp(buffer, "#\t", 2)) ) { + buffer[0] = ' '; + l_dscrpt = g_string_append(l_dscrpt + , buffer); + } else { + fputs(buffer, fp); + break; /* Long description ends */ + } + } + continue; + } + if( l_dscrpt ) + xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, + BAD_CAST (l_dscrpt->str)); + + if ( 0 == strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + /* Get to the out border of LSB comment block */ + break; + } + + if ( buffer[0] != '#' ) { + break; /* Out of comment block in the beginning */ + } + } + fclose(fp); + + g_string_sprintf( meta_data, meta_data_template, rsc_type + , (xml_l_dscrpt==NULL)? rsc_type : xml_l_dscrpt + , (s_dscrpt==NULL)? rsc_type : s_dscrpt + , (provides==NULL)? "" : provides + , (req_start==NULL)? "" : req_start + , (req_stop==NULL)? "" : req_stop + , (shld_start==NULL)? "" : shld_start + , (shld_stop==NULL)? "" : shld_stop + , (dflt_start==NULL)? "" : dflt_start + , (dflt_stop==NULL)? "" : dflt_stop ); + + ZAPXMLOBJ(xml_l_dscrpt); + ZAPXMLOBJ(s_dscrpt); + ZAPXMLOBJ(provides); + ZAPXMLOBJ(req_start); + ZAPXMLOBJ(req_stop); + ZAPXMLOBJ(shld_start); + ZAPXMLOBJ(shld_stop); + ZAPXMLOBJ(dflt_start); + ZAPXMLOBJ(dflt_stop); + + if( l_dscrpt ) + g_string_free(l_dscrpt, TRUE); + return meta_data->str; +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexecocf.c b/lib/plugins/lrm/raexecocf.c new file mode 100644 index 0000000..f7cd7ed --- /dev/null +++ b/lib/plugins/lrm/raexecocf.c @@ -0,0 +1,496 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: raexecocf.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <libgen.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <pils/plugin.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <lrm/raexec.h> + +# define PIL_PLUGINTYPE RA_EXEC_TYPE +# define PIL_PLUGINTYPE_S "RAExec" +# define PIL_PLUGINLICENSE LICENSE_PUBDOM +# define PIL_PLUGINLICENSEURL URL_PUBDOM + +# define PIL_PLUGIN ocf +# define PIL_PLUGIN_S "ocf" +/* + * Are there multiple paths? Now according to OCF spec, the answer is 'no'. + * But actually or for future? + */ +static const char * RA_PATH = OCF_RA_DIR; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); +static uniform_ret_execra_t map_ra_retvalue(int ret_execra, + const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +static void add_OCF_prefix(GHashTable * params, GHashTable * new_params); +static void add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider); +static void add_prefix_foreach(gpointer key, gpointer value, + gpointer user_data); + +static void hash_to_str(GHashTable * , GString *); +static void hash_to_str_foreach(gpointer key, gpointer value, + gpointer user_data); + +static int raexec_setenv(GHashTable * env_params); +static void set_env(gpointer key, gpointer value, gpointer user_data); + +static gboolean let_remove_eachitem(gpointer key, gpointer value, + gpointer user_data); +static int get_providers(const char* class_path, const char* op_type, + GList ** providers); +static void merge_string_list(GList** old, GList* new); +static gint compare_str(gconstpointer a, gconstpointer b); + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +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 interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * The function to execute a RA. + */ +static int +execra(const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + GHashTable * tmp_for_setenv; + GString * params_gstring; + char * inherit_debuglevel = NULL; + int save_errno; + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + /* Setup environment correctly */ + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_prefix(params, tmp_for_setenv); + add_OCF_env_vars(tmp_for_setenv, rsc_id, rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + params_gstring = g_string_new(""); + hash_to_str(params, params_gstring); + cl_log(LOG_DEBUG, "RA instance %s executing: OCF::%s %s. Parameters: " + "{%s}", rsc_id, rsc_type, op_type, params_gstring->str); + g_string_free(params_gstring, TRUE); + } + + closefiles(); /* don't leak open files */ + /* execute the RA */ + execl(ra_pathname, ra_pathname, op_type, (const char *)NULL); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execl failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Because the UNIFORM_RET_EXECRA is compatible with OCF standard, + * no actual mapping except validating, which ensure the return code + * will be in the range 0 to 7. Too strict? + */ + if (ret_execra < 0 || ret_execra > 9) { + cl_log(LOG_WARNING, "mapped the invalid return code %d." + , ret_execra); + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static gint +compare_str(gconstpointer a, gconstpointer b) +{ + return strncmp(a,b,RA_MAX_NAME_LENGTH); +} + +static int +get_resource_list(GList ** rsc_info) +{ + struct dirent **namelist; + GList* item; + int file_num; + char subdir[FILENAME_MAX+1]; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + file_num = scandir(RA_PATH, &namelist, NULL, alphasort); + if (file_num < 0) { + return -2; + } + while (file_num--) { + GList* ra_subdir = NULL; + struct stat prop; + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + + snprintf(subdir,FILENAME_MAX,"%s/%s", + RA_PATH, namelist[file_num]->d_name); + + if (stat(subdir, &prop) == -1) { + cl_perror("%s:%s:%d: stat failed for %s" + , __FILE__, __FUNCTION__, __LINE__, subdir); + free(namelist[file_num]); + continue; + } else if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + get_runnable_list(subdir,&ra_subdir); + + merge_string_list(rsc_info,ra_subdir); + + while (NULL != (item = g_list_first(ra_subdir))) { + ra_subdir = g_list_remove_link(ra_subdir, item); + g_free(item->data); + g_list_free_1(item); + } + + free(namelist[file_num]); + } + free(namelist); + + return 0; +} + +static void +merge_string_list(GList** old, GList* new) +{ + GList* item = NULL; + char* newitem; + for( item=g_list_first(new); NULL!=item; item=g_list_next(item)){ + if (!g_list_find_custom(*old, item->data,compare_str)){ + newitem = g_strndup(item->data,RA_MAX_NAME_LENGTH); + *old = g_list_append(*old, newitem); + } + } +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + int ret; + ret = get_providers(RA_PATH, ra_type, providers); + if (0>ret) { + cl_log(LOG_ERR, "scandir failed in OCF RA plugin"); + } + return ret; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE* file = NULL; + GHashTable * tmp_for_setenv; + struct timespec short_sleep = {0,200000000L}; /*20ms*/ + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + strncat(ra_pathname, " meta-data",RA_MAX_NAME_LENGTH-strlen(ra_pathname)-1); + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_env_vars(tmp_for_setenv, "DUMMY_INSTANCE", rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + file = popen(ra_pathname, "r"); + if (NULL==file) { + cl_log(LOG_ERR, "%s: popen failed: %s", __FUNCTION__, strerror(errno)); + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + read_len = fread(buff, 1, BUFF_LEN - 1, file); + if (0<read_len) { + *(buff+read_len) = '\0'; + g_string_append(g_str_tmp, buff); + } + else { + nanosleep(&short_sleep,NULL); + } + } + if( pclose(file) ) { + cl_log(LOG_ERR, "%s: pclose failed: %s", __FUNCTION__, strerror(errno)); + } + if (0 == g_str_tmp->len) { + g_string_free(g_str_tmp, TRUE); + return NULL; + } + data = (char*)g_new(char, g_str_tmp->len+1); + 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); + + return data; +} + +static void +add_OCF_prefix(GHashTable * env_params, GHashTable * new_env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, add_prefix_foreach, + new_env_params); + } +} + +static void +add_prefix_foreach(gpointer key, gpointer value, gpointer user_data) +{ + const int MAX_LENGTH_OF_ENV = 128; + int prefix = STRLEN_CONST("OCF_RESKEY_"); + GHashTable * new_hashtable = (GHashTable *) user_data; + char * newkey; + int keylen = strnlen((char*)key, MAX_LENGTH_OF_ENV-prefix)+prefix+1; + + newkey = g_new(gchar, keylen); + strncpy(newkey, "OCF_RESKEY_", keylen); + strncat(newkey, key, keylen-strlen(newkey)-1); + g_hash_table_insert(new_hashtable, (gpointer)newkey, g_strdup(value)); +} + +static void +hash_to_str(GHashTable * params , GString * str) +{ + if (params) { + g_hash_table_foreach(params, hash_to_str_foreach, str); + } +} + +static void +hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data) +{ + char buffer_tmp[60]; + GString * str = (GString *)user_data; + + snprintf(buffer_tmp, 60, "%s=%s ", (char *)key, (char *)value); + str = g_string_append(str, buffer_tmp); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +static int +raexec_setenv(GHashTable * env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, set_env, NULL); + } + return 0; +} + +static void +set_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv(key, value, 1) != 0) { + cl_log(LOG_ERR, "setenv failed in raexecocf."); + } +} + +static int +get_providers(const char* class_path, const char* ra_type, GList ** providers) +{ + struct dirent **namelist; + int file_num; + + if ( providers == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers"); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers."\ + "will cause memory leak."); + *providers = NULL; + } + + file_num = scandir(class_path, &namelist, 0, alphasort); + if (file_num < 0) { + return -2; + }else{ + char tmp_buffer[FILENAME_MAX+1]; + struct stat prop; + + while (file_num--) { + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s", + class_path, namelist[file_num]->d_name); + stat(tmp_buffer, &prop); + if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s/%s", + class_path, namelist[file_num]->d_name, ra_type); + + if ( filtered(tmp_buffer) == TRUE ) { + *providers = g_list_append(*providers, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*providers); +} + +static void +add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider) +{ + if ( env == NULL ) { + cl_log(LOG_WARNING, "env should not be a NULL pointer."); + return; + } + + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MAJOR"), + g_strdup("1")); + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MINOR"), + g_strdup("0")); + g_hash_table_insert(env, g_strdup("OCF_ROOT"), + g_strdup(OCF_ROOT_DIR)); + + if ( rsc_id != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_INSTANCE"), + g_strdup(rsc_id)); + } + + /* Currently the rsc_type=="the filename of the RA script/executable", + * It seems always correct even in the furture. ;-) + */ + if ( rsc_type != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_TYPE"), + g_strdup(rsc_type)); + } + + /* Notes: this is not added to specification yet. Sept 10,2004 */ + if ( provider != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_PROVIDER"), + g_strdup(provider)); + } +} + diff --git a/lib/plugins/lrm/raexecupstart.c b/lib/plugins/lrm/raexecupstart.c new file mode 100644 index 0000000..baa0278 --- /dev/null +++ b/lib/plugins/lrm/raexecupstart.c @@ -0,0 +1,222 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: raexecupstart.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + * + * Heavily based on raexeclsb.c and raexechb.c: + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for Upstart. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN upstart +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "upstart" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> +#include <libgen.h> + +#include <glib-object.h> + +#include <libxml/entities.h> + +#include "upstart-dbus.h" + +#define meta_data_template \ +"<?xml version=\"1.0\"?>\n"\ +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"\ +"<resource-agent name=\"%s\" version=\"0.1\">\n"\ +" <version>1.0</version>\n"\ +" <longdesc lang=\"en\">\n"\ +" %s"\ +" </longdesc>\n"\ +" <shortdesc lang=\"en\">%s</shortdesc>\n"\ +" <parameters>\n"\ +" </parameters>\n"\ +" <actions>\n"\ +" <action name=\"start\" timeout=\"15\" />\n"\ +" <action name=\"stop\" timeout=\"15\" />\n"\ +" <action name=\"status\" timeout=\"15\" />\n"\ +" <action name=\"restart\" timeout=\"15\" />\n"\ +" <action name=\"monitor\" timeout=\"15\" interval=\"15\" start-delay=\"15\" />\n"\ +" <action name=\"meta-data\" timeout=\"5\" />\n"\ +" </actions>\n"\ +" <special tag=\"upstart\">\n"\ +" </special>\n"\ +"</resource-agent>\n" + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + PluginImports = imports; + OurPlugin = us; + + imports->register_plugin(us, &OurPIExports); + + g_type_init (); + + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + UpstartJobCommand cmd; + + if (!g_strcmp0(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(EXECRA_OK); + } else if (!g_strcmp0(op_type, "monitor") || !g_strcmp0(op_type, "status")) { + gboolean running = upstart_job_is_running (rsc_type); + printf("%s", running ? "running" : "stopped"); + + if (running) + exit(EXECRA_OK); + else + exit(EXECRA_NOT_RUNNING); + } else if (!g_strcmp0(op_type, "start")) { + cmd = UPSTART_JOB_START; + } else if (!g_strcmp0(op_type, "stop")) { + cmd = UPSTART_JOB_STOP; + } else if (!g_strcmp0(op_type, "restart")) { + cmd = UPSTART_JOB_RESTART; + } else { + exit(EXECRA_UNIMPLEMENT_FEATURE); + } + + /* It'd be better if it returned GError, so we can distinguish + * between failure modes (can't contact upstart, no such job, + * or failure to do action. */ + if (upstart_job_do(rsc_type, cmd, timeout)) { + exit(EXECRA_OK); + } else { + exit(EXECRA_NO_RA); + } +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* no need to map anything, execra() returns correct exit code */ + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + gchar **jobs; + gint i; + *rsc_info = NULL; + + jobs = upstart_get_all_jobs(); + + if (!jobs) + return 0; + + for (i = 0; jobs[i] != NULL; i++) { + *rsc_info = g_list_prepend(*rsc_info, jobs[i]); + } + + /* free the array, but not the strings */ + g_free(jobs); + + *rsc_info = g_list_reverse(*rsc_info); + return g_list_length(*rsc_info); +} + +static char * +get_resource_meta (const gchar *rsc_type, const gchar *provider) +{ + return g_strdup_printf(meta_data_template, rsc_type, + rsc_type, rsc_type); +} + +static int +get_provider_list (const gchar *ra_type, GList **providers) +{ + *providers = g_list_prepend(*providers, g_strdup("upstart")); + return g_list_length(*providers); +} + diff --git a/lib/plugins/lrm/upstart-dbus.c b/lib/plugins/lrm/upstart-dbus.c new file mode 100644 index 0000000..b994d87 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.c @@ -0,0 +1,406 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + * + * + * Each exported function is standalone, and creates a new connection to + * the upstart daemon. This is because lrmd plugins fork off for exec, + * and if we try and share the connection, the whole thing blocks + * indefinitely. + */ + +#include "upstart-dbus.h" + +#include <glib.h> +#include <dbus/dbus-glib.h> + +#include <dbus/dbus.h> + +#include "dbus/Upstart.h" +#include "dbus/Upstart_Job.h" +#include "dbus/Upstart_Instance.h" + +#include <stdio.h> + +#define SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +#define UPSTART_BUS_ADDRESS "unix:abstract=/com/ubuntu/upstart" +#define UPSTART_SERVICE_NAME "com.ubuntu.Upstart" +#define UPSTART_MANAGER_PATH "/com/ubuntu/Upstart" +#define UPSTART_IFACE "com.ubuntu.Upstart0_6" +#define UPSTART_JOB_IFACE UPSTART_IFACE ".Job" +#define UPSTART_INSTANCE_IFACE UPSTART_IFACE ".Instance" +#define UPSTART_ERROR_ALREADY_STARTED UPSTART_IFACE ".Error.AlreadyStarted" +#define UPSTART_ERROR_UNKNOWN_INSTANCE UPSTART_IFACE ".Error.UnknownInstance" + +static DBusGConnection * +get_connection(void) +{ + GError *error = NULL; + DBusGConnection *conn; + + conn = dbus_g_bus_get_private(DBUS_BUS_SYSTEM, NULL, &error); + + if (error) + { + g_error_free(error); + error = NULL; + + conn = dbus_g_connection_open("unix:abstract=/com/ubuntu/upstart", + &error); + + if (error) + { + g_warning("Can't connect to either system or Upstart " + "DBus bus."); + g_error_free(error); + + return NULL; + } + } + + return conn; +} + +static DBusGProxy * +new_proxy(DBusGConnection *conn, const gchar *object_path, + const gchar *iface) +{ + return dbus_g_proxy_new_for_name(conn, + UPSTART_SERVICE_NAME, + object_path, + iface); +} + +static GHashTable * +get_object_properties(DBusGProxy *obj, const gchar *iface) +{ + GError *error = NULL; + DBusGProxy *proxy; + GHashTable *asv; + GHashTable *retval; + GHashTableIter iter; + gpointer k, v; + + proxy = dbus_g_proxy_new_from_proxy(obj, + DBUS_INTERFACE_PROPERTIES, NULL); + + dbus_g_proxy_call(proxy, "GetAll", &error, G_TYPE_STRING, + iface, G_TYPE_INVALID, + dbus_g_type_get_map("GHashTable", + G_TYPE_STRING, + G_TYPE_VALUE), + &asv, G_TYPE_INVALID); + + if (error) { + g_warning("Error getting %s properties: %s", iface, error->message); + g_error_free(error); + g_object_unref(proxy); + return NULL; + } + + retval = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_iter_init(&iter, asv); + while (g_hash_table_iter_next(&iter, &k, &v)) { + gchar *key = k; + GValue *val = v; + + /* all known properties are strings */ + if (G_VALUE_TYPE(val) == G_TYPE_STRING) { + g_hash_table_insert(retval, g_strdup(key), + g_value_dup_string(val)); + } + } + + g_hash_table_destroy(asv); + + return retval; +} + +gchar ** +upstart_get_all_jobs(void) +{ + DBusGConnection *conn; + DBusGProxy *manager; + GError *error = NULL; + GPtrArray *array; + gchar **retval = NULL; + gint i, j; + + conn = get_connection(); + if (!conn) + return NULL; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + dbus_g_proxy_call(manager, "GetAllJobs", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllJobs: %s", error->message); + g_error_free(error); + g_object_unref(manager); + dbus_g_connection_unref(conn); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0, j = 0; i < array->len; i++) + { + DBusGProxy *job; + + job = new_proxy(conn, g_ptr_array_index(array, i), + UPSTART_JOB_IFACE); + + if (job) { + GHashTable *props = get_object_properties(job, + UPSTART_JOB_IFACE); + + if (props) { + gchar *name = g_hash_table_lookup(props, + "name"); + + if (name) + retval[j++] = g_strdup(name); + + g_hash_table_destroy(props); + } + + g_object_unref(job); + } + } + + g_ptr_array_free(array, TRUE); + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +static DBusGProxy * +upstart_get_job_by_name(DBusGConnection *conn, DBusGProxy *manager, + const gchar *name) +{ + GError *error = NULL; + gchar *object_path; + DBusGProxy *retval; + + dbus_g_proxy_call(manager, "GetJobByName", &error, G_TYPE_STRING, + name, G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH, &object_path, + G_TYPE_INVALID); + + if (error) + { + g_warning("Error calling GetJobByName: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = new_proxy(conn, object_path, UPSTART_JOB_IFACE); + + g_free(object_path); + + return retval; +} + +static gchar ** +get_job_instances(DBusGProxy *job) +{ + GError *error = NULL; + GPtrArray *array; + gchar **retval; + gint i; + + dbus_g_proxy_call(job, "GetAllInstances", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllInstances: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0; i < array->len; i++) + { + retval[i] = g_ptr_array_index(array, i); + } + + g_ptr_array_free(array, TRUE); + + return retval; +} + +static DBusGProxy * +get_first_instance(DBusGConnection *conn, DBusGProxy *job) +{ + gchar **instances; + DBusGProxy *instance = NULL; + + instances = get_job_instances(job); + + if (!instances) + return NULL; + + if (*instances) + { + instance = new_proxy(conn, instances[0], + UPSTART_INSTANCE_IFACE); + } + + g_strfreev(instances); + return instance; +} + +gboolean +upstart_job_is_running(const gchar *name) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval = FALSE; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + DBusGProxy *instance = get_first_instance(conn, job); + + if (instance) { + GHashTable *props = get_object_properties(instance, + UPSTART_INSTANCE_IFACE); + + if (props) { + const gchar *state = g_hash_table_lookup(props, + "state"); + + retval = !g_strcmp0(state, "running"); + + g_hash_table_destroy(props); + } + + g_object_unref(instance); + } + + g_object_unref(job); + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +gboolean +upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + GError *error = NULL; + const gchar *cmd_name = NULL; + gchar *instance_path = NULL; + gchar *no_args[] = { NULL }; + + switch (cmd) { + case UPSTART_JOB_START: + cmd_name = "Start"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + case UPSTART_JOB_STOP: + cmd_name = "Stop"; + dbus_g_proxy_call_with_timeout(job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + G_TYPE_INVALID); + break; + case UPSTART_JOB_RESTART: + cmd_name = "Restart"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + default: + g_assert_not_reached(); + } + + if (error) { + g_warning("Could not issue %s: %s", cmd_name, + error->message); + + /* ignore "already started" or "not running" errors */ + if (dbus_g_error_has_name(error, + UPSTART_ERROR_ALREADY_STARTED) || + dbus_g_error_has_name(error, + UPSTART_ERROR_UNKNOWN_INSTANCE)) { + retval = TRUE; + } else { + retval = FALSE; + } + g_error_free(error); + } else { + retval = TRUE; + } + + g_object_unref(job); + } else { + retval = FALSE; + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + return retval; +} + + diff --git a/lib/plugins/lrm/upstart-dbus.h b/lib/plugins/lrm/upstart-dbus.h new file mode 100644 index 0000000..bc72c95 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU 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 + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + */ +#ifndef _UPSTART_DBUS_H_ +#define _UPSTART_DBUS_H_ + +#include <glib.h> + +typedef enum { + UPSTART_JOB_START, + UPSTART_JOB_STOP, + UPSTART_JOB_RESTART +} UpstartJobCommand; + +G_GNUC_INTERNAL gchar **upstart_get_all_jobs(void); +G_GNUC_INTERNAL gboolean upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout); +G_GNUC_INTERNAL gboolean upstart_job_is_running (const gchar *name); + +#endif /* _UPSTART_DBUS_H_ */ + diff --git a/lib/plugins/stonith/Makefile.am b/lib/plugins/stonith/Makefile.am new file mode 100644 index 0000000..01f2f4a --- /dev/null +++ b/lib/plugins/stonith/Makefile.am @@ -0,0 +1,216 @@ +# +# stonith: STONITH plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = external + +INCFILES = stonith_expect_helpers.h \ + stonith_plugin_common.h \ + stonith_signal.h \ + stonith_config_xml.h + +idir=$(includedir)/stonith + +i_HEADERS = $(INCFILES) + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @NON_FATAL_CFLAGS@ + +# +# We need "vacmclient_api.h" and -lvacmclient to make the VACM +# plugin work. +# +if USE_VACM +vacm_LIB = vacm.la +else +vacm_LIB = +endif + +# +# We need <ucd-snmp/asn1.h>, <ucd-snmp/snmp_api.h>, <ucd-snmp/snmp.h> +# <ucd-snmp/snmp_client.h>, <ucd-snmp/mib.h>, -lsnmp and -lcrypto +# for the apcmastersnmp plugin to work +# + +if USE_APC_SNMP +apcmastersnmp_LIB = apcmastersnmp.la wti_mpc.la +else +apcmastersnmp_LIB = +endif +if IPMILAN_BUILD +OPENIPMI_LIB = -lOpenIPMI -lOpenIPMIposix -lOpenIPMIutils +libipmilan_LIB = libipmilan.la +ipmilan_LIB = ipmilan.la +ipmilan_TEST = ipmilantest +else +OPENIPMI_LIB = +libipmilan_LIB = +ipmilan_LIB = +endif +# +# We need <curl/curl.h>, <libxml/xmlmemory.h>, +# <libxml/parser.h>, <libxml/xpath.h>, +# -lcurl and -lxml2 for the drac3 plugin to work +# +if USE_DRAC3 +drac3_LIB = drac3.la +else +drac3_LIB = +endif + +# +# We need OpenHPI to make the IBM BladeCenter plugin work. +# +if USE_OPENHPI +bladehpi_LIB = bladehpi.la +else +bladehpi_LIB = +endif + +noinst_PROGRAMS = $(ipmilan_TEST) +ipmilantest_SOURCES = ipmilan_test.c +ipmilantest_LDADD = $(libipmilan_LIB) \ + $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/lib/stonith/libstonith.la \ + $(OPENIPMI_LIB) + +## libraries + +plugindir = $(stonith_plugindir)/stonith2 + +plugin_LTLIBRARIES = apcmaster.la \ + $(apcmastersnmp_LIB) \ + apcsmart.la \ + baytech.la \ + $(bladehpi_LIB) \ + cyclades.la \ + $(drac3_LIB) \ + external.la \ + rhcs.la \ + ibmhmc.la \ + $(ipmilan_LIB) \ + meatware.la \ + null.la \ + nw_rpc100s.la \ + rcd_serial.la \ + rps10.la \ + ssh.la \ + suicide.la \ + $(vacm_LIB) \ + wti_nps.la +noinst_LTLIBRARIES = $(libipmilan_LIB) + +apcmaster_la_SOURCES = apcmaster.c $(INCFILES) +apcmaster_la_LDFLAGS = -export-dynamic -module -avoid-version +apcmaster_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcmastersnmp_la_SOURCES= apcmastersnmp.c $(INCFILES) +apcmastersnmp_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +apcmastersnmp_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcsmart_la_SOURCES = apcsmart.c $(INCFILES) +apcsmart_la_LDFLAGS = -export-dynamic -module -avoid-version +apcsmart_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +baytech_la_SOURCES = baytech.c $(INCFILES) +baytech_la_LDFLAGS = -export-dynamic -module -avoid-version +baytech_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +bladehpi_la_SOURCES = bladehpi.c $(INCFILES) +bladehpi_la_LDFLAGS = -export-dynamic -module -avoid-version +bladehpi_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) -lopenhpi + +cyclades_la_SOURCES = cyclades.c $(INCFILES) +cyclades_la_LDFLAGS = -export-dynamic -module -avoid-version +cyclades_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +drac3_la_SOURCES = drac3.c drac3_command.c drac3_command.h drac3_hash.c drac3_hash.h $(INCFILES) +drac3_la_LDFLAGS = -export-dynamic -module -avoid-version +drac3_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la -lcurl -lxml2 $(GLIBLIB) + +external_la_SOURCES = external.c $(INCFILES) +external_la_LDFLAGS = -export-dynamic -module -avoid-version +external_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +rhcs_la_SOURCES = rhcs.c $(INCFILES) +rhcs_la_LDFLAGS = -export-dynamic -module -avoid-version +rhcs_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +ibmhmc_la_SOURCES = ibmhmc.c $(INCFILES) +ibmhmc_la_LDFLAGS = -export-dynamic -module -avoid-version +ibmhmc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +ipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +ipmilan_la_LDFLAGS = -export-dynamic -module -avoid-version +ipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +libipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +libipmilan_la_LDFLAGS = -version-info 1:0:0 +libipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +meatware_la_SOURCES = meatware.c $(INCFILES) +meatware_la_LDFLAGS = -export-dynamic -module -avoid-version +meatware_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +null_la_SOURCES = null.c $(INCFILES) +null_la_LDFLAGS = -export-dynamic -module -avoid-version +null_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +nw_rpc100s_la_SOURCES = nw_rpc100s.c $(INCFILES) +nw_rpc100s_la_LDFLAGS = -export-dynamic -module -avoid-version +nw_rpc100s_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +rcd_serial_la_SOURCES = rcd_serial.c $(INCFILES) +rcd_serial_la_LDFLAGS = -export-dynamic -module -avoid-version +rcd_serial_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +rps10_la_SOURCES = rps10.c $(INCFILES) +rps10_la_LDFLAGS = -export-dynamic -module -avoid-version +rps10_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +ssh_la_SOURCES = ssh.c $(INCFILES) +ssh_la_LDFLAGS = -export-dynamic -module -avoid-version +ssh_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +vacm_la_SOURCES = vacm.c $(INCFILES) +vacm_la_LDFLAGS = -export-dynamic -module -avoid-version +vacm_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +wti_nps_la_SOURCES = wti_nps.c $(INCFILES) +wti_nps_la_LDFLAGS = -export-dynamic -module -avoid-version +wti_nps_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +wti_mpc_la_SOURCES= wti_mpc.c $(INCFILES) +wti_mpc_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +wti_mpc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +suicide_la_SOURCES = suicide.c $(INCFILES) +suicide_la_LDFLAGS = -export-dynamic -module -avoid-version +suicide_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +stonithscriptdir = $(stonith_plugindir)/stonith2 + +stonithscript_SCRIPTS = ribcl.py diff --git a/lib/plugins/stonith/apcmaster.c b/lib/plugins/stonith/apcmaster.c new file mode 100644 index 0000000..09a56d3 --- /dev/null +++ b/lib/plugins/stonith/apcmaster.c @@ -0,0 +1,822 @@ +/* +* +* Copyright 2001 Mission Critical Linux, Inc. +* +* All Rights Reserved. +*/ +/* + * Stonith module for APC Master Switch (AP9211) + * + * Copyright (c) 2001 Mission Critical Linux, Inc. + * author: mike ledoux <mwl@mclinux.com> + * author: Todd Wheeling <wheeling@mclinux.com> + * mangled by Sun Jiang Dong, <sunjd@cn.ibm.com>, IBM, 2005 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * 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 + * + */ + +/* Observations/Notes + * + * 1. The APC MasterSwitch, unlike the BayTech network power switch, + * accepts only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the MasterSwitch + * will result in a connection refused/closed failure. In a cluster + * environment or other environment utilizing polling/monitoring of the + * MasterSwitch (from multiple nodes), this can clearly cause problems. + * Obviously the more nodes and the shorter the polling interval, the more + * frequently such errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the MasterSwitch became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include <lha_internal.h> + +#define DEVICE "APC MasterSwitch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN apcmaster +#define PIL_PLUGIN_S "apcmaster" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * apcmaster_new(const char *); +static void apcmaster_destroy(StonithPlugin *); +static const char * const * apcmaster_get_confignames(StonithPlugin *); +static int apcmaster_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmaster_getinfo(StonithPlugin * s, int InfoType); +static int apcmaster_status(StonithPlugin * ); +static int apcmaster_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmaster_hostlist(StonithPlugin *); + +static struct stonith_ops apcmasterOps ={ + apcmaster_new, /* Create new STONITH object */ + apcmaster_destroy, /* Destroy STONITH object */ + apcmaster_getinfo, /* Return STONITH info string */ + apcmaster_get_confignames, /* Get configuration parameters */ + apcmaster_set_config, /* Set configuration */ + apcmaster_status, /* Return STONITH device status */ + apcmaster_reset_req, /* Request a reset */ + apcmaster_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; + +#include "stonith_expect_helpers.h" + +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 + , &apcmasterOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an AP9211. This code has been tested with this switch. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +static const char * pluginid = "APCMS-Stonith"; +static const char * NOTpluginID = "APCMS device has been destroyed"; + +/* + * Different expect strings that we get from the APC MasterSwitch + */ + +#define APCMSSTR "American Power Conversion" + +static struct Etoken EscapeChar[] = { {"Escape character is '^]'.", 0, 0} + , {NULL,0,0}}; +static struct Etoken login[] = { {"User Name :", 0, 0}, {NULL,0,0}}; +static struct Etoken password[] = { {"Password :", 0, 0} ,{NULL,0,0}}; +static struct Etoken Prompt[] = { {"> ", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {APCMSSTR, 0, 0} + , {"User Name :", 1, 0} ,{NULL,0,0}}; +static struct Etoken Separator[] = { {"-----", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"Press <ENTER> to continue", 0, 0} + , {"Enter 'YES' to continue", 1, 0} + , {NULL,0,0}}; + +#include "stonith_config_xml.h" + +static const char *apcmasterXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int MS_connect_device(struct pluginDevice * ms); +static int MSLogin(struct pluginDevice * ms); +static int MSRobustLogin(struct pluginDevice * ms); +static int MSNametoOutlet(struct pluginDevice*, const char * name); +static int MSReset(struct pluginDevice*, int outletNum, const char * host); +static int MSLogout(struct pluginDevice * ms); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int apcmaster_onoff(struct pluginDevice*, int outletnum, const char * unitid +, int request); +#endif + +/* Login to the APC Master Switch */ + +static int +MSLogin(struct pluginDevice * ms) +{ + EXPECT(ms->rdfd, EscapeChar, 10); + + /* + * We should be looking at something like this: + * User Name : + */ + EXPECT(ms->rdfd, login, 10); + SEND(ms->wrfd, ms->user); + SEND(ms->wrfd, "\r"); + + /* Expect "Password :" */ + EXPECT(ms->rdfd, password, 10); + SEND(ms->wrfd, ms->passwd); + SEND(ms->wrfd, "\r"); + + switch (StonithLookFor(ms->rdfd, LoginOK, 30)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", ms->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", ms->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + return(S_OK); +} + +/* Attempt to login up to 20 times... */ + +static int +MSRobustLogin(struct pluginDevice * ms) +{ + int rc = S_OOPS; + int j = 0; + + for ( ; ; ) { + if (MS_connect_device(ms) == S_OK) { + rc = MSLogin(ms); + if( rc == S_OK ) { + break; + } + } + if ((++j) == 20) { + break; + } else { + sleep(1); + } + } + + return rc; +} + +/* Log out of the APC Master Switch */ + +static +int MSLogout(struct pluginDevice* ms) +{ + int rc; + + /* Make sure we're in the right menu... */ + /*SEND(ms->wrfd, "\033\033\033\033\033\033\033"); */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect "> " */ + rc = StonithLookFor(ms->rdfd, Prompt, 5); + + /* "4" is logout */ + SEND(ms->wrfd, "4\r"); + + close(ms->wrfd); + close(ms->rdfd); + ms->wrfd = ms->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} +/* Reset (power-cycle) the given outlets */ +static int +MSReset(struct pluginDevice* ms, int outletNum, const char *host) +{ + char unum[32]; + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + EXPECT(ms->rdfd, Prompt, 5); + snprintf(unum, sizeof(unum), "%i\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "1\r"); + + /* Select menu 3 (Immediate Reboot) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "3\r"); + + /* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press <ENTER>" Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + + LOG(PIL_INFO, "Host being rebooted: %s", host); + + /* Expect ">" */ + if (StonithLookFor(ms->rdfd, Prompt, 10) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", host); + + /* Return to top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +apcmaster_onoff(struct pluginDevice* ms, int outletNum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "1\r" : "2\r"); + int rc; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + snprintf(unum, sizeof(unum), "%d\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + SEND(ms->wrfd, "1\r"); + + /* Send ON/OFF command for given outlet */ + SEND(ms->wrfd, onoff); + + /* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press <ENTER>" Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + EXPECT(ms->rdfd, Prompt, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to MS outlet(s) %d turned %s", outletNum, onoff); + /* Pop back to main menu */ + SEND(ms->wrfd, "\033\033\033\033\033\033\033\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +MSNametoOutlet(struct pluginDevice* ms, const char * name) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int times = 0; + int ret = -1; + + /* Verify that we're in the top-level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + EXPECT(ms->rdfd, Separator, 5); + EXPECT(ms->rdfd, CRNL, 5); + EXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + times++; + NameMapping[0] = EOS; + SNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + ret = sockno; + } + } + } while (strlen(NameMapping) > 2 && times < 8); + + /* Pop back out to the top level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + return(ret); +} + +static int +apcmaster_status(StonithPlugin *s) +{ + struct pluginDevice* ms; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Expect ">" */ + SEND(ms->wrfd, "\033\r"); + EXPECT(ms->rdfd, Prompt, 5); + + return(MSLogout(ms)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this MS unit + */ + +static char ** +apcmaster_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* ms; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + ms = (struct pluginDevice*) s; + + if (MSRobustLogin(ms) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(NULL); + } + + /* Expect ">" */ + NULLEXPECT(ms->rdfd, Prompt, 10); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(ms->rdfd, Separator, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)MSLogout(ms); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + return(NULL); +} + +/* + * Connect to the given MS device. We should add serial support here + * eventually... + */ +static int +MS_connect_device(struct pluginDevice * ms) +{ + int fd = OurImports->OpenStreamSocket(ms->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + ms->rdfd = ms->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +apcmaster_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* ms; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + }else{ + int noutlet; + noutlet = MSNametoOutlet(ms, host); + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ms->device, host); + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; + case ST_POWEROFF: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; +#endif + case ST_GENERIC_RESET: + rc = MSReset(ms, noutlet, host); + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = MSLogout(ms); + return(rc != S_OK ? rc : lorc); +} + +/* + * Get the configuration parameters names + */ +static const char * const * +apcmaster_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +apcmaster_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +apcmaster_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ms; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ms = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ms->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = ms->device; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via telnet)\n" + "NOTE: The APC MasterSwitch accepts only one (telnet)\n" + "connection/session a time. When one session is active,\n" + "subsequent attempts to connect to the MasterSwitch" + " will fail."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmasterXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * APC MasterSwitch StonithPlugin destructor... + */ +static void +apcmaster_destroy(StonithPlugin *s) +{ + struct pluginDevice* ms; + + VOIDERRIFWRONGDEV(s); + + ms = (struct pluginDevice *)s; + + ms->pluginid = NOTpluginID; + if (ms->rdfd >= 0) { + close(ms->rdfd); + ms->rdfd = -1; + } + if (ms->wrfd >= 0) { + close(ms->wrfd); + ms->wrfd = -1; + } + if (ms->device != NULL) { + FREE(ms->device); + ms->device = NULL; + } + if (ms->user != NULL) { + FREE(ms->user); + ms->user = NULL; + } + if (ms->passwd != NULL) { + FREE(ms->passwd); + ms->passwd = NULL; + } + FREE(ms); +} + +/* Create a new APC Master Switch StonithPlugin device. */ + +static StonithPlugin * +apcmaster_new(const char *subplugin) +{ + struct pluginDevice* ms = ST_MALLOCT(struct pluginDevice); + + if (ms == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ms, 0, sizeof(*ms)); + ms->pluginid = pluginid; + ms->pid = -1; + ms->rdfd = -1; + ms->wrfd = -1; + ms->user = NULL; + ms->device = NULL; + ms->passwd = NULL; + ms->idinfo = DEVICE; + ms->sp.s_ops = &apcmasterOps; + + return(&(ms->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.c b/lib/plugins/stonith/apcmastersnmp.c new file mode 100644 index 0000000..a9eeaeb --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.c @@ -0,0 +1,890 @@ +/* + * Stonith module for APC Masterswitch (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * 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> + +/* device ID */ +#define DEVICE "APC MasterSwitch (SNMP)" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN apcmastersnmp +#define PIL_PLUGIN_S "apcmastersnmp" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * apcmastersnmp_new(const char *); +static void apcmastersnmp_destroy(StonithPlugin *); +static const char * const * apcmastersnmp_get_confignames(StonithPlugin *); +static int apcmastersnmp_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmastersnmp_getinfo(StonithPlugin * s, int InfoType); +static int apcmastersnmp_status(StonithPlugin * ); +static int apcmastersnmp_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmastersnmp_hostlist(StonithPlugin *); + +static struct stonith_ops apcmastersnmpOps ={ + apcmastersnmp_new, /* Create new STONITH object */ + apcmastersnmp_destroy, /* Destroy STONITH object */ + apcmastersnmp_getinfo, /* Return STONITH info string */ + apcmastersnmp_get_confignames, /* Get configuration parameters */ + apcmastersnmp_set_config, /* Set configuration */ + apcmastersnmp_status, /* Return STONITH device status */ + apcmastersnmp_reset_req, /* Request a reset */ + apcmastersnmp_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; + DEBUGCALL; + + 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 + , &apcmastersnmpOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 1 +#define OUTLET_OFF 2 +#define OUTLET_REBOOT 3 +#define OUTLET_NO_CMD_PEND 2 + +/* oids */ +#define OID_IDENT ".1.3.6.1.4.1.318.1.1.12.1.5.0" +#define OID_NUM_OUTLETS ".1.3.6.1.4.1.318.1.1.12.1.8.0" +#define OID_OUTLET_NAMES ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.2.%i" +#define OID_OUTLET_STATE ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%i" +#define OID_OUTLET_COMMAND_PENDING ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.5.%i" +#define OID_OUTLET_REBOOT_DURATION ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.6.%i" + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* for checking hardware (issue a warning if mismatch) */ +static const char* APC_tested_ident[] = {"AP9606","AP7920","AP7921","AP7900","AP_other_well_tested"}; + +/* constant strings */ +static const char *pluginid = "APCMS-SNMP-Stonith"; +static const char *NOTpluginID = "APCMS SNMP device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void APC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *APC_open(char *hostname, int port +, char *community); +static void *APC_read(struct snmp_session *sptr, const char *objname +, int type); +static int APC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +APC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +APC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + APC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +APC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +APC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +apcmastersnmp_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + int i; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = APC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* issue a warning if ident mismatches */ + for(i=DIMOF(APC_tested_ident) -1; i >=0 ; i--) { + if (strcmp(ident, APC_tested_ident[i]) == 0) { + break; + } + } + + if (i<0) { + LOG(PIL_WARN + , "%s: module not tested with this hardware '%s'." + , __FUNCTION__, ident); + } + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcmastersnmp_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, j + 1); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +apcmastersnmp_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int expect_state = OUTLET_ON; + int i, h, num_outlets, outlet, reboot_duration, *state, bad_outlets; + int outlets[8]; /* Assume that one node is connected to a + maximum of 8 outlets */ + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + num_outlets = 0; + reboot_duration = 0; + bad_outlets = 0; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, outlet); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + /* Check that the outlet is not administratively down */ + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + + /* get outlet's state */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + /* prepare oid */ + snprintf(objname, MAX_STRING, OID_OUTLET_REBOOT_DURATION + , outlet); + + /* read reboot duration of the port */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read reboot duration for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (num_outlets == 0) { + /* save the inital value of the first port */ + reboot_duration = *state; + } else if (reboot_duration != *state) { + LOG(PIL_WARN, "%s: outlet %d has a different reboot duration!" + , __FUNCTION__, outlet); + if (reboot_duration < *state) + reboot_duration = *state; + } + + /* Ok, add it to the list of outlets to control */ + outlets[num_outlets]=outlet; + num_outlets++; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (num_outlets < 1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + expect_state = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + expect_state = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + expect_state = OUTLET_ON; + break; + default: break; + } + + /* Turn them all off */ + + for (outlet=outlets[0], i=0 ; i < num_outlets; i++, outlet = outlets[i]) { + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_COMMAND_PENDING, outlet); + + /* are there pending commands ? */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read pending commands for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != OUTLET_NO_CMD_PEND) { + LOG(PIL_CRIT, "%s: command pending.", __FUNCTION__); + return (S_RESETFAIL); + } + + /* prepare objnames */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!APC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + } + + /* wait max. 2*reboot_duration for all outlets to go back on */ + for (i = 0; i < reboot_duration << 1; i++) { + + sleep(1); + + bad_outlets = 0; + for (outlet=outlets[0], h=0 ; h < num_outlets; h++, + outlet = outlets[h]) { + + /* prepare objname of the first outlet */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + /* get outlet's state */ + + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != expect_state) + bad_outlets++; + } + + if (bad_outlets == 0) + return (S_OK); + } + + if (bad_outlets == num_outlets) { + /* reset failed */ + LOG(PIL_CRIT, "%s: stonith operation for '%s' failed." + , __FUNCTION__, host); + return (S_RESETFAIL); + } else { + /* Not all outlets back on, but at least one; implies node was */ + /* rebooted correctly */ + LOG(PIL_WARN,"%s: Not all outlets in the expected state!" + , __FUNCTION__); + return (S_OK); + } +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +apcmastersnmp_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +apcmastersnmp_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + int * i; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("apcmastersnmp"); + + /* now try to get a snmp session */ + if ((sd->sptr = APC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of outlets from the masterswitch */ + if ((i = APC_read(sd->sptr, OID_NUM_OUTLETS, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + /* store the number of outlets */ + sd->num_outlets = *i; + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +apcmastersnmp_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via SNMP)\n" + "The APC MasterSwitch can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +apcmastersnmp_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcmastersnmp_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcmastersnmpOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.cfg.example b/lib/plugins/stonith/apcmastersnmp.cfg.example new file mode 100644 index 0000000..76fea08 --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.cfg.example @@ -0,0 +1,39 @@ +# +# this is an example config for the stonith module apcmastersnmp +# +# 1. what does the fields on the line mean ? +# +# all parameters must be given on a single line. blank lines and lines +# starting with '#' are ignored. only the first not ignored line will +# be processed. all subsequent lines will be ignored. the different +# fields must be seperated by white-spaces (blanks and/or tabs). +# +# the first field is the either the hostname or the ip address. the +# hostname must be resolvable. the second fields specifies the snmp port +# the masterswitch is listening. for snmp the default is 161. the last +# field contains the so called 'community' string. this must be the same +# as the one in the masterswitch configuration. +# +# +# 2. how must the masterswitch be configured ? +# +# as said above, the community string must be set to the same value entered +# in this config. the different outlets must be named after the connected +# hosts. that means, the outlet names must be the same as the node names +# in /etc/ha.d/ha.cf. the reset values should be set to reasonable values. +# +# the module DON'T configure the module in any way! +# +# +# 3. how does the module work ? +# +# in case of a stonith the module receives the nodename of the host, which +# should be reset. the module looks up this nodename in the list of outlet +# names. that's why the names must be identical (see 2.). if it finds the +# name, it'll reset the appropriate outlet using the configured values +# (eg. delay, duration). then the module waits for the outlet to coming +# up. if it comes up, a successful stonith will be reported back. otherwise +# the stonith failed and a failure code will be returned. +# + +192.168.1.110 161 private diff --git a/lib/plugins/stonith/apcsmart.c b/lib/plugins/stonith/apcsmart.c new file mode 100644 index 0000000..18d1612 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.c @@ -0,0 +1,1028 @@ +/* + * Stonith module for APCSmart Stonith device + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * 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 + * + * Original version of this UPS code was taken from: + * 'Network UPS Tools' by Russell Kroll <rkroll@exploits.org> + * homepage: http://www.networkupstools.org/ + * + * Significantly mangled by Alan Robertson <alanr@unix.sh> + */ + +#include <lha_internal.h> + +#define DEVICE "APCSmart" + +#include "stonith_plugin_common.h" + +/* + * APCSmart (tested with old 900XLI, APC SmartUPS 700 and SmartUPS-1000) + * + * The reset is a combined reset: "S" and "@000" + * The "S" command tells the ups that if it is on-battery, it should + * remain offline until the power is back. + * If that command is not accepted, the "@000" command will be sent + * to tell the ups to turn off and back on right away. + * In both cases, if the UPS supports a 20 second shutdown grace + * period (such as on the 900XLI), the shutdown will delay that long, + * otherwise the shutdown will happen immediately (the code searches + * for the smallest possible delay). + */ + +#define CFG_FILE "/etc/ha.d/apcsmart.cfg" + +#define MAX_DEVICES 1 + +#define SERIAL_TIMEOUT 3 /* timeout in sec */ +#define SEND_DELAY 50000 /* in microseconds */ +#define ENDCHAR 10 /* use LF */ +#define MAX_STRING 512 +#define MAX_DELAY_STRING 16 +#define SWITCH_TO_NEXT_VAL "-" /* APC cmd for cycling through + * the values + */ + +#define CMD_SMART_MODE "Y" +#define RSP_SMART_MODE "SM" +#define CMD_GET_STATUS "Q" +#define RSP_GET_STATUS NULL +#define CMD_RESET "S" /* turn off & stay off if on battery */ +#define CMD_RESET2 "@000" /* turn off & immediately turn on */ +#define RSP_RESET "*" /* RESET response from older models */ +#define RSP_RESET2 "OK" /* RESET response from newer models */ +#define RSP_NA "NA" +#define CMD_READREG1 "~" +#define CMD_OFF "Z" +#define CMD_ON "\016" /* (control-n) */ +#define CMD_SHUTDOWN_DELAY "p" +#define CMD_WAKEUP_DELAY "r" + +#define CR 13 + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; /* of object */ + const char * idinfo; /* type of device */ + char ** hostlist; /* served by the device (only 1) */ + int hostcount;/* of hosts (1) */ + char * upsdev; /* */ + int upsfd; /* for serial port */ + int retries; + char shutdown_delay[MAX_DELAY_STRING]; + char old_shutdown_delay[MAX_DELAY_STRING]; + char wakeup_delay[MAX_DELAY_STRING]; + char old_wakeup_delay[MAX_DELAY_STRING]; +}; + +/* saving old settings */ +/* FIXME! These should be part of pluginDevice struct above */ +static struct termios old_tio; + +static int f_serialtimeout; /* flag for timeout */ +static const char *pluginid = "APCSmart-Stonith"; +static const char *NOTpluginID = "APCSmart device has been destroyed"; + +/* + * stonith prototypes + */ + +#define PIL_PLUGIN apcsmart +#define PIL_PLUGIN_S "apcsmart" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * apcsmart_new(const char *); +static void apcsmart_destroy(StonithPlugin *); +static const char * const * apcsmart_get_confignames(StonithPlugin*); +static int apcsmart_set_config(StonithPlugin *, StonithNVpair*); +static const char * apcsmart_get_info(StonithPlugin * s, int InfoType); +static int apcsmart_status(StonithPlugin * ); +static int apcsmart_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcsmart_hostlist(StonithPlugin *); + +static struct stonith_ops apcsmartOps ={ + apcsmart_new, /* Create new STONITH object */ + apcsmart_destroy, /* Destroy STONITH object */ + apcsmart_get_info, /* Return STONITH info string */ + apcsmart_get_confignames, /* Return STONITH info string */ + apcsmart_set_config, /* Get configuration from NVpairs */ + apcsmart_status, /* Return STONITH device status */ + apcsmart_reset_req, /* Request a reset */ + apcsmart_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 + , &apcsmartOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#include "stonith_config_xml.h" + +static const char *apcsmartXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +int APC_open_serialport(const char *port, speed_t speed); +void APC_close_serialport(const char *port, int upsfd); +void APC_sh_serial_timeout(int sig); +int APC_send_cmd(int upsfd, const char *cmd); +int APC_recv_rsp(int upsfd, char *rsp); +int APC_enter_smartmode(int upsfd); +int APC_set_ups_var(int upsfd, const char *cmd, char *newval); +int APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay); +int APC_init( struct pluginDevice *ad ); +void APC_deinit( struct pluginDevice *ad ); + +/* + * Signal handler for serial port timeouts + */ + +void +APC_sh_serial_timeout(int sig) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + STONITH_IGNORE_SIG(SIGALRM); + + if (Debug) { + LOG(PIL_DEBUG, "%s: serial port timed out.", __FUNCTION__); + } + + f_serialtimeout = TRUE; + + return; +} + +/* + * Open serial port and set it to b2400 + */ + +int +APC_open_serialport(const char *port, speed_t speed) +{ + struct termios tio; + int fd; + int rc; + int errno_save; + int fflags; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if ((rc = OurImports->TtyLock(port)) < 0) { + LOG(PIL_CRIT, "%s: Could not lock tty %s [rc=%d]." + , __FUNCTION__, port, rc); + return -1; + } + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + alarm(SERIAL_TIMEOUT); + f_serialtimeout = FALSE; + + fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL); + errno_save = errno; + + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + if (fd < 0) { + LOG(PIL_CRIT, "%s: Open of %s %s [%s].", __FUNCTION__ + , port + , f_serialtimeout ? "timed out" : "failed" + , strerror(errno_save)); + OurImports->TtyUnlock(port); + return -1; + } + + if ((fflags = fcntl(fd, F_GETFL)) < 0 + || fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) { + LOG(PIL_CRIT, "%s: Setting flags on %s failed [%s]." + , __FUNCTION__ + , port + , strerror(errno_save)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + if (tcgetattr(fd, &old_tio) < 0) { + LOG(PIL_CRIT, "%s: tcgetattr of %s failed [%s].", __FUNCTION__ + , port + , strerror(errno)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + memcpy(&tio, &old_tio, sizeof(struct termios)); + tio.c_cflag = CS8 | CLOCAL | CREAD; + tio.c_iflag = IGNPAR; + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + + cfsetispeed(&tio, speed); + cfsetospeed(&tio, speed); + + tcflush(fd, TCIOFLUSH); + tcsetattr(fd, TCSANOW, &tio); + + return (fd); +} + +/* + * Close serial port and restore old settings + */ + +void +APC_close_serialport(const char *port, int upsfd) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (upsfd < 0) { + return; + } + + tcflush(upsfd, TCIFLUSH); + tcsetattr(upsfd, TCSANOW, &old_tio); + close(upsfd); + if (port != NULL) { + OurImports->TtyUnlock(port); + } +} + +/* + * Send a command to the ups + */ + +int +APC_send_cmd(int upsfd, const char *cmd) +{ + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s(\"%s\")", __FUNCTION__, cmd); + } + + tcflush(upsfd, TCIFLUSH); + for (i = strlen(cmd); i > 0; i--) { + if (write(upsfd, cmd++, 1) != 1) { + return (S_ACCESS); + } + + usleep(SEND_DELAY); + } + return (S_OK); +} + +/* + * Get the response from the ups + */ + +int +APC_recv_rsp(int upsfd, char *rsp) +{ + char *p = rsp; + char inp; + int num = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + *p = '\0'; + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + + alarm(SERIAL_TIMEOUT); + + while (num < MAX_STRING) { + + if (read(upsfd, &inp, 1) == 1) { + + /* shutdown sends only a '*' without LF */ + if ((inp == '*') && (num == 0)) { + *p++ = inp; + num++; + inp = ENDCHAR; + } + + if (inp == ENDCHAR) { + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + *p = '\0'; + if (Debug) { + LOG(PIL_DEBUG, "return(\"%s\")/*%s*/;" + , rsp, __FUNCTION__); + } + return (S_OK); + } + + if (inp != CR) { + *p++ = inp; + num++; + } + }else{ + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + *p = '\0'; + LOG(PIL_DEBUG, "%s: %s.", __FUNCTION__, + f_serialtimeout ? "timeout" : + "can't access device" ); + return (f_serialtimeout ? S_TIMEOUT : S_ACCESS); + } + } + return (S_ACCESS); +} + +/* + * Enter smart mode + */ + +int +APC_enter_smartmode(int upsfd) +{ + int rc; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + strcpy(resp, RSP_SMART_MODE); + + if (((rc = APC_send_cmd(upsfd, CMD_SMART_MODE)) == S_OK) + && ((rc = APC_recv_rsp(upsfd, resp)) == S_OK) + && (strcmp(RSP_SMART_MODE, resp) == 0)) { + return (S_OK); + } + + return (S_ACCESS); +} + +/* + * Set a value in the hardware using the <cmdchar> '-' (repeat) approach + */ + +int +APC_set_ups_var(int upsfd, const char *cmd, char *newval) +{ + char resp[MAX_STRING]; + char orig[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' original val %s" + , __FUNCTION__, cmd, orig); + } + + if (strcmp(orig, newval) == 0) { + return (S_OK); /* already set */ + } + + *resp = '\0'; + + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (strcmp(resp, newval) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' set to %s" + , __FUNCTION__, cmd, newval); + } + + strcpy(newval, orig); /* return the old value */ + return (S_OK); /* got it */ + } + } + + LOG(PIL_CRIT, "%s(): Could not set variable '%s' to %s!" + , __FUNCTION__, cmd, newval); + LOG(PIL_CRIT, "%s(): This UPS may not support STONITH :-(" + , __FUNCTION__); + + return (S_OOPS); +} + +/* + * Query the smallest delay supported by the hardware using the + * <cmdchar> '-' (repeat) approach and looping through all possible values, + * saving the smallest + */ + +int +APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay) +{ + char resp[MAX_DELAY_STRING]; + char orig[MAX_DELAY_STRING]; + int delay, smallest; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + smallest = atoi(orig); + strcpy(smdelay, orig); + + *resp = '\0'; + + /* search for smallest delay; need to loop through all possible + * values so that we leave delay the way we found it */ + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if ((delay = atoi(resp)) < smallest) { + smallest = delay; + strcpy(smdelay, resp); + } + } + + return (S_OK); +} + +/* + * Initialize the ups + */ + +int +APC_init(struct pluginDevice *ad) +{ + int upsfd; + char value[MAX_DELAY_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* if ad->upsfd != -1 device has already been configured. */ + /* Just enter smart mode again because otherwise a SmartUPS-1000 */ + /* has been observed to sometimes not respond. */ + if(ad->upsfd >= 0) { + if(APC_enter_smartmode(ad->upsfd) != S_OK) { + return(S_OOPS); + } + return S_OK; + } + + /* open serial port and store the fd in ad->upsfd */ + if ((upsfd = APC_open_serialport(ad->upsdev, B2400)) == -1) { + return S_OOPS; + } + + /* switch into smart mode */ + if (APC_enter_smartmode(upsfd) != S_OK) { + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the smallest possible delays for this particular hardware */ + if (APC_get_smallest_delay(upsfd, CMD_SHUTDOWN_DELAY + , ad->shutdown_delay) != S_OK + || APC_get_smallest_delay(upsfd, CMD_WAKEUP_DELAY + , ad->wakeup_delay) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't retrieve smallest delay from UPS" + , __FUNCTION__); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the old settings and store them */ + strcpy(value, ad->shutdown_delay); + if (APC_set_ups_var(upsfd, CMD_SHUTDOWN_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set shutdown delay to %s" + , __FUNCTION__, ad->shutdown_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_shutdown_delay, value); + strcpy(value, ad->wakeup_delay); + if (APC_set_ups_var(upsfd, CMD_WAKEUP_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set wakeup delay to %s" + , __FUNCTION__, ad->wakeup_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_wakeup_delay, value); + + ad->upsfd = upsfd; + return S_OK; +} + +/* + * Restore original settings and close the port + */ + +void +APC_deinit(struct pluginDevice *ad) +{ + APC_enter_smartmode( ad->upsfd ); + + APC_set_ups_var(ad->upsfd, CMD_SHUTDOWN_DELAY, ad->old_shutdown_delay); + APC_set_ups_var(ad->upsfd, CMD_WAKEUP_DELAY, ad->old_wakeup_delay); + + /* close serial port */ + if (ad->upsfd >= 0) { + APC_close_serialport(ad->upsdev, ad->upsfd); + ad->upsfd = -1; + } +} +static const char * const * +apcsmart_get_confignames(StonithPlugin* sp) +{ + static const char * names[] = {ST_TTYDEV, ST_HOSTLIST, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Stash away the config info we've been given... + */ + +static int +apcsmart_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice * ad = (struct pluginDevice*)s; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ad->upsdev = namestocopy[0].s_value; + ad->hostlist = OurImports->StringToHostList(namestocopy[1].s_value); + FREE(namestocopy[1].s_value); + + if (ad->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (ad->hostcount = 0; ad->hostlist[ad->hostcount] + ; ad->hostcount++) { + strdown(ad->hostlist[ad->hostcount]); + } + if (access(ad->upsdev, R_OK|W_OK|F_OK) < 0) { + LOG(PIL_CRIT,"Cannot access tty [%s]", ad->upsdev); + return S_BADCONFIG; + } + + return ad->hostcount ? S_OK : S_BADCONFIG; +} + +/* + * return the status for this device + */ + +static int +apcsmart_status(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + char resp[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + + /* get status */ + if (((rc = APC_init( ad )) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_GET_STATUS)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)) { + return (S_OK); /* everything ok. */ + } + if (Debug) { + LOG(PIL_DEBUG, "%s: failed, rc=%d.", __FUNCTION__, rc); + } + return (rc); +} + + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcsmart_hostlist(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFNOTCONFIGED(s,NULL); + + return OurImports->CopyHostList((const char **)(void*)ad->hostlist); +} + +static gboolean +apcsmart_RegisterBitsSet(struct pluginDevice * ad, int nreg, unsigned bits +, gboolean* waserr) +{ + const char* reqregs[4] = {"?", "~", "'", "8"}; + unsigned regval; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + + if (APC_enter_smartmode(ad->upsfd) != S_OK + || APC_send_cmd(ad->upsfd, reqregs[nreg]) != S_OK + || APC_recv_rsp(ad->upsfd, resp) != S_OK + || (sscanf(resp, "%02x", ®val) != 1)) { + if (waserr){ + *waserr = TRUE; + } + return FALSE; + } + if (waserr){ + *waserr = FALSE; + } + return ((regval & bits) == bits); +} + +#define apcsmart_IsPoweredOff(ad, err) apcsmart_RegisterBitsSet(ad,1,0x40,err) +#define apcsmart_ResetHappening(ad,err) apcsmart_RegisterBitsSet(ad,3,0x08,err) + + +static int +apcsmart_ReqOnOff(struct pluginDevice * ad, int request) +{ + const char * cmdstr; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + cmdstr = (request == ST_POWEROFF ? CMD_OFF : CMD_ON); + /* enter smartmode, send on/off command */ + if ((rc =APC_enter_smartmode(ad->upsfd)) != S_OK + || (rc = APC_send_cmd(ad->upsfd, cmdstr)) != S_OK) { + return rc; + } + sleep(2); + if ((rc = APC_send_cmd(ad->upsfd, cmdstr)) == S_OK) { + gboolean ison; + gboolean waserr; + sleep(1); + ison = !apcsmart_IsPoweredOff(ad, &waserr); + if (waserr) { + return S_RESETFAIL; + } + if (request == ST_POWEROFF) { + return ison ? S_RESETFAIL : S_OK; + }else{ + return ison ? S_OK : S_RESETFAIL; + } + } + return rc; +} + +/* + * reset the host + */ + +static int +apcsmart_ReqGenericReset(struct pluginDevice *ad) +{ + char resp[MAX_STRING]; + int rc = S_RESETFAIL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* send reset command(s) */ + if (((rc = APC_init(ad)) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_RESET)) == S_OK)) { + if (((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* first kind of reset command was accepted */ + } else if (((rc = APC_send_cmd(ad->upsfd, CMD_RESET2)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* second kind of command was accepted */ + } else { + if (Debug) { + LOG(PIL_DEBUG, "APC: neither reset command " + "was accepted"); + } + rc = S_RESETFAIL; + } + } + if (rc == S_OK) { + /* we wait grace period + up to 10 seconds after shutdown */ + int maxdelay = atoi(ad->shutdown_delay)+10; + int j; + + for (j=0; j < maxdelay; ++j) { + gboolean err; + if (apcsmart_ResetHappening(ad, &err)) { + return err ? S_RESETFAIL : S_OK; + } + sleep(1); + } + LOG(PIL_CRIT, "%s: timed out waiting for reset to end." + , __FUNCTION__); + return S_RESETFAIL; + + }else{ + if (strcmp(resp, RSP_NA) == 0){ + gboolean iserr; + /* This means it's currently powered off */ + /* or busy on a previous command... */ + if (apcsmart_IsPoweredOff(ad, &iserr)) { + if (iserr) { + LOG(PIL_DEBUG, "%s: power off " + "detection failed.", __FUNCTION__); + return S_RESETFAIL; + } + if (Debug) { + LOG(PIL_DEBUG, "APC: was powered off, " + "powering back on."); + } + return apcsmart_ReqOnOff(ad, ST_POWERON); + } + } + } + strcpy(resp, "?"); + + /* reset failed */ + + return S_RESETFAIL; +} + +static int +apcsmart_reset_req(StonithPlugin * s, int request, const char *host) +{ + char ** hl; + int b_found=FALSE; + struct pluginDevice * ad = (struct pluginDevice *) s; + int rc; + + ERRIFNOTCONFIGED(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "%s: invalid hostname argument.", __FUNCTION__); + return (S_INVAL); + } + + /* look through the hostlist */ + hl = ad->hostlist; + + while (*hl && !b_found ) { + if( strcasecmp( *hl, host ) == 0 ) { + b_found = TRUE; + break; + }else{ + ++hl; + } + } + + /* host not found in hostlist */ + if( !b_found ) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist." + , __FUNCTION__, host); + return S_BADHOST; + } + if ((rc = APC_init(ad)) != S_OK) { + return rc; + } + + if (request == ST_POWERON || request == ST_POWEROFF) { + return apcsmart_ReqOnOff(ad, request); + } + return apcsmart_ReqGenericReset(ad); +} + + +/* + * get info about the stonith device + */ + +static const char * +apcsmart_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + const char *ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->upsdev; + break; + + case ST_DEVICEDESCR: + ret = "APC Smart UPS\n" + " (via serial port - NOT USB!). \n" + " Works with higher-end APC UPSes, like\n" + " Back-UPS Pro, Smart-UPS, Matrix-UPS, etc.\n" + " (Smart-UPS may have to be >= Smart-UPS 700?).\n" + " See http://www.networkupstools.org/protocols/apcsmart.html\n" + " for protocol compatibility details."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcsmartXML; + break; + + default: + ret = NULL; + break; + } + return (ret); +} + +/* + * APC Stonith destructor... + */ + +static void +apcsmart_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + VOIDERRIFWRONGDEV(s); + + if (ad->upsfd >= 0 && ad->upsdev) { + APC_deinit( ad ); + } + + ad->pluginid = NOTpluginID; + + if (ad->hostlist) { + stonith_free_hostlist(ad->hostlist); + ad->hostlist = NULL; + } + if (ad->upsdev != NULL) { + FREE(ad->upsdev); + ad->upsdev = NULL; + } + + ad->hostcount = -1; + ad->upsfd = -1; + + FREE(ad); + +} + +/* + * Create a new APC Stonith device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcsmart_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + memset(ad, 0, sizeof(*ad)); + + ad->pluginid = pluginid; + ad->hostlist = NULL; + ad->hostcount = -1; + ad->upsfd = -1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcsmartOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully.", __FUNCTION__); + } + return &(ad->sp); +} diff --git a/lib/plugins/stonith/apcsmart.cfg.example b/lib/plugins/stonith/apcsmart.cfg.example new file mode 100644 index 0000000..278f925 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.cfg.example @@ -0,0 +1 @@ +/dev/ups hostname diff --git a/lib/plugins/stonith/baytech.c b/lib/plugins/stonith/baytech.c new file mode 100644 index 0000000..33093ad --- /dev/null +++ b/lib/plugins/stonith/baytech.c @@ -0,0 +1,924 @@ +/* + * Stonith module for BayTech Remote Power Controllers (RPC-x devices) + * + * 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> +#define DEVICE "BayTech power switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN baytech +#define PIL_PLUGIN_S "baytech" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * baytech_new(const char *); +static void baytech_destroy(StonithPlugin *); +static int baytech_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * baytech_get_confignames(StonithPlugin * s); +static const char * baytech_get_info(StonithPlugin * s, int InfoType); +static int baytech_status(StonithPlugin *); +static int baytech_reset_req(StonithPlugin * s, int request, const char * host); +static char ** baytech_hostlist(StonithPlugin *); + +static struct stonith_ops baytechOps ={ + baytech_new, /* Create new STONITH object */ + baytech_destroy, /* Destroy STONITH object */ + baytech_get_info, /* Return STONITH info string */ + baytech_get_confignames, /* Return STONITH config vars */ + baytech_set_config, /* set configuration from vars */ + baytech_status, /* Return STONITH device status */ + baytech_reset_req, /* Request a reset */ + baytech_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; + +#include "stonith_expect_helpers.h" + +#define MAXOUTLET 32 + +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 + , &baytechOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an RPC-5. This code has been tested with this switch. + * + * The BayTech switches are quite nice, but the dialogues are a bit of a + * pain for mechanical parsing. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * unitid; + const struct BayTechModelInfo* modelinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +struct BayTechModelInfo { + const char * type; /* Baytech model info */ + size_t socklen; /* Length of socket name string */ + struct Etoken * expect; /* Expect string before outlet list */ +}; + +static int parse_socket_line(struct pluginDevice*,const char * +, int *, char *); + +static const char * pluginid = "BayTech-Stonith"; +static const char * NOTpluginID = "BayTech device has been destroyed"; + +/* + * Different expect strings that we get from the Baytech + * Remote Power Controllers... + */ + +#define BAYTECHASSOC "Bay Technical Associates" + +static struct Etoken BayTechAssoc[] = { {BAYTECHASSOC, 0, 0}, {NULL,0,0}}; +static struct Etoken UnitId[] = { {"Unit ID: ", 0, 0}, {NULL,0,0}}; +static struct Etoken login[] = { {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken password[] = { {"password>", 0, 0} + , {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken Selection[] = { {"election>", 0, 0} ,{NULL,0,0}}; +static struct Etoken RPC[] = { {"RPC", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {"RPC", 0, 0}, {"Invalid password", 1, 0} + , {NULL,0,0}}; +static struct Etoken GTSign[] = { {">", 0, 0} ,{NULL,0,0}}; +static struct Etoken Menu[] = { {"Menu:", 0, 0} ,{NULL,0,0}}; +static struct Etoken Temp[] = { {"emperature: ", 0, 0} + , {NULL,0,0}}; +static struct Etoken Break[] = { {"Status", 0, 0} + , {NULL,0,0}}; +static struct Etoken PowerApplied[] = { {"ower applied to outlet", 0, 0} + , {NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Rebooting[] = { {"ebooting selected outlet", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already off.", 2, 0} + , {NULL,0,0}}; + +static struct Etoken TurningOnOff[] = { {"RPC", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already ", 2, 0} + , {NULL,0,0}}; + + +static struct BayTechModelInfo ModelInfo [] = { + {"BayTech RPC-5", 18, Temp},/* This first model will be the default */ + {"BayTech RPC-3", 10, Break}, + {"BayTech RPC-3A", 10, Break}, + {NULL, 0, NULL}, +}; + +#include "stonith_config_xml.h" + +static const char *baytechXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int RPC_connect_device(struct pluginDevice * bt); +static int RPCLogin(struct pluginDevice * bt); +static int RPCRobustLogin(struct pluginDevice * bt); +static int RPCNametoOutletList(struct pluginDevice*, const char * name +, int outletlist[]); +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +static int RPCLogout(struct pluginDevice * bt); + + +static int RPC_onoff(struct pluginDevice*, int unitnum, const char * unitid +, int request); + +/* Login to the Baytech Remote Power Controller (RPC) */ + +static int +RPCLogin(struct pluginDevice * bt) +{ + char IDinfo[128]; + static char IDbuf[128]; + char * idptr = IDinfo; + char * delim; + int j; + + EXPECT(bt->rdfd, RPC, 10); + + /* Look for the unit type info */ + if (EXPECT_TOK(bt->rdfd, BayTechAssoc, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", bt->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * RPC-5 Telnet Host + * Revision F 4.22, (C) 1999 + * Bay Technical Associates + */ + + /* Truncate the result after the RPC-5 part */ + if ((delim = strchr(idptr, ' ')) != NULL) { + *delim = EOS; + } + snprintf(IDbuf, sizeof(IDbuf), "BayTech RPC%s", idptr); + REPLSTR(bt->idinfo, IDbuf); + if (bt->idinfo == NULL) { + return(S_OOPS); + } + + bt->modelinfo = &ModelInfo[0]; + + for (j=0; ModelInfo[j].type != NULL; ++j) { + /* + * TIMXXX - + * Look at device ID as this really describes the model. + */ + if (strcasecmp(ModelInfo[j].type, IDbuf) == 0) { + bt->modelinfo = &ModelInfo[j]; + break; + } + } + + /* Look for the unit id info */ + EXPECT(bt->rdfd, UnitId, 10); + SNARF(bt->rdfd, IDbuf, 2); + delim = IDbuf + strcspn(IDbuf, WHITESPACE); + *delim = EOS; + REPLSTR(bt->unitid, IDbuf); + if (bt->unitid == NULL) { + return(S_OOPS); + } + + /* Expect "username>" */ + EXPECT(bt->rdfd, login, 2); + + SEND(bt->wrfd, bt->user); + SEND(bt->wrfd, "\r"); + + /* Expect "password>" */ + + switch (StonithLookFor(bt->rdfd, password, 5)) { + case 0: /* Good! */ + break; + + case 1: /* OOPS! got another username prompt */ + LOG(PIL_CRIT, "Invalid username for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + SEND(bt->wrfd, bt->passwd); + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + + switch (StonithLookFor(bt->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + EXPECT(bt->rdfd, Menu, 2); + + return(S_OK); +} + +static int +RPCRobustLogin(struct pluginDevice * bt) +{ + int rc=S_OOPS; + int j; + + for (j=0; j < 20 && rc != S_OK; ++j) { + + + if (RPC_connect_device(bt) != S_OK) { + continue; + } + + rc = RPCLogin(bt); + } + return rc; +} + +/* Log out of the Baytech RPC */ + +static int +RPCLogout(struct pluginDevice* bt) +{ + int rc; + + /* Make sure we're in the right menu... */ + SEND(bt->wrfd, "\r"); + + /* Expect "Selection>" */ + rc = StonithLookFor(bt->rdfd, Selection, 5); + + /* Option 6 is Logout */ + SEND(bt->wrfd, "6\r"); + + close(bt->wrfd); + close(bt->rdfd); + bt->wrfd = bt->rdfd = -1; + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlet number */ +static int +RPCReset(struct pluginDevice* bt, int unitnum, const char * rebootid) +{ + char unum[32]; + + + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send REBOOT command for given outlet */ + snprintf(unum, sizeof(unum), "REBOOT %d\r", unitnum); + SEND(bt->wrfd, unum); + + /* Expect "ebooting "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(bt->rdfd, Rebooting, 5)) { + case 0: /* Got "Rebooting" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + goto retry; + + case 2: /* Outlet is turned off */ + LOG(PIL_CRIT, "Host is OFF: %s.", rebootid); + return(S_ISOFF); + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host %s (outlet %d) being rebooted." + , rebootid, unitnum); + + /* Expect "ower applied to outlet" */ + if (StonithLookFor(bt->rdfd, PowerApplied, 30) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host %s (outlet %d)." + , rebootid, unitnum); + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC,5); + EXPECT(bt->rdfd, GTSign, 5); + + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +static int +RPC_onoff(struct pluginDevice* bt, int unitnum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "on" : "off"); + int rc; + + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %d\r" + , onoff, unitnum); + SEND(bt->wrfd, unum); + + /* Expect "RPC->x "... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(bt->rdfd, TurningOnOff, 10) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + EXPECT(bt->rdfd, TurningOnOff, 10); + } + + EXPECT(bt->rdfd, GTSign, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to host %s (outlet %d) turned %s." + , unitid, unitnum, onoff); + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +RPCNametoOutletList(struct pluginDevice* bt, const char * name +, int outletlist[]) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int maxfound = 0; + + + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + EXPECT(bt->rdfd, bt->modelinfo->expect, 5); + EXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + char * last; + NameMapping[0] = EOS; + SNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + outletlist[maxfound] = sockno; + ++maxfound; + } + } while (strlen(NameMapping) > 2 && maxfound < MAXOUTLET); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + return(maxfound); +} + +static int +baytech_status(StonithPlugin *s) +{ + struct pluginDevice* bt; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + return(RPCLogout(bt)); +} +/* + * Return the list of hosts (outlet names) for the devices on this BayTech unit + */ + +static char ** +baytech_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* bt; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + bt = (struct pluginDevice*) s; + + if (RPCRobustLogin(bt) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(NULL); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + NULLEXPECT(bt->rdfd, bt->modelinfo->expect, 5); + NULLEXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + char * last; + char * nm; + + NameMapping[0] = EOS; + + NULLSNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)RPCLogout(bt); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + return(NULL); +} + +/* + * Connect to the given BayTech device. + * We should add serial support here eventually... + */ +static int +RPC_connect_device(struct pluginDevice * bt) +{ + int fd = OurImports->OpenStreamSocket(bt->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + bt->rdfd = bt->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +baytech_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = 0; + struct pluginDevice* bt; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + }else{ + int noutlets; + int outlets[MAXOUTLET]; + int j; + noutlets = RPCNametoOutletList(bt, host, outlets); + + if (noutlets < 1) { + LOG(PIL_CRIT, "%s %s doesn't control host [%s]" + , bt->idinfo, bt->unitid, host); + return(S_BADHOST); + } + switch(request) { + + case ST_POWERON: + case ST_POWEROFF: + for (j=0; rc == S_OK && j < noutlets;++j) { + rc = RPC_onoff(bt, outlets[j], host, request); + } + break; + case ST_GENERIC_RESET: + /* + * Our strategy here: + * 1. Power off all outlets except the last one + * 2. reset the last outlet + * 3. power the other outlets back on + */ + + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt,outlets[j],host + , ST_POWEROFF); + } + if (rc == S_OK) { + rc = RPCReset(bt, outlets[j], host); + } + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt, outlets[j], host + , ST_POWERON); + } + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = RPCLogout(bt); + + return(rc != S_OK ? rc : lorc); +} + +static const char * const * +baytech_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +baytech_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* bt = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (bt->sp.isconfigured) { + return S_OOPS; + } + + if ((rc =OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + bt->device = namestocopy[0].s_value; + bt->user = namestocopy[1].s_value; + bt->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +baytech_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* bt; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + bt = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = bt->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = bt->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Bay Technical Associates (Baytech) RPC " + "series power switches (via telnet).\n" + "The RPC-5, RPC-3 and RPC-3A switches are well tested" + "."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.baytech.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = baytechXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Baytech Stonith destructor... + */ +static void +baytech_destroy(StonithPlugin *s) +{ + struct pluginDevice* bt; + + VOIDERRIFWRONGDEV(s); + + bt = (struct pluginDevice *)s; + + bt->pluginid = NOTpluginID; + if (bt->rdfd >= 0) { + close(bt->rdfd); + bt->rdfd = -1; + } + if (bt->wrfd >= 0) { + close(bt->wrfd); + bt->wrfd = -1; + } + if (bt->device != NULL) { + FREE(bt->device); + bt->device = NULL; + } + if (bt->user != NULL) { + FREE(bt->user); + bt->user = NULL; + } + if (bt->passwd != NULL) { + FREE(bt->passwd); + bt->passwd = NULL; + } + if (bt->idinfo != NULL) { + FREE(bt->idinfo); + bt->idinfo = NULL; + } + if (bt->unitid != NULL) { + FREE(bt->unitid); + bt->unitid = NULL; + } + FREE(bt); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +baytech_new(const char *subplugin) +{ + struct pluginDevice* bt = ST_MALLOCT(struct pluginDevice); + + if (bt == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(bt, 0, sizeof(*bt)); + bt->pluginid = pluginid; + bt->pid = -1; + bt->rdfd = -1; + bt->wrfd = -1; + REPLSTR(bt->idinfo, DEVICE); + if (bt->idinfo == NULL) { + FREE(bt); + return(NULL); + } + bt->modelinfo = &ModelInfo[0]; + bt->sp.s_ops = &baytechOps; + + return &(bt->sp); /* same as "bt" */ +} + +static int +parse_socket_line(struct pluginDevice * bt, const char *NameMapping +, int *sockno, char *sockname) +{ +#if 0 + char format[64]; + snprintf(format, sizeof(format), "%%7d %%%dc" + , bt->modelinfo->socklen); + /* 7 digits, 7 blanks, then 'socklen' characters */ + /* [0-6]: digits, NameMapping[13] begins the sockname */ + /* NameMapping strlen must be >= socklen + 14 */ + + if (sscanf(NameMapping, format, sockno, sockname) != 2) { + return FALSE; + } +#else +# define OFFSET 14 + + if (sscanf(NameMapping, "%7d", sockno) != 1 + || strlen(NameMapping) < OFFSET+bt->modelinfo->socklen) { + return FALSE; + } + strncpy(sockname, NameMapping+OFFSET, bt->modelinfo->socklen); + sockname[bt->modelinfo->socklen] = EOS; +#endif + return TRUE; +} diff --git a/lib/plugins/stonith/bladehpi.c b/lib/plugins/stonith/bladehpi.c new file mode 100644 index 0000000..ae9a4cf --- /dev/null +++ b/lib/plugins/stonith/bladehpi.c @@ -0,0 +1,1101 @@ +/* + * Stonith module for BladeCenter via OpenHPI, an implementation of Service + * Availability Forum's Hardware Platfrom Interface + * + * Author: Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2005 International Business Machines + * + * 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> + +#define DEVICE "IBM BladeCenter (OpenHPI)" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN bladehpi +#define PIL_PLUGIN_S "bladehpi" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <openhpi/SaHpi.h> + +/* Maximum number of seconds to wait for host to power off */ +#define MAX_POWEROFF_WAIT 60 + +/* entity_root, the one required plugin parameter */ +#define ST_ENTITYROOT "entity_root" + +/* String format of entity_root */ +#define SYSTEM_CHASSIS_FMT "{SYSTEM_CHASSIS,%d}" + +/* soft_reset, the one optional plugin parameter */ +#define ST_SOFTRESET "soft_reset" + +#define OPENHPIURL "http://www.openhpi.org/" + +/* OpenHPI resource types of interest to this plugin */ +#define OHRES_NONE 0 +#define OHRES_BLADECENT 1 +#define OHRES_MGMTMOD 2 +#define OHRES_BLADE 3 + +/* IBMBC_WAIT_FOR_OFF - This constant has to do with the problem that + saHpiResourcePowerStateSet can return before the desired state has been + achieved by the blade. In the SAHPI_POWER_OFF case this is not good, + as whoever calls this plugin assumes that the power is actually off + when the plugin returns with a successful return code. Define this + constant to build code that loops in one second intervals after calling + saHpiResourcePowerStateSet(SAHPI_POWER_OFF) to make sure the power is + really off. +#define IBMBC_WAIT_FOR_OFF */ + +static StonithPlugin * bladehpi_new(const char *); +static void bladehpi_destroy(StonithPlugin *); +static const char * bladehpi_getinfo(StonithPlugin *, int); +static const char * const * bladehpi_get_confignames(StonithPlugin *); +static int bladehpi_status(StonithPlugin *); +static int bladehpi_reset_req(StonithPlugin *, int, const char *); +static char ** bladehpi_hostlist(StonithPlugin *); +static int bladehpi_set_config(StonithPlugin *, StonithNVpair *); + +static struct stonith_ops bladehpiOps = { + bladehpi_new, /* Create new STONITH object */ + bladehpi_destroy, /* Destroy STONITH object */ + bladehpi_getinfo, /* Return STONITH info string */ + bladehpi_get_confignames, /* Return configuration parameters */ + bladehpi_set_config, /* Set configuration */ + bladehpi_status, /* Return STONITH device status */ + bladehpi_reset_req, /* Request a reset */ + bladehpi_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 + , &bladehpiOps + , NULL /* close */ + , &OurInterface + , (void *)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * device; + int softreset; + GList * hostlist; + SaHpiVersionT ohver; /* OpenHPI interface version */ + SaHpiSessionIdT ohsession; /* session ID */ + SaHpiUint32T ohrptcnt; /* RPT count for hostlist */ + SaHpiResourceIdT ohdevid; /* device resource ID */ + SaHpiResourceIdT ohsensid; /* sensor resource ID */ + SaHpiSensorNumT ohsensnum; /* sensor number */ +}; + +static int open_hpi_session(struct pluginDevice *dev); +static void close_hpi_session(struct pluginDevice *dev); + +static const char *pluginid = "BladeCenterDevice-Stonith"; +static const char *NOTpluginID = "IBM BladeCenter device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_ENTITYROOT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_ENTITYROOT \ + XML_PARM_SHORTDESC_END + +#define XML_ENTITYROOT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The entity_root of the STONITH device from the OpenHPI config file" \ + XML_PARM_LONGDESC_END + +#define XML_ENTITYROOT_PARM \ + XML_PARAMETER_BEGIN(ST_ENTITYROOT, "string", "1", "0") \ + XML_ENTITYROOT_SHORTDESC \ + XML_ENTITYROOT_LONGDESC \ + XML_PARAMETER_END + +#define XML_SOFTRESET_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SOFTRESET \ + XML_PARM_SHORTDESC_END + +#define XML_SOFTRESET_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Soft reset indicator, true|1 if STONITH device should use soft reset (power cycle) to reset nodes, false|0 if device should use hard reset (power off, wait, power on); default is false" \ + XML_PARM_LONGDESC_END + +#define XML_SOFTRESET_PARM \ + XML_PARAMETER_BEGIN(ST_SOFTRESET, "string", "0", "0") \ + XML_SOFTRESET_SHORTDESC \ + XML_SOFTRESET_LONGDESC \ + XML_PARAMETER_END + +static const char *bladehpiXML = + XML_PARAMETERS_BEGIN + XML_ENTITYROOT_PARM + XML_SOFTRESET_PARM + XML_PARAMETERS_END; + +static int get_resource_type(char *, SaHpiRptEntryT *); +static int get_sensor_num(SaHpiSessionIdT, SaHpiResourceIdT); +static int get_bladehpi_hostlist(struct pluginDevice *); +static void free_bladehpi_hostlist(struct pluginDevice *); +static int get_num_tokens(char *str); + +struct blade_info { + char * name; /* blade name */ + SaHpiResourceIdT resourceId; /* blade resource ID */ + SaHpiCapabilitiesT resourceCaps; /* blade capabilities */ +}; + + +static int +bladehpi_status(StonithPlugin *s) +{ + struct pluginDevice * dev; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_BADCONFIG; + goto done; + } + } + + /* At this point, hostlist is up to date */ + if (dev->ohsensid && dev->ohsensnum) { + /* + * For accurate status, need to make a call that goes out to + * BladeCenter MM because the calls made so far by this + * function (and perhaps get_bladehpi_hostlist) only retrieve + * information from memory cached by OpenHPI + */ + ohrc = saHpiSensorReadingGet(dev->ohsession + , dev->ohsensid, dev->ohsensnum, NULL, NULL); + if (ohrc == SA_ERR_HPI_BUSY || ohrc == SA_ERR_HPI_NO_RESPONSE) { + LOG(PIL_CRIT, "Unable to connect to BladeCenter in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + +done: + close_hpi_session(dev); + return (rc == S_OK) ? (dev->ohdevid ? S_OK : S_OOPS) : rc; +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +bladehpi_hostlist(StonithPlugin *s) +{ + struct pluginDevice * dev; + int numnames = 0, j; + char ** ret = NULL; + GList * node = NULL; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return NULL; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + goto done; + } + } + + /* At this point, hostlist is up to date */ + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + goto done; + } + + ret = (char **)MALLOC((numnames+1) * sizeof(char *)); + if (ret == NULL) { + LOG(PIL_CRIT, "Out of memory for malloc in %s", __FUNCTION__); + goto done; + } + + memset(ret, 0, (numnames+1) * sizeof(char *)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + ret[j] = STRDUP(((struct blade_info *)node->data)->name); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + stonith_free_hostlist(ret); + ret = NULL; + goto done; + } + strdown(ret[j]); + } + +done: + close_hpi_session(dev); + return ret; +} + + +static const char * const * +bladehpi_get_confignames(StonithPlugin *s) +{ + static const char * names[] = {ST_ENTITYROOT, NULL}; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + return names; +} + + +/* + * Reset the given host, and obey the request type. + */ + +static int +bladehpi_reset_req(StonithPlugin *s, int request, const char *host) +{ + GList * node = NULL; + struct pluginDevice * dev = NULL; + struct blade_info * bi = NULL; + SaHpiPowerStateT ohcurstate, ohnewstate; + SaHpiDomainInfoT ohdi; + SaErrorT ohrc; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, request=%d, host=%s" + , __FUNCTION__, request, host); + } + + ERRIFWRONGDEV(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "Invalid host argument to %s", __FUNCTION__); + rc = S_OOPS; + goto done; + } + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + + for (node = g_list_first(dev->hostlist) + ; node != NULL + ; node = g_list_next(node)) { + bi = ((struct blade_info *)node->data); + if (Debug) { + LOG(PIL_DEBUG, "Found host %s in hostlist", bi->name); + } + + if (!strcasecmp(bi->name, host)) { + break; + } + } + + if (!node || !bi) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module, " + "please check your configuration information", host); + rc = S_OOPS; + goto done; + } + + /* Make sure host has proper capabilities for get */ + if (!(bi->resourceCaps & SAHPI_CAPABILITY_POWER)) { + LOG(PIL_CRIT + , "Host %s does not have power capability", host); + rc = S_OOPS; + goto done; + } + + ohrc = saHpiResourcePowerStateGet(dev->ohsession, bi->resourceId + , &ohcurstate); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + + switch (request) { + case ST_POWERON: + if (ohcurstate == SAHPI_POWER_ON) { + LOG(PIL_INFO, "Host %s already on", host); + goto done; + } + ohnewstate = SAHPI_POWER_ON; + + break; + + case ST_POWEROFF: + if (ohcurstate == SAHPI_POWER_OFF) { + LOG(PIL_INFO, "Host %s already off", host); + goto done; + } + ohnewstate = SAHPI_POWER_OFF; + + break; + + case ST_GENERIC_RESET: + if (ohcurstate == SAHPI_POWER_OFF) { + ohnewstate = SAHPI_POWER_ON; + } else { + ohnewstate = SAHPI_POWER_CYCLE; + } + + break; + + default: + LOG(PIL_CRIT, "Invalid request argument to %s" + , __FUNCTION__); + rc = S_INVAL; + goto done; + } + + if (!dev->softreset && (ohnewstate == SAHPI_POWER_CYCLE)) { + int maxwait; + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_OFF); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " OFF (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + + /* + * Must wait for power off here or subsequent power on request + * may take place while power is still on and thus ignored + */ + maxwait = MAX_POWEROFF_WAIT; + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_ON); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " ON (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + } else { + /* Make sure host has proper capabilities to reset */ + if ((ohnewstate == SAHPI_POWER_CYCLE) && + (!(bi->resourceCaps & SAHPI_CAPABILITY_RESET))) { + LOG(PIL_CRIT + , "Host %s does not have reset capability" + , host); + rc = S_OOPS; + goto done; + } + + if ((ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, ohnewstate)) != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + } + +#ifdef IBMBC_WAIT_FOR_OFF + if (ohnewstate == SAHPI_POWER_OFF) { + int maxwait = MAX_POWEROFF_WAIT; + + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + } +#endif + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + +done: + close_hpi_session(dev); + return rc; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +bladehpi_set_config(StonithPlugin *s, StonithNVpair *list) +{ + struct pluginDevice * dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_ENTITYROOT, NULL} + , {NULL, NULL} + }; + int rc, i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + + if (Debug) { + LOG(PIL_DEBUG, "%s conditionally compiled with:" +#ifdef IBMBC_WAIT_FOR_OFF + " IBMBC_WAIT_FOR_OFF" +#endif + , dev->pluginid); + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s = %s", ST_ENTITYROOT + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for soft_reset */ + const char *softreset = + OurImports->GetValue(list, ST_SOFTRESET); + if (softreset != NULL) { + if (!strcasecmp(softreset, "true") || + !strcmp(softreset, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(softreset, "false") || + !strcmp(softreset, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, softreset); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + } else { + /* -p or -F option with args "entity_root [soft_reset]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over entity_root and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (!strcasecmp(pch, "true") || !strcmp(pch, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(pch, "false") || !strcmp(pch, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, pch); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + + dev->device = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (dev->device == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s", __FUNCTION__); + return S_OOPS; + } + + if (strcspn(dev->device, WHITESPACE) != strlen(dev->device) || + sscanf(dev->device, SYSTEM_CHASSIS_FMT, &i) != 1 || i < 0) { + LOG(PIL_CRIT, "Invalid %s %s, must be of format %s" + , ST_ENTITYROOT, dev->device, SYSTEM_CHASSIS_FMT); + return S_BADCONFIG; + } + + dev->ohver = saHpiVersionGet(); + if (dev->ohver > SAHPI_INTERFACE_VERSION) { + LOG(PIL_CRIT, "Installed OpenHPI interface (%x) greater than " + "one used by plugin (%x), incompatibilites may exist" + , dev->ohver, SAHPI_INTERFACE_VERSION); + return S_BADCONFIG; + } + return S_OK; +} + +static int +open_hpi_session(struct pluginDevice *dev) +{ + SaErrorT ohrc; + + ohrc = saHpiSessionOpen(SAHPI_UNSPECIFIED_DOMAIN_ID + , &dev->ohsession, NULL); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to open HPI session (%d)", ohrc); + return S_BADCONFIG; + } + + ohrc = saHpiDiscover(dev->ohsession); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to discover resources (%d)", ohrc); + return S_BADCONFIG; + } + + return S_OK; +} +static void +close_hpi_session(struct pluginDevice *dev) +{ + if (dev && dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } +} + +static const char * +bladehpi_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice * dev; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, reqtype=%d" + , __FUNCTION__, reqtype); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->device; + break; + + case ST_DEVICEDESCR: + ret = "IBM BladeCenter via OpenHPI\n" + "Use for IBM xSeries systems managed by BladeCenter\n" + " Required parameter name " ST_ENTITYROOT " is " + "a string (no white-space) of\n" + "the format \""SYSTEM_CHASSIS_FMT"\" " + "which is entity_root of BladeCenter\n" + "from OpenHPI config file, where %d is a positive " + "integer\n" + " Optional parameter name " ST_SOFTRESET " is " + "true|1 if STONITH device should\n" + "use soft reset (power cycle) to reset nodes or " + "false|0 if device should\n" + "use hard reset (power off, wait, power on); " + "default is false"; + break; + + case ST_DEVICEURL: + ret = OPENHPIURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = bladehpiXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +bladehpi_destroy(StonithPlugin *s) +{ + struct pluginDevice * dev; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->device) { + FREE(dev->device); + dev->device = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_bladehpi_hostlist(dev); + + if (dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } + + FREE(dev); +} + + +static StonithPlugin * +bladehpi_new(const char *subplugin) +{ + struct pluginDevice * dev = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "Out of memory in %s", __FUNCTION__); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->device = NULL; + dev->hostlist = NULL; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return NULL; + } + dev->sp.s_ops = &bladehpiOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully", __FUNCTION__); + } + + return ((void *)dev); +} + + +static int +get_resource_type(char *entityRoot, SaHpiRptEntryT *ohRPT) +{ + int i, rc = OHRES_NONE; + int foundBlade = 0, foundExp = 0, foundMgmt = 0; + int foundRoot = 0, foundOther = 0; + char rootName[64]; + SaHpiEntityPathT * ohep = &ohRPT->ResourceEntity; + + if (ohep == NULL || entityRoot == NULL) { + return 0; + } + + /* First find root of entity path, which is last entity in entry */ + for (i = 0; i < SAHPI_MAX_ENTITY_PATH; i++) { + if (ohep->Entry[i].EntityType == SAHPI_ENT_ROOT) { + break; + } + } + + /* Then back up through entries looking for specific entity */ + for (i--; i >= 0; i--) { + switch (ohep->Entry[i].EntityType) { + case SAHPI_ENT_SBC_BLADE: + foundBlade = 1; + break; + + case SAHPI_ENT_SYS_EXPANSION_BOARD: + foundExp = 1; + break; + + case SAHPI_ENT_SYS_MGMNT_MODULE: + if (ohep->Entry[i].EntityLocation == 0) { + foundMgmt = 1; + } + break; + + case SAHPI_ENT_SYSTEM_CHASSIS: + snprintf(rootName, sizeof(rootName) + , SYSTEM_CHASSIS_FMT + , ohep->Entry[i].EntityLocation); + if (!strcmp(entityRoot, rootName)) { + foundRoot = 1; + } + break; + + default: + foundOther = 1; + break; + } + } + + /* We are only interested in specific entities on specific device */ + if (foundRoot) { + if (foundMgmt && !(foundBlade||foundExp||foundOther)) { + rc = OHRES_MGMTMOD; + } else if (!(foundMgmt||foundBlade||foundExp||foundOther)) { + rc = OHRES_BLADECENT; + } else if (foundBlade && !foundExp) { + rc = OHRES_BLADE; + } + } + + return rc; +} + + +static int +get_sensor_num(SaHpiSessionIdT ohsession, SaHpiResourceIdT ohresid) +{ + SaErrorT ohrc = SA_OK; + SaHpiEntryIdT ohnextid; + SaHpiRdrT ohRDR; + + ohnextid = SAHPI_FIRST_ENTRY; + do { + ohrc = saHpiRdrGet(ohsession, ohresid, ohnextid + , &ohnextid, &ohRDR); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RDR entry in %s (%d)" + , __FUNCTION__, ohrc); + } else if (ohRDR.RdrType == SAHPI_SENSOR_RDR) { + return ohRDR.RdrTypeUnion.SensorRec.Num; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + return 0; +} + + +/* + * Get RPT update count + * Loop through all RPT entries + * If entry is BladeCenter, save resource ID in dev->ohdevid + * If entry is MgmtMod and has sensor, save resource ID in dev->ohsensid + * and sensor number in dev->ohsensnum + * If entry is blade, save blade_info and add to dev->hostlist + * Get RPT update count + * If RPT update count changed since start of loop, repeat loop + * Save RPT update count in dev->ohrptcnt + * + * Note that not only does this function update hostlist, it also + * updates ohrptcnt, ohdevid, ohsensid and ohsensnum. However, with + * this logic it does not need to be called again until the RPT update + * count changes. + */ + +static int +get_bladehpi_hostlist(struct pluginDevice *dev) +{ + struct blade_info * bi; + SaErrorT ohrc; + SaHpiEntryIdT ohnextid; + SaHpiRptEntryT ohRPT; + SaHpiDomainInfoT ohdi; + SaHpiUint32T ohupdate; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, dev->device=%s" + , __FUNCTION__, dev->device); + } + + if (dev->device == NULL || *dev->device == 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + return S_BADCONFIG; + } + +try_again: + ohupdate = ohdi.RptUpdateCount; + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; + ohnextid = SAHPI_FIRST_ENTRY; + do { + char blname[SAHPI_MAX_TEXT_BUFFER_LENGTH]; + int blnum; + + ohrc = saHpiRptEntryGet(dev->ohsession, ohnextid + , &ohnextid, &ohRPT); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RPT entry in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + switch (get_resource_type(dev->device, &ohRPT)) { + case OHRES_BLADECENT: + dev->ohdevid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG, "BladeCenter '%s' has id %d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohdevid); + } + break; + + case OHRES_MGMTMOD: + if (ohRPT.ResourceCapabilities&SAHPI_CAPABILITY_SENSOR){ + dev->ohsensnum = get_sensor_num(dev->ohsession + , ohRPT.ResourceId); + + if (dev->ohsensnum) { + dev->ohsensid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG + , "MgmtModule '%s' has id %d " + "with sensor #%d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohsensid + , dev->ohsensnum); + } + } + } + break; + + case OHRES_BLADE: + if ((bi = (struct blade_info *) + MALLOC(sizeof(struct blade_info))) == NULL) { + LOG(PIL_CRIT, "Out of memory in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + /* + * New format consists of "Blade N - name" while older + * format consists only of "name"; we only need to + * stash name because ResourceID is the important info + */ + if (sscanf((char*)ohRPT.ResourceTag.Data, "Blade %d - %s" + , &blnum, blname) == 2) { + bi->name = STRDUP(blname); + } else { + bi->name = STRDUP((char*)ohRPT.ResourceTag.Data); + } + if (bi->name == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + bi->resourceId = ohRPT.ResourceId; + bi->resourceCaps = ohRPT.ResourceCapabilities; + dev->hostlist = g_list_append(dev->hostlist, bi); + + if (Debug) { + LOG(PIL_DEBUG, "Blade '%s' has id %d, caps %x" + , bi->name, bi->resourceId, bi->resourceCaps); + } + break; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + if (ohupdate != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if(Debug){ + LOG(PIL_DEBUG, "Looping through entries again," + " count changed from %d to %d" + , ohupdate, ohdi.RptUpdateCount); + } + goto try_again; + } + + dev->ohrptcnt = ohupdate; + + return S_OK; +} + + +static void +free_bladehpi_hostlist(struct pluginDevice *dev) +{ + if (dev->hostlist) { + GList *node; + while (NULL != (node = g_list_first(dev->hostlist))) { + dev->hostlist = + g_list_remove_link(dev->hostlist, node); + FREE(((struct blade_info *)node->data)->name); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; +} + + +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; +} diff --git a/lib/plugins/stonith/cyclades.c b/lib/plugins/stonith/cyclades.c new file mode 100644 index 0000000..6744cd4 --- /dev/null +++ b/lib/plugins/stonith/cyclades.c @@ -0,0 +1,650 @@ +/* + * Stonith module for Cyclades AlterPath PM + * Bases off the SSH plugin + * + * Copyright (c) 2004 Cyclades corp. + * + * Author: Jon Taylor <jon.taylor@cyclades.com> + * + * Rewritten from scratch using baytech.c structure and code + * and currently maintained by + * Marcelo Tosatti <marcelo.tosatti@cyclades.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> + +#define DEVICE "Cyclades AlterPath PM" + +#define DOESNT_USE_STONITHSCANLINE + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN cyclades +#define PIL_PLUGIN_S "cyclades" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * cyclades_new(const char *); +static void cyclades_destroy(StonithPlugin *); +static int cyclades_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * cyclades_get_confignames(StonithPlugin * s); +static const char * cyclades_get_info(StonithPlugin * s, int InfoType); +static int cyclades_status(StonithPlugin *); +static int cyclades_reset_req(StonithPlugin * s, int request, const char * host); +static char ** cyclades_hostlist(StonithPlugin *); + + + +static struct stonith_ops cycladesOps ={ + cyclades_new, /* Create new STONITH object */ + cyclades_destroy, /* Destroy STONITH object */ + cyclades_get_info, /* Return STONITH info string */ + cyclades_get_confignames, /* Return STONITH config vars */ + cyclades_set_config, /* set configuration from vars */ + cyclades_status, /* Return STONITH device status */ + cyclades_reset_req, /* Request a reset */ + cyclades_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; + +#include "stonith_expect_helpers.h" + +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 + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Cyclades STONITH device + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char * device; + char * user; + + 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)", 1, 0}, + { "Outlet\tName\t\t\tStatus\t\tInterval (s)\tUsers", 2, 0}, + { "Outlet Name Status Post-on Delay(s)", 3, 0}, + { NULL, 0, 0} +}; + +static struct Etoken CRNL[] = { {"\n\r",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 pluginDevice *, char *); + +static const char * pluginid = "CycladesDevice-Stonith"; +static const char * NOTpluginID = "Cyclades device has been destroyed"; + +#define MAX_OUTLETS 128 + +#define ST_SERIALPORT "serialport" + +#define ZEROEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(0); \ + } + +#define RESETEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) { \ + FREE(outletstr); \ + return(errno == ETIMEDOUT \ + ? S_RESETFAIL : S_OOPS); \ + } \ + } + +#include "stonith_config_xml.h" + +#define XML_SERIALPORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SERIALPORT \ + XML_PARM_SHORTDESC_END + +#define XML_SERIALPORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The serial port of the IPDU which can powercycle the node" \ + XML_PARM_LONGDESC_END + +#define XML_SERIALPORT_PARM \ + XML_PARAMETER_BEGIN(ST_SERIALPORT, "string", "1", "0") \ + XML_SERIALPORT_SHORTDESC \ + XML_SERIALPORT_LONGDESC \ + XML_PARAMETER_END + +static const char *cycladesXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_SERIALPORT_PARM + XML_PARAMETERS_END; + +static int +CYCScanLine(struct pluginDevice *sd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max, Debug) < 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + return(S_OOPS); + } + return(S_OK); +} + +static int +cyclades_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char *cmd = status_all; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return(S_OOPS); + } + + EXPECT(sd->rdfd, StatusOutput, 50); + + return(S_OK); +} + +static int CYC_run_command(struct pluginDevice *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 pluginDevice *sd, char *cmd) +{ + int rc = S_OOPS; + int i; + + for (i=0; i < 20 && rc != S_OK; i++) { + + if (sd->pid > 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + } + + if (CYC_run_command(sd, cmd) != S_OK) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + continue; + } + rc = S_OK; + } + + return rc; +} + +#define MAXSAVE 512 +static int CYCNametoOutlet(struct pluginDevice *sd, const char *host, int *outlets, int maxoutlet) +{ + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err; + int outlet, numoutlet = 0; + char name[17], locked[11], on[4]; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return 0; + } + + ZEROEXPECT(sd->rdfd, StatusOutput, 50); + + ZEROEXPECT(sd->rdfd, CRNL, 50); + + 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 %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + if (!strncasecmp(name, host, strlen(host))) { + if (numoutlet >= maxoutlet) { + LOG(PIL_CRIT, "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(StonithPlugin *s) +{ + struct pluginDevice* sd; + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err, i; + int outlet; + int numnames = 0; + char name[17], locked[11], on[4]; + char *NameList[MAX_OUTLETS]; + char **ret = NULL; + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return (NULL); + } + + memset(savebuf, 0, sizeof(savebuf)); + + NULLEXPECT(sd->rdfd, StatusOutput, 50); + + NULLEXPECT(sd->rdfd, CRNL, 50); + + 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 %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + nm = (char *) STRDUP (name); + if (!nm) { + goto out_of_memory; + } + 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) { + goto out_of_memory; + } else { + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + return (ret); + } + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + + return (NULL); +} + + +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 pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid, int req) +{ + 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) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", onoff, outletstr); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run %s command", onoff); + FREE(outletstr); + return(S_OOPS); + } + + 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 log */ + + EXPECT(sd->rdfd, exp, 50); + } + + LOG(PIL_DEBUG, "Power to host %s turned %s", unitid, onoff); + + FREE(outletstr); + return (S_OK); +} + +static int cyclades_reset(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid) +{ + 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) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr); + + LOG(PIL_INFO, "Host %s being rebooted.", unitid); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run cycle command"); + FREE(outletstr); + return(S_OOPS); + } + + 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(sd->rdfd, exp, 50); + } + + 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(sd->rdfd, exp, 50); + } + + FREE(outletstr); + return (S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +cyclades_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice *sd; + int rc = 0; + int numoutlet, outlets[MAX_OUTLETS]; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS); + + if (!numoutlet) { + LOG(PIL_CRIT, "Unknown host %s to Cyclades PM", 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; +} + +static const char * const * +cyclades_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_SERIALPORT, NULL}; + return ret; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +cyclades_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy[] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_SERIALPORT, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->serial_port = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + + return(S_OK); +} + +static const char * +cyclades_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * sd; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + sd = (struct pluginDevice*) s; + + switch (reqtype) { + case ST_DEVICEID: /* What type of device? */ + /* FIXME: could inform the exact PM model */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* What particular device? */ + ret = sd->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Cyclades AlterPath PM " + "series power switches (via TS/ACS/KVM)."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.cyclades.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = cycladesXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Cyclades Stonith destructor... + */ +static void +cyclades_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice*) s; + + sd->pluginid = NOTpluginID; + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + 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 StonithPlugin * +cyclades_new(const char *plugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->pid = -1; + sd->rdfd = -1; + sd->wrfd = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &cycladesOps; + + return &(sd->sp); /* same as sd */ +} diff --git a/lib/plugins/stonith/drac3.c b/lib/plugins/stonith/drac3.c new file mode 100644 index 0000000..95be775 --- /dev/null +++ b/lib/plugins/stonith/drac3.c @@ -0,0 +1,359 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * Tiny bits Copyright 2005 International Business Machines + * Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * (Using snippets of other stonith modules code) + * + * 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 + * + */ + +#define DEVICE "Dell DRACIII Card" +#include "stonith_plugin_common.h" + +#include <curl/curl.h> +#include "drac3_command.h" + +#define PIL_PLUGIN drac3 +#define PIL_PLUGIN_S "drac3" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> +#include "stonith_signal.h" + +static StonithPlugin * drac3_new(const char *); +static void drac3_destroy(StonithPlugin *); +static const char * const * drac3_get_confignames(StonithPlugin *); +static int drac3_set_config(StonithPlugin *, StonithNVpair *); +static const char * drac3_getinfo(StonithPlugin * s, int InfoType); +static int drac3_status(StonithPlugin * ); +static int drac3_reset_req(StonithPlugin * s, int request, const char * host); +static char ** drac3_hostlist(StonithPlugin *); + +static struct stonith_ops drac3Ops ={ + drac3_new, /* Create new STONITH object */ + drac3_destroy, /* Destroy STONITH object */ + drac3_getinfo, /* Return STONITH info string */ + drac3_get_confignames, /* Return configuration parameters */ + drac3_set_config, /* Set configuration */ + drac3_status, /* Return STONITH device status */ + drac3_reset_req, /* Request a reset */ + drac3_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 + , &drac3Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define BUFLEN 1024 +#define ST_HOST "host" + +struct pluginDevice { + StonithPlugin sp; + const char *pluginid; + const char *idinfo; + CURL *curl; + char *host; + char *user; + char *pass; +}; + +static const char *pluginid = "Dell-DRACIII-Stonith"; +static const char *NOTpluginID = "Dell DRACIII device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_HOST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOST \ + XML_PARM_SHORTDESC_END + +#define XML_HOST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOST_PARM \ + XML_PARAMETER_BEGIN(ST_HOST, "string", "1", "1") \ + XML_HOST_SHORTDESC \ + XML_HOST_LONGDESC \ + XML_PARAMETER_END + +static const char *drac3XML = + XML_PARAMETERS_BEGIN + XML_HOST_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* ------------------------------------------------------------------ */ +/* STONITH PLUGIN API */ +/* ------------------------------------------------------------------ */ +static StonithPlugin * +drac3_new(const char *subplugin) +{ + struct pluginDevice *drac3d = ST_MALLOCT(struct pluginDevice); + + if (drac3d == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(drac3d, 0, sizeof(*drac3d)); + drac3d->pluginid = pluginid; + drac3d->curl = curl_easy_init(); + drac3InitCurl(drac3d->curl); + drac3d->host = NULL; + drac3d->user = NULL; + drac3d->pass = NULL; + drac3d->idinfo = DEVICE; + drac3d->sp.s_ops = &drac3Ops; + return (&(drac3d->sp)); +} + +/* ------------------------------------------------------------------ */ +static void +drac3_destroy(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + + VOIDERRIFWRONGDEV(s); + + drac3d = (struct pluginDevice *) s; + + drac3d->pluginid = NOTpluginID; + + /* release curl connection */ + if (drac3d->curl != NULL) { + drac3Logout(drac3d->curl, drac3d->host); + curl_easy_cleanup(drac3d->curl); + drac3d->curl = NULL; + } + + if (drac3d->host != NULL) { + FREE(drac3d->host); + drac3d->host = NULL; + } + if (drac3d->user != NULL) { + FREE(drac3d->user); + drac3d->user = NULL; + } + if (drac3d->pass != NULL) { + FREE(drac3d->pass); + drac3d->pass = NULL; + } + + /* release stonith-object itself */ + FREE(drac3d); +} + +/* ------------------------------------------------------------------ */ +static const char * const * +drac3_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_HOST, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* ------------------------------------------------------------------ */ +static int +drac3_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOST, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->host = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->pass = namestocopy[2].s_value; + + return(S_OK); +} + +/* ------------------------------------------------------------------ */ +const char * +drac3_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *drac3d; + const char *ret = NULL; + + ERRIFWRONGDEV(s,NULL); + + drac3d = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = drac3d->idinfo; + break; + case ST_DEVICENAME: + ret = drac3d->host; + break; + case ST_DEVICEDESCR: + ret = "Dell DRACIII (via HTTPS)\n" + "The Dell Remote Access Controller accepts XML " + "commands over HTTPS"; + break; + case ST_DEVICEURL: + ret = "http://www.dell.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = drac3XML; + break; + default: + ret = NULL; + break; + } + + return(ret); +} + +/* ------------------------------------------------------------------ */ +int +drac3_status(StonithPlugin *s) +{ + struct pluginDevice *drac3d; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + if (drac3GetSysInfo(drac3d->curl, drac3d->host)) { + return(S_ACCESS); + }else{ + return(S_OK); + } +} + +/* ------------------------------------------------------------------ */ +int +drac3_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *drac3d; + int rc = S_OK; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (strcasecmp(host, drac3d->host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , drac3d->idinfo, host); + return(S_BADHOST); + } + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + switch(request) { +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + /* TODO... */ +#endif + case ST_GENERIC_RESET: + if (drac3PowerCycle(drac3d->curl, drac3d->host)) + rc = S_ACCESS; + break; + default: + rc = S_INVAL; + break; + } + + return(rc); +} + +/* ------------------------------------------------------------------ */ +char ** +drac3_hostlist(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + char **hl; + + ERRIFNOTCONFIGED(s,NULL); + + drac3d = (struct pluginDevice *) s; + + hl = OurImports->StringToHostList(drac3d->host); + if (hl == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(hl[0]); + } + + return(hl); +} diff --git a/lib/plugins/stonith/drac3_command.c b/lib/plugins/stonith/drac3_command.c new file mode 100644 index 0000000..4d9002d --- /dev/null +++ b/lib/plugins/stonith/drac3_command.c @@ -0,0 +1,342 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include <curl/curl.h> + +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> + +#include "drac3_command.h" +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +#define DEBUG 0 + +/* Hardcoded XML commands and response codes */ +#define CMD_POWERCYCLE "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"serveraction\"><ACT>powercycle</ACT></REQ></RMCSEQ>\n" +#define CMD_GETSYSINFO "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"xml2cli\"><CMDINPUT>getsysinfo -A</CMDINPUT></REQ></RMCSEQ>\n" +#define RC_OK "0x0\n" + +struct Chunk { + char *memory; + size_t size; +}; + +/* prototypes */ +int xmlGetXPathString (const char *str, const char * expr, char * rc, const int len); +size_t writeFunction (void *ptr, size_t size, size_t nmemb, void *data); + + +/* ---------------------------------------------------------------------- * + * XML PARSING * + * ---------------------------------------------------------------------- */ + +int +xmlGetXPathString (const char *str, + const char * expr, + char * rc, + const int len) +{ + xmlDocPtr doc; + xmlNodePtr cur; + xmlXPathContextPtr ctx; + xmlXPathObjectPtr path; + xmlChar *xmlRC; + + if (!strchr(str,'<')) { + fprintf(stderr,"%s malformed\n", str); + rc[0] = 0x00; + return(1); + } + + doc = xmlParseMemory(str, strlen(str)); + xmlXPathInit(); + ctx = xmlXPathNewContext(doc); + path = xmlXPathEvalExpression((const xmlChar *)expr, ctx); + cur = path->nodesetval->nodeTab[0]; + + if (cur != NULL) { + xmlRC = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + snprintf(rc, len, "%s\n", xmlRC); + xmlFree(xmlRC); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + return(0); + } else { + fprintf(stderr,"error in obtaining XPath %s\n", expr); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + rc[0] = 0x00; + return(1); + } +} + + +/* ---------------------------------------------------------------------- * + * CURL CALLBACKS * + * ---------------------------------------------------------------------- */ + +size_t +writeFunction (void *ptr, size_t size, size_t nmemb, void *data) +{ + + register int realsize = size * nmemb; + struct Chunk *mem = (struct Chunk *)data; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory) { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + return realsize; +} + + +/* ---------------------------------------------------------------------- * + * DRAC3 CURL COMMANDS * + * ---------------------------------------------------------------------- */ + +int +drac3InitCurl (CURL *curl) +{ +#ifdef CURLOPT_NOSIGNAL + if (curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1)) return(1); +#endif + if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30)) return(1); + if (curl_easy_setopt(curl, CURLOPT_VERBOSE, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction)) return(1); + if (curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/dev/null")) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0)) return(1); + return(0); +} + +int +drac3Login (CURL *curl, + const char *host, + const char *user, + const char *pass) +{ + char url[BUFLEN]; + char chall[BUFLEN]; + char token[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) + return(1); + + /* ask for challenge */ + snprintf(url, BUFLEN, "https://%s/cgi/challenge", host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + /* extract challenge */ + status = xmlGetXPathString(chunk.memory, "//CHALLENGE", chall, BUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + /* calculate authToken */ + drac3AuthHash(chall, pass, token, BUFLEN); + + if (DEBUG) printf("T: %s\n", token); + + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + if (status) return(1); + chunk.memory = NULL; + chunk.size = 0; + + /* sends authToken */ + snprintf(url, BUFLEN, "https://%s/cgi/login?user=%s&hash=%s", + host, + user, + token); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3PowerCycle (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_POWERCYCLE; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3GetSysInfo (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_GETSYSINFO; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3Logout (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/logout", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3VerifyLogin (CURL *curl, + const char *host) +{ + /*We try to do a GetSysInfo */ + return(drac3GetSysInfo (curl, host)); +} + +/* -------------------------------------------------------------------- */ + diff --git a/lib/plugins/stonith/drac3_command.h b/lib/plugins/stonith/drac3_command.h new file mode 100644 index 0000000..cd03e15 --- /dev/null +++ b/lib/plugins/stonith/drac3_command.h @@ -0,0 +1,29 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 + * + */ + +int drac3InitCurl (CURL *curl); +int drac3Login (CURL *curl, const char *host, const char *user, const char *pass); +int drac3PowerCycle (CURL *curl, const char *host); +int drac3GetSysInfo (CURL *curl, const char *host); +int drac3Logout (CURL *curl, const char *host); +int drac3VerifyLogin (CURL *curl, const char *host); + diff --git a/lib/plugins/stonith/drac3_hash.c b/lib/plugins/stonith/drac3_hash.c new file mode 100644 index 0000000..605a126 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.c @@ -0,0 +1,106 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 <string.h> +#include <stdio.h> +#include <clplumbing/base64.h> +#include <clplumbing/md5.h> +#include <glib.h> + +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +/* Hash functions for DRAC3 authentication */ + +guint16 +drac3Crc16(const char *str, + const int l) { + + int i,j; + guint16 crc = 0; + + for (i=0; i<l; i++) { + crc = crc ^ (str[i] << 8); + for (j=0; j<8; j++) + crc = ( (crc & 0x8000) == 32768 ? (crc<<1) ^ 0x1021 : crc<<1); + } + crc = crc & 0xFFFF; + return crc; +} + +void +drac3AuthHash(const char * chall, + const char * pass, + char * token, + int len) { + + char * chall_dup; + char challBytes[MD5LEN]; + char passMD5[MD5LEN]; + char xorBytes[MD5LEN]; + char xorBytesMD5[MD5LEN]; + guint16 crc; + char response[MD5LEN+2]; + char responseb64[SBUFLEN]; + int i; + + /* decodes chall -> challBytes */ + memset(challBytes, 0, MD5LEN); + chall_dup = g_strdup(chall); + if (chall_dup[strlen(chall_dup) - 1] == '\n' ) { + chall_dup[strlen(chall_dup) - 1] = '\0'; + } + base64_to_binary(chall_dup, strlen(chall_dup), challBytes, MD5LEN); + + /* gets MD5 from pass -> passMD5 */ + MD5((const unsigned char *)pass, strlen(pass), (unsigned char *)passMD5); + + /* calculate challBytes and passMD5 xor -> xorBytes */ + for (i=0; i<MD5LEN; i++) { + xorBytes[i] = challBytes[i] ^ passMD5[i]; + } + + /* calculate xorBytes MD5 -> xorBytesMD5 */ + MD5((unsigned char *)xorBytes, MD5LEN, (unsigned char *)xorBytesMD5); + + /* calculate xorBytesMD5 crc16 */ + crc = drac3Crc16(xorBytesMD5, MD5LEN); + + /* joins xorBytesMD5 and crc16 -> response */ + memcpy(response, xorBytesMD5, MD5LEN); + memcpy(response+MD5LEN, &crc, 2); + + /* calculate response base64 -> responseb64 */ + memset(responseb64, 0, SBUFLEN); + binary_to_base64(response, MD5LEN+2, responseb64, SBUFLEN); + + /* assuring null-termination */ + responseb64[SBUFLEN-1]=0x00; + + snprintf(token, len, "%s", responseb64); + token[len-1]=0x00; +} diff --git a/lib/plugins/stonith/drac3_hash.h b/lib/plugins/stonith/drac3_hash.h new file mode 100644 index 0000000..fab2f58 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.h @@ -0,0 +1,28 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.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 <sys/types.h> +#include <glib.h> + +guint16 drac3Crc16(const char *str, const int l); +void drac3AuthHash(const char *chall, const char *pass, char *token, int len); + diff --git a/lib/plugins/stonith/external.c b/lib/plugins/stonith/external.c new file mode 100644 index 0000000..da03665 --- /dev/null +++ b/lib/plugins/stonith/external.c @@ -0,0 +1,868 @@ +/* + * 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 * const * 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 (rc != 0) { + LOG(PIL_WARN, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + } + else { + 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 (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + 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); + 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; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + 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++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + + 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_DEBUG, "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 * const * +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 (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_DEBUG, "plugin output: %s", output); + } + } + + 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 * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +external_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + char * output = 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, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + } + sd->outputbuf = output; + return(output); + } + 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); +} + +#define LOGTAG_VAR "HA_LOGTAG" + +/* 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 status, rc; + char * data = NULL; + FILE * file; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + char *path, *new_path, *logtag, *savevar = NULL; + int new_path_len, logtag_len; + gboolean nodata; + + 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); + } + + /* external plugins need path to ha_log.sh */ + path = getenv("PATH"); + if (strncmp(GLUE_SHARED_DIR,path,strlen(GLUE_SHARED_DIR))) { + new_path_len = strlen(path)+strlen(GLUE_SHARED_DIR)+2; + new_path = (char *)g_malloc(new_path_len); + snprintf(new_path, new_path_len, "%s:%s", GLUE_SHARED_DIR, path); + setenv("PATH", new_path, 1); + g_free(new_path); + } + + /* set the logtag appropriately */ + logtag_len = strlen(PIL_PLUGIN_S)+strlen(sd->subplugin)+2; + logtag = (char *)g_malloc(logtag_len); + snprintf(logtag, logtag_len, "%s/%s", PIL_PLUGIN_S, sd->subplugin); + if (getenv(LOGTAG_VAR)) { + savevar = g_strdup(getenv(LOGTAG_VAR)); + } + setenv(LOGTAG_VAR, logtag, 1); + g_free(logtag); + + 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); + rc = -1; + goto out; + } + + if (output) { + slen=0; + data = MALLOC(1); + data[slen] = EOS; + } + while (!feof(file)) { + nodata = TRUE; + if (output) { + 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; + nodata = FALSE; + } + } else { + if (fgets(buff, BUFF_LEN, file)) { + LOG(PIL_INFO, "%s: '%s' output: %s", __FUNCTION__, cmd, buff); + nodata = FALSE; + } + } + if (nodata) { + sleep(1); + } + } + if (output && !data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + rc = -1; + goto out; + } + + status = pclose(file); + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + if (rc != 0 && Debug) { + LOG(PIL_DEBUG, + "%s: Calling '%s' returned %d", __FUNCTION__, cmd, rc); + } + } else { + if (WIFSIGNALED(status)) { + LOG(PIL_CRIT, "%s: '%s' got signal %d", + __FUNCTION__, cmd, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + LOG(PIL_INFO, "%s: '%s' stopped with signal %d", + __FUNCTION__, cmd, WSTOPSIG(status)); + } else { + LOG(PIL_CRIT, "%s: '%s' exited abnormally (core dumped?)", + __FUNCTION__, cmd); + } + rc = -1; + } + if (Debug && output && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + +out: + if (savevar) { + setenv(LOGTAG_VAR, savevar, 1); + g_free(savevar); + } else { + unsetenv(LOGTAG_VAR); + } + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL); + } + if (!rc) { + if (output) { + *output = data; + } + } else { + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + } + return rc; +} diff --git a/lib/plugins/stonith/external/Makefile.am b/lib/plugins/stonith/external/Makefile.am new file mode 100644 index 0000000..42e0046 --- /dev/null +++ b/lib/plugins/stonith/external/Makefile.am @@ -0,0 +1,33 @@ +# Makefile.am for OCF RAs +# +# Author: Sun Jing Dong +# Copyright (C) 2004 IBM +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = drac5 dracmc-telnet ibmrsa-telnet ipmi rackpdu vmware vcenter xen0 \ + xen0-ha-dom0-stonith-helper kdumpcheck nut + +extdir = $(stonith_ext_plugindir) + +helperdir = $(stonith_plugindir) + +ext_SCRIPTS = drac5 dracmc-telnet ibmrsa ibmrsa-telnet ipmi riloe ssh vmware vcenter rackpdu xen0 hmchttp \ + xen0-ha kdumpcheck ippower9258 nut libvirt \ + hetzner + +helper_SCRIPTS = xen0-ha-dom0-stonith-helper diff --git a/lib/plugins/stonith/external/drac5.in b/lib/plugins/stonith/external/drac5.in new file mode 100644 index 0000000..218cbd3 --- /dev/null +++ b/lib/plugins/stonith/external/drac5.in @@ -0,0 +1,113 @@ +#!/bin/sh +# +# External STONITH module for DRAC5 adapters. +# +# Author: Jun Wang +# License: GNU General Public License (GPL) +# + +trap 'if [ -n "$outf" ]; then ha_log.sh err "`cat $outf`"; rm -f "$outf"; fi' 0 +outf=`mktemp` || { + ha_log.sh err "mktemp failed" + exit 1 +} + +sshlogin() { + if [ x = "x$ipaddr" -o x = "x$userid" ] + then + ha_log.sh err "ipaddr or userid missing; check configuration" + return 1 + fi + @SSH@ -q -x -n $userid@$ipaddr racadm serveraction "$1" >$outf 2>&1 +} + +drac_reset() { + sshlogin hardreset +} + +drac_on() { + sshlogin poweron +} + +drac_off() { + sshlogin poweroff +} + +drac_status() { + sshlogin powerstatus +} + +case $1 in +gethosts) + echo $hostname + ;; +on) + drac_poweron + ;; +off) + drac_poweroff + ;; +reset) + drac_reset + ;; +status) + drac_status + ;; +getconfignames) + for i in hostname ipaddr userid; do + echo $i + done + ;; +getinfo-devid) + echo "DRAC5 STONITH device" + ;; +getinfo-devname) + echo "DRAC5 STONITH device" + ;; +getinfo-devdescr) + echo "DRAC5 host reset/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.dell.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/dracmc-telnet b/lib/plugins/stonith/external/dracmc-telnet new file mode 100644 index 0000000..d993961 --- /dev/null +++ b/lib/plugins/stonith/external/dracmc-telnet @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# vim: set filetype=python +####################################################################### +# +# dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to Dell Drac/MC Blade Enclosure via a Cyclades +# terminal server with telnet and switches power of named +# blade servers appropriatelly. +# +# Required parameters: +# nodename: The name of the server you want to touch on your network +# cyclades_ip: The IP address of the cyclades terminal server +# cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) +# servername: The DRAC/MC server name of the blade (i.e. Server-7) +# username: The login user name for the DRAC/MC +# password: The login password for the DRAC/MC +# +# Author: Alex Tsariounov <alext@novell.com> +# +# Based on ibmrsa-telnet external stonith plugin by Andreas Mock +# (andreas.mock@web.de), Copyright by Adreas Mock and released as part +# of HAv2. +# +# History: +# 2009-10-12 First release. +# +# Copyright (c) 2009 Novell, Inc. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import random +import subprocess + +LOGINRETRIES = 10 + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class DracMC(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self) + self._timeout = 4 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + self._server = args[0] + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer): + self._history.append(self._get_timestamp() + ': WRITE: ' + repr(buffer)) + telnetlib.Telnet.write(self, buffer) + + def read_until(self, what, timeout=2): + line = telnetlib.Telnet.read_until(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line.endswith(what): + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(0.3) + try: + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + except: + self.write("\r") + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + try: + line = self.read_until('DRAC/MC:', self._timeout) + except: + self.write("\r") + line = self.read_until('DRAC/MC:', self._timeout) + + def hardreset(self): + self.write('serveraction -s %s hardreset\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def powercycle(self): + self.write('serveraction -s %s powercycle\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def on(self): + self.write('serveraction -s %s powerup\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def off(self): + self.write('serveraction -s %s powerdown\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class DracMCStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'cyclades_ip', 'cyclades_port', + 'servername', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + subprocess.call("ha_log.sh debug '%s'" % ' '.join(args), shell=True) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call("ha_log.sh %s '%s'" % (level,' '.join(args)), shell=True) + + def _get_connection(self): + if not self._connection: + c = DracMC(self._parameters['servername']) + self._echo_debug("Connecting to '%s:%s'" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + tries = 0 + while tries < LOGINRETRIES: + try: + c.open(self._parameters['cyclades_ip'], + self._parameters['cyclades_port']) + c.login(self._parameters['username'], + self._parameters['password']) + except Exception, args: + if "Connection reset by peer" in str(args): + self._echo_debug("Someone is already logged in... retry=%s" % tries) + c.close() + time.sleep(random.uniform(1.0, 5.0)) + else: + raise + else: + break + tries += 1 + + if tries == LOGINRETRIES: + c.close() + raise Exception("Could not log in to %s:%s" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + # self._connection.hardreset() + self._connection.powercycle() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'cyclades_ip', 'cyclades_port', 'servername', + 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for Dell DRAC/MC via Cyclades") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for Dell Drac/MC connecting " + "via Telnet to a Cyclades port") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a Dell DRAC/MC connected via a Cyclades port with telnet. " + "Commands to turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2009 by Novell, Inc. (alext@novell.com)") + return(0) + + def getinfo_devurl(self): + self.echo("http://support.dell.com/support/edocs/software/smdrac3/dracmc/1.3/en/index.htm") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node to be stonithed. + </longdesc> + </parameter> + <parameter name="cyclades_ip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of cyclades</shortdesc> + <longdesc lang="en"> + Hostname or IP address of Cyclades connected to DRAC/MC. + </longdesc> + </parameter> + <parameter name="cyclades_port" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">telnet port to use on cyclades</shortdesc> + <longdesc lang="en"> + Port used with the Cyclades telnet interface which is connected to the DRAC/MC. + </longdesc> + </parameter> + <parameter name="servername" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">DRAC/MC name of blade to be stonithed</shortdesc> + <longdesc lang="en"> + Name of server blade to be stonithed on the DRAC/MC (example: Server-7) + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Username to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Password to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = DracMCStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/hetzner b/lib/plugins/stonith/external/hetzner new file mode 100755 index 0000000..2b3e675 --- /dev/null +++ b/lib/plugins/stonith/external/hetzner @@ -0,0 +1,139 @@ +#!/bin/sh +# +# External STONITH module for Hetzner. +# +# Copyright (c) 2011 MMUL S.a.S. - Raoul Scarazzini <rasca@mmul.it> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# Read parameters from config file, format is based upon the hetzner OCF resource agent +# developed by Kumina: http://blog.kumina.nl/2011/02/hetzner-failover-ip-ocf-script/ +conf_file="/etc/hetzner.cfg" + +case $1 in + get*) ;; # don't print errors if conf_file not present + *) + user=`sed -n 's/^user.*=\ *//p' $conf_file` + pass=`sed -n 's/^pass.*=\ *//p' $conf_file` + ;; +esac + +hetzner_server="https://robot-ws.your-server.de" + +check_http_response() { + # If the response is 200 then return 0 + if [ $1 = 200 ] + then + return 0 + else + # If the response is not 200 then display a description of the problem and return 1 + case $1 in + 400) ha_log.sh err "INVALID_INPUT - Invalid input parameters" + ;; + 404) ha_log.sh err "SERVER_NOT_FOUND - Server with ip $remote_ip not found" + ;; + 409) ha_log.sh err "RESET_MANUAL_ACTIVE - There is already a running manual reset" + ;; + 500) ha_log.sh err "RESET_FAILED - Resetting failed due to an internal error" + ;; + esac + return 1 + fi +} + +case $1 in +gethosts) + echo $hostname + exit 0 + ;; +on) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power on is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +off) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power off is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +reset) + # Launching the reset action via webservice + check_http_response $(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/reset/$remote_ip -d type=hw) + exit $? + ;; +status) + # Check if we can contact the webservice + check_http_response "$(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/server/$remote_ip)" + exit $? + ;; +getconfignames) + echo "hostname" + echo "remote_ip" + exit 0 + ;; +getinfo-devid) + echo "Hetzner STONITH device" + exit 0 + ;; +getinfo-devname) + echo "Hetzner STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "Hetzner host reset" + echo "Manages the remote webservice for reset a remote server." + exit 0 + ;; +getinfo-devurl) + echo "http://wiki.hetzner.de/index.php/Robot_Webservice_en" + exit 0 + ;; +getinfo-xml) + cat << HETZNERXML +<parameters> +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="remote_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Remote IP +</shortdesc> +<longdesc lang="en"> +The address of the remote IP that manages this server. +</longdesc> +</parameter> +</parameters> +HETZNERXML + exit 0 + ;; +*) + ha_log.sh err "Don't know what to do for '$remote_ip'" + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/hmchttp b/lib/plugins/stonith/external/hmchttp new file mode 100644 index 0000000..9d111bc --- /dev/null +++ b/lib/plugins/stonith/external/hmchttp @@ -0,0 +1,218 @@ +#!/bin/sh +# External STONITH module for HMC web console +# +# Copyright (c) 2007 Xinwei Hu <hxinwei@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +#set -x +hostlist=`echo $hostlist | tr ',' ' '` + +trap '[ ! -e "$COOKIEFILE" ] || rm -f "$COOKIEFILE"' 0 +COOKIEFILE=`mktemp` || exit 1 + +: ${CURLBIN="/usr/bin/curl"} +: ${user=admin} +: ${password=admin} + +check_parameter() { + if [ ! -x $CURLBIN ] + then + ha_log.sh err "Curl can't be found in normal place. Set CURLBIN to override the default value" + exit 1 + fi + + if [ -z $hmc_ipaddr ] + then + ha_log.sh err "The address of HMC web console is not specified" + exit 1 + fi +} + +HMCUSERNAME=$user +HMCPASSWORD=$password + +HMC_LOGIN_COMMAND="$CURLBIN -3 -k -c $COOKIEFILE -d user=$HMCUSERNAME -d password=$HMCPASSWORD -d lang=0 -d submit=Log+in " +HMC_LOGOUT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d submit=Log+out " +HMC_TOC_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=2 " +HMC_POWERON_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 -d sp=255 -d is=0 -d om=4 -d id=1 -d ip=2 -d plt=1 -d pm=0 -d on=Save+settings+and+power+on " +HMC_POWERSTATE_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 " +HMC_POWEROFF_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=30 -d submit=Continue " +HMC_REBOOT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=74 -d submit=Continue " + +hmc_login() { + iamin=0 + while [ $iamin -eq 0 ]; do + $HMC_LOGIN_COMMAND https://$hmc_ipaddr/cgi-bin/cgi >/dev/null 2>&1 + $HMC_TOC_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Too many users" + iamin=$? + sleep 2 + done +} +hmc_logout() { + $HMC_LOGOUT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} + +hmc_reboot() { + check_parameter + $HMC_REBOOT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_poweron() { + r=1 + while [ 0 -ne $r ]; do + $HMC_POWERON_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Operation completed successfully" + r=$? + done +} +hmc_poweroff() { + check_parameter + $HMC_POWEROFF_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_powerstate() { + check_parameter + r=`$HMC_POWERSTATE_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null| grep "Current system power state:" | sed 's/<br>//g' | awk '{print $5}'` + echo $r +} + +hmc_poweroffon() { + check_parameter + hmc_poweroff + while [ 1 ]; do + r=`hmc_powerstate` + ha_log.sh debug "power state: $r" + if [ $r = "Off" ]; then + break + fi + sleep 5 + done + sleep 3 + hmc_poweron +} + +case $1 in +gethosts) + for h in $hostlist; do + echo $h + done + exit 0 + ;; +status) + if + ping -w1 -c1 "$hmc_ipaddr" 2>&1 + then + exit 0 + fi + exit 1 + ;; +getconfignames) + for f in hostlist hmc_ipaddr user password; do + echo $f + done + exit 0 + ;; +getinfo-devid) + echo "HMC web console STONITH device" + exit 0 + ;; +getinfo-devname) + echo "HMC web console STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "HMC web console based host power control" + echo "Use for i5, p5, pSeries and OpenPower systems that are managed via " + echo "web console through a direct connection to system's HMC port." + exit 0 + ;; +getinfo-devurl) + echo "http://www.ibm.com" + exit 0 + ;; +getinfo-xml) + cat << HMCXML +<parameters> + +<parameter name="hostlist" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Hostlist</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="hmc_ipaddr" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">HMC IPAddr</shortdesc> +<longdesc lang="en"> +The IP address of the HMC web console +</longdesc> +</parameter> + +<parameter name="user" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">User</shortdesc> +<longdesc lang="en"> +User name to log into HMC web console +</longdesc> +</parameter> + +<parameter name="password" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password of user name to log into HMC web console +</longdesc> +</parameter> + +</parameters> +HMCXML + exit 0 + ;; +esac + +case $1 in +on|off|reset|powerstate|poweroffon) + hmc_login + case $1 in + on) + hmc_poweron $hmc_ipaddr + ;; + off) + hmc_poweroff $hmc_ipaddr + ;; + reset) +# hmc_reboot $hmc_ipaddr + hmc_poweroffon $hmc_ipaddr + ;; + powerstate) + hmc_powerstate + ;; + poweroffon) + hmc_poweroffon + ;; + esac + hmc_logout + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa b/lib/plugins/stonith/external/ibmrsa new file mode 100644 index 0000000..7408465 --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2006 Dejan Muhamedagic <dmuhamedagic@at.ibm.com>, IBM Austria +# +# External STONITH module for IBM RSA adapters. +# External STONITH module for IBM BMC. +# This STONITH module depends on IBMmpcli. +# + +trap 'rm -f "$outf"' 0 +outf=`mktemp` || { + ha_log.sh err 'mktemp failed' + exit 1 +} + +chkmpcli() { + test -x /opt/IBMmpcli/bin/MPCLI.sh +} +mpcli() { + chkmpcli || { + ha_log.sh err "IBM mpcli not installed" + return 1 + } + if [ x = "x$ipaddr" -o x = "x$userid" -o x = "x$passwd" ] + then + ha_log.sh err "ipaddr, userid, or passwd missing; check configuration" + return 1 + fi + type=${type:-"ibm"} + + goodstg="SUCCESS" + failstg="FAILURE" + ( + echo "logonip -h $ipaddr -u $userid -p $passwd -t $type" + echo "outputfile $outf" + cat + ) | /opt/IBMmpcli/bin/MPCLI.sh | grep -w $goodstg >/dev/null 2>&1 + rc=$? + grep -w $failstg $outf >/dev/null + if [ $rc -eq 0 -a $? -eq 1 ]; then + return 0 + else + ha_log.sh err "MPCLI.sh failed: `cat $outf`" + return 1 + fi +} +ibmrsa_reboot() { + echo restart -now | mpcli +} +ibmrsa_poweron() { + echo poweron | mpcli +} +ibmrsa_poweroff() { + echo poweroff | mpcli +} +ibmrsa_status() { + echo | mpcli +} + +hostname=`echo ${hostname} | tr ',' ' '` + +case $1 in +gethosts) + echo $hostname + ;; +on) + ibmrsa_poweron + ;; +off) + ibmrsa_poweroff + ;; +reset) + ibmrsa_reboot + ;; +status) + ibmrsa_status + ;; +getconfignames) + for i in hostname ipaddr userid passwd type; do + echo $i + done + ;; +getinfo-devid) + echo "IBM MP STONITH device" + ;; +getinfo-devname) + echo "IBM MP STONITH device" + ;; +getinfo-devdescr) + echo "IBM MP host reboot/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.ibm.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="passwd" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="type" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Management processor type +</shortdesc> +<longdesc lang="en"> +The type of the management processor. Possible values are +"ibm" (default, typically used for RSA) and "ipmi" +(for IPMI compliant processors such as BMC). +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa-telnet b/lib/plugins/stonith/external/ibmrsa-telnet new file mode 100644 index 0000000..4d75d9a --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa-telnet @@ -0,0 +1,320 @@ +#!/usr/bin/python +# vim: set filetype=python +####################################################################### +# +# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to IBM RSA Board via telnet and switches power +# of server appropriately. +# +# Author: Andreas Mock (andreas.mock@web.de) +# +# History: +# 2007-10-19 Fixed bad commandline handling in case of stonithing +# 2007-10-11 First release. +# +# Comment: Please send bug fixes and enhancements. +# I hope the functionality of communicating via telnet is encapsulated +# enough so that someone can use it for similar purposes. +# +# Description: IBM offers Remote Supervisor Adapters II for several +# servers. These RSA boards can be accessed in different ways. +# One of that is via telnet. Once logged in you can use 'help' to +# show all available commands. With 'power' you can reset, power on and +# off the controlled server. This command is used in combination +# with python's standard library 'telnetlib' to do it automatically. +# +# cib-snippet: Please see README.ibmrsa-telnet for examples. +# +# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de) +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import subprocess + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class RSABoard(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self, *args, **kwargs) + self._timeout = 10 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer, nolog = False): + self._history.append(self._get_timestamp() + ': WRITE: ' + + (nolog and '******' or repr(buffer))) + telnetlib.Telnet.write(self, buffer) + + def expect(self, what, timeout=20): + line = telnetlib.Telnet.expect(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line: + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(1) + line = self.expect(['\nlogin : ', '\nusername: '], self._timeout) + self.write(user) + self.write('\r') + line = self.expect(['\nPassword: ', '\npassword: '], self._timeout) + self.write(passwd, nolog = True) + self.write('\r') + line = self.expect(['\nsystem>', '> '], self._timeout) + + def reset(self): + self.write('power cycle\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def on(self): + self.write('power on\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def off(self): + self.write('power off\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class RSAStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'ip_address', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + self.echo_log('debug', *args) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call(('ha_log.sh', level) + args) + + def _get_connection(self): + if not self._connection: + c = RSABoard() + self._echo_debug("Connect to '%s'" % + (self._parameters['ip_address'],)) + c.open(self._parameters['ip_address']) + self._echo_debug("Connection established") + c.login(self._parameters['username'], + self._parameters['password']) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + self._connection.reset() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'ip_address', 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for IBM RSA Boards") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for IBM RSA Boards connecting " + "via Telnet") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a RSA board on IBM servers via telnet. Commands to " + "turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2007 by Andreas Mock (andreas.mock@web.de)") + return(0) + + def getinfo_devurl(self): + self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node which has to be stonithed in case. + </longdesc> + </parameter> + <parameter name="ip_address" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of RSA</shortdesc> + <longdesc lang="en"> + Hostname or ip address of RSA board used to reset node. + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on RSA board</shortdesc> + <longdesc lang="en"> + Username to login on RSA board. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on RSA board</shortdesc> + <longdesc lang="en"> + Password to login on RSA board. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = RSAStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/ipmi b/lib/plugins/stonith/external/ipmi new file mode 100644 index 0000000..abadd5a --- /dev/null +++ b/lib/plugins/stonith/external/ipmi @@ -0,0 +1,276 @@ +#!/bin/sh +# +# External STONITH module using IPMI. +# This modules uses uses the ipmitool program available from +# http://ipmitool.sf.net/ for actual communication with the +# managed device. +# +# Copyright (c) 2007 Martin Bene <martin.bene@icomedias.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# Initialization -- fix locale settings so we can parse output from +# binaries if we need it +LANG=C +LC_ALL=C + +RESET="power reset" +POWEROFF="power off" +POWERON="power on" +STATUS="power status" +IPMITOOL=${ipmitool:-"`which ipmitool 2>/dev/null`"} + +have_ipmi() { + test -x "${IPMITOOL}" +} + +# Wrapper function for ipmitool that sets the correct host IP address, +# username, and password, and invokes ipmitool with any arguments +# passed in +run_ipmitool() { + local ipmitool_opts privlvl="" + have_ipmi || { + ha_log.sh err "ipmitool not installed" + return 1 + } + if [ -z "${ipaddr}" -o -z "${userid}" -o -z "${passwd}" ]; then + ha_log.sh err "ipaddr, userid or passwd missing; check configuration" + return 1 + fi + + if [ -z "${interface}" ]; then + # default to "lan" interface + interface="lan" + fi + if [ -n "${priv}" ]; then + # default to "lan" interface + privlvl="-L $priv" + fi + + ipmitool_opts="-I ${interface} -H ${ipaddr} $privlvl" + + case "${passwd_method}" in + param|'') + passwd_method=param + M="-P" + ;; + env) + M="-E" + ;; + file) + M="-f" + ;; + *) + ha_log.sh err "invalid passwd_method: \"${passwd_method}\"" + return 1 + esac + + action="$*" + + if [ $passwd_method = env ] + then + IPMI_PASSWORD="${passwd}" ${IPMITOOL} $ipmitool_opts -U "${userid}" -E ${action} + else + ${IPMITOOL} $ipmitool_opts -U "${userid}" $M "${passwd}" ${action} + fi 2>&1 +} + +# Yet another convenience wrapper that invokes run_ipmitool, captures +# its output, logs the output, returns either 0 (on success) or 1 (on +# any error) +do_ipmi() { + if outp=`run_ipmitool $*`; then + ha_log.sh debug "ipmitool output: `echo $outp`" + return 0 + else + ha_log.sh err "error executing ipmitool: `echo $outp`" + return 1 + fi +} + +# Check if the managed node is powered on. To do so, issue the "power +# status" command. Should return either "Chassis Power is on" or +# "Chassis Power is off". +ipmi_is_power_on() { + local outp + outp=`run_ipmitool ${STATUS}` + case "${outp}" in + *on) + return 0 + ;; + *off) + return 1 + ;; + esac +} + + +case ${1} in +gethosts) + echo $hostname + exit 0 + ;; +on) + do_ipmi "${POWERON}" + exit + ;; +off) + do_ipmi "${POWEROFF}" + exit + ;; +reset) + if ipmi_is_power_on; then + do_ipmi "${RESET}" + else + do_ipmi "${POWERON}" + fi + exit + ;; +status) + # "status" reflects the status of the stonith _device_, not + # the managed node. Hence, only check if we can contact the + # IPMI device with "power status" command, don't pay attention + # to whether the node is in fact powered on or off. + do_ipmi "${STATUS}" + exit $? + ;; +getconfignames) + for i in hostname ipaddr userid passwd interface; do + echo $i + done + exit 0 + ;; +getinfo-devid) + echo "IPMI STONITH device" + exit 0 + ;; +getinfo-devname) + echo "IPMI STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ipmitool based power management. Apparently, the power off" + echo "method of ipmitool is intercepted by ACPI which then makes" + echo "a regular shutdown. If case of a split brain on a two-node" + echo "it may happen that no node survives. For two-node clusters" + echo "use only the reset method." + exit 0 + ;; +getinfo-devurl) + echo "http://ipmitool.sf.net/" + exit 0 + ;; +getinfo-xml) + cat << IPMIXML +<parameters> +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device. +</longdesc> +</parameter> + +<parameter name="userid" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd_method" unique="0"> +<content type="string" default="param"/> +<shortdesc lang="en"> +Method for passing passwd parameter +</shortdesc> +<longdesc lang="en"> +Method for passing the passwd parameter to ipmitool + param: pass as parameter (-P) + env: pass via environment (-E) + file: value of "passwd" is actually a file name, pass with (-f) +</longdesc> +</parameter> + +<parameter name="interface" unique="0"> +<content type="string" default="lan"/> +<shortdesc lang="en"> +IPMI interface +</shortdesc> +<longdesc lang="en"> +IPMI interface to use, such as "lan" or "lanplus". +</longdesc> +</parameter> + +<parameter name="priv" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +The privilege level of the user. +</shortdesc> +<longdesc lang="en"> +The privilege level of the user, for instance OPERATOR. If +unspecified the privilege level is ADMINISTRATOR. See +ipmitool(1) -L option for more information. +</longdesc> +</parameter> + +<parameter name="ipmitool" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +IPMI command(ipmitool) +</shortdesc> +<longdesc lang="en"> +Specify the full path to IPMI command. +</longdesc> +</parameter> + +</parameters> +IPMIXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ippower9258.in b/lib/plugins/stonith/external/ippower9258.in new file mode 100755 index 0000000..6ae7e02 --- /dev/null +++ b/lib/plugins/stonith/external/ippower9258.in @@ -0,0 +1,316 @@ +#!/bin/sh +# +# External STONITH module using IP Power 9258 or compatible devices. +# +# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# +# Basic commands & parameters independent from individual device + +DEVICE="IP Power 9258" +IPPowerOn="1" +IPPowerOff="0" +IPGetPower="Set.cmd?CMD=GetPower" +IPSetPower="Set.cmd?CMD=SetPower" +IPPort_name="P" +IPPort0=60 +HTTP_COMMAND="wget -q -O - --" +LOG_ERROR="ha_log.sh err" +LOG_WARNING="ha_log.sh warn" +LOG_INFO="ha_log.sh info" +LOG_DEBUG="ha_log.sh debug" +MY_COOKIES="cookies.txt" +MY_TEMPFILE="temp.htm" +PORT_STATUS="iocontrol.htm" +UNDEFINED_HOSTNAME="*not-defined*" + +# +# check MY_ROOT_PATH for IP Power 9258 and create it if necessary +MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258" + +# +# script functions +# + +get_challenge() { + # + # device sends a challenge for md5 encryption of username, password and challenge + send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6 +} + +get_cookie_from_device(){ + # the form on the login page has these fields: + # Username, Password, Challenge, Response, ScreenWidth + # + challenge=`get_challenge` + response=`echo -n "$username$password$challenge" | md5sum | cut -b -32` + postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024" + send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi" + if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE + then + $LOG_ERROR "Login to device $deviceip failed." + $LOG_ERROR "Received Challenge = <<<$challenge>>>." + $LOG_ERROR "Sent postdata = <<<$postdata>>>." + exit 1 + fi +} + +get_data_from_device() { + # If successful all device info is available in MY_PATH + rm -f "$MY_PATH/$PORT_STATUS" + send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS" + if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS + then + $LOG_ERROR "received no port data from $deviceip (Cookie Time Out)" + exit 1 + fi +} + +send_http_request() { + # ececution of http commands supported by the device + $HTTP_COMMAND "http://$username:$password@$deviceip/$1" +} + +send_web_command(){ + # ececution of web commands through the web-interface + WEB_COMMAND="wget -q --keep-session-cookies" + WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES" + WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES" + $WEB_COMMAND -O $1 -- $2 +} + +name2port() { + local name=$1 + local i=$IPPort0 + for h in $device_hostlist ; do + if [ $h = $name ]; then + echo $IPPort_name$i + return + fi + i=`expr $i + 1` + done + echo "invalid" +} + +set_port() { + # + # port status is always set. Even if requested status is current status. + # host status is not considered. + local host=$1 + local requested_status=$2 # 0 or 1 + local port=`name2port $host` + if [ "$port" = "invalid" ] + then + $LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip." + exit 1 + fi + ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11` + if [ "$ret" != "$requested_status" ] + then + $LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port." + exit 1 + fi + return 0 +} + +build_device_hostlist() { + # + # hostnames are available from http://$deviceip/iocontrol.htm" + # check for number of ports + # + device_hostlist=$( + w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' | + sed 's/[^[]*\[//;s/\].*//;s/ *//' | + while read h; do + [ -z "$h" ] && + echo $UNDEFINED_HOSTNAME || + echo $h + done + ) + if [ x = x"$device_hostlist" ]; then + $LOG_ERROR "cannot get hostlist for $deviceip" + exit 1 + fi + $LOG_DEBUG "Got new hostlist ($device_hostlist) from $deviceip" +} + +filter_device_hostlist() { + # check the given hostlist against the device hostlist + local host + for host in $device_hostlist; do + [ "$host" != "$UNDEFINED_HOSTNAME" ] && + echo $host + done +} + +check_hostlist() { + # check the given hostlist against the device hostlist + local cnt=`echo "$hostlist" | wc -w` + local cnt2=0 + local host + for host in $hostlist; do + if [ `name2port $host` != "invalid" ]; then + cnt2=$((cnt2+1)) + else + $LOG_ERROR "host $host not defined at $deviceip" + fi + done + [ $cnt -ne $cnt2 ] && + exit 1 +} + +get_http_status() { + pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]" + ret=`send_http_request "$IPGetPower" | grep $pattern` + if [ "X$ret" = "X" ] + then + $LOG_ERROR "$DEVICE at $deviceip returns invalid or no string." + exit 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +case $1 in +gethosts|on|off|reset|status) + # need environment from stonithd + # and device information from individual device + # + # default device username is admin + # IP Power 9258 does not allow user management. + # + if [ "X$username" = "X" ] + then + username="admin" + fi + + mkdir -p $MY_ROOT_PATH + tmp_path="$deviceip" # ensure a simple unique pathname + MY_PATH="$MY_ROOT_PATH/$tmp_path" + mkdir -p $MY_PATH + get_cookie_from_device + get_data_from_device + build_device_hostlist + if [ "X$hostlist" = "X" ]; then + hostlist="`filter_device_hostlist`" + else + check_hostlist + fi + ;; +*) + # the client is asking for meta-data + ;; +esac + +target=`echo $2 | sed 's/[.].*//'` +# the necessary actions for stonithd +case $1 in +gethosts) + echo $hostlist + ;; +on) + set_port $target $IPPowerOn + ;; +off) + set_port $target $IPPowerOff + ;; +reset) + set_port $target $IPPowerOff + sleep 5 + set_port $target $IPPowerOn + ;; +status) + # werify http command interface + get_http_status + ;; +getconfignames) + # return all the config names + for ipparam in deviceip username password hostlist + do + echo $ipparam + done + ;; +getinfo-devid) + echo "IP Power 9258" + ;; +getinfo-devname) + echo "IP Power 9258 power switch" + ;; +getinfo-devdescr) + echo "Power switch IP Power 9258 with 4 or 8 power outlets." + echo "WARNING: It is different from IP Power 9258 HP" + ;; +getinfo-devurl) + echo "http://www.aviosys.com/manual.htm" + ;; +getinfo-xml) + cat << IPPOWERXML +<parameters> +<parameter name="deviceip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP address or hostname of the device. +</shortdesc> +<longdesc lang="en"> +The IP Address or the hostname of the device. +</longdesc> +</parameter> + +<parameter name="password" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password to log in with. +</longdesc> +</parameter> + +<parameter name="hostlist" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the device controls. +If you leave this list empty, we will retrieve the hostnames from the device. +</longdesc> +</parameter> + +<parameter name="username" unique="0" required="0"> +<content type="string" default="admin"/> +<shortdesc lang="en"> +Account Name +</shortdesc> +<longdesc lang="en"> +The user to log in with. +</longdesc> +</parameter> + +</parameters> +IPPOWERXML + ;; +*) + $LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip." + exit 1; + ;; +esac diff --git a/lib/plugins/stonith/external/kdumpcheck.in b/lib/plugins/stonith/external/kdumpcheck.in new file mode 100644 index 0000000..7f3f752 --- /dev/null +++ b/lib/plugins/stonith/external/kdumpcheck.in @@ -0,0 +1,274 @@ +#!/bin/sh +# +# External STONITH module to check kdump. +# +# Copyright (c) 2008 NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n" +#Set default user name. +USERNAME="kdumpchecker" +#Initialize identity file-path options for ssh command +IDENTITY_OPTS="" + +#Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo ${hostlist} | tr ',' ' '` + +## +# Check the parameter hostlist is set or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_hostlist() { + if [ -z "${hostlist}" ]; then + ha_log.sh err "hostlist is empty" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Set kdump check user name to USERNAME. +# always return 0. +## +get_username() { + kdump_conf="/etc/kdump.conf" + + if [ ! -f "${kdump_conf}" ]; then + ha_log.sh debug "${kdump_conf} doesn't exist" + return 0 + fi + + tmp="" + while read config_opt config_val; do + if [ "${config_opt}" = "kdump_check_user" ]; then + tmp="${config_val}" + fi + done < "${kdump_conf}" + if [ -n "${tmp}" ]; then + USERNAME="${tmp}" + fi + + ha_log.sh debug "kdump check user name is ${USERNAME}." +} + +## +# Check the specified or default identity file exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_identity_file() { + IDENTITY_OPTS="" + if [ -n "${identity_file}" ]; then + if [ ! -f "${identity_file}" ]; then + ha_log.sh err "${identity_file} doesn't exist." + exit 6 #ERR_CONFIGURED + fi + IDENTITY_OPTS="-i ${identity_file}" + else + flg_file_exists=0 + homedir=`eval echo "~${USERNAME}"` + for filename in "${homedir}/.ssh/id_rsa" \ + "${homedir}/.ssh/id_dsa" \ + "${homedir}/.ssh/identity" + do + if [ -f "${filename}" ]; then + flg_file_exists=1 + IDENTITY_OPTS="${IDENTITY_OPTS} -i ${filename}" + fi + done + if [ ${flg_file_exists} -eq 0 ]; then + ha_log.sh err "${USERNAME}'s identity file for ssh command" \ + " doesn't exist." + exit 6 #ERR_CONFIGURED + fi + fi +} + +## +# Check the user to check doing kdump exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_user_existence() { + + # Get kdump check user name and check whether he exists or not. + grep -q "^${USERNAME}:" /etc/passwd > /dev/null 2>&1 + ret=$? + if [ ${ret} != 0 ]; then + ha_log.sh err "user ${USERNAME} doesn't exist." \ + "please confirm \"kdump_check_user\" setting in /etc/kdump.conf." \ + "(default user name is \"kdumpchecker\")" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Check the target node is kdumping or not. +# arg1 : target node name. +# ret : 0 -> the target is kdumping. +# : 1 -> the target is _not_ kdumping. +# : else -> failed to check. +## +check_kdump() { + target_node="$1" + + # Get kdump check user name. + get_username + check_user_existence + exec_cmd="${SSH_COMMAND} -l ${USERNAME}" + + # Specify kdump check user's identity file for ssh command. + check_identity_file + exec_cmd="${exec_cmd} ${IDENTITY_OPTS}" + + # Now, check the target! + # In advance, Write the following setting at the head of + # kdump_check_user's public key in authorized_keys file on target node. + # command="test -s /proc/vmcore", \ + # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty + ha_log.sh debug "execute the command [${exec_cmd} ${target_node}]." + ${exec_cmd} ${target_node} > /dev/null 2>&1 + ret=$? + ha_log.sh debug "the command's result is ${ret}." + + #ret -> 0 : vmcore file's size is not zero. the node is kdumping. + #ret -> 1 : the node is _not_ kdumping (vmcore didn't exist or + # its size is zero). It still needs to be STONITH'ed. + #ret -> 255 : ssh command is failed. + # else : Maybe command strings in authorized_keys is wrong... + return ${ret} +} + +### +# +# Main function. +# +### +case $1 in +gethosts) + check_hostlist + for hostname in ${hostlist} ; do + echo "${hostname}" + done + exit 0 + ;; +on) + # This plugin does only check whether a target node is kdumping or not. + exit 1 + ;; +reset|off) + check_hostlist + ret=1 + h_target=`echo $2 | tr A-Z a-z` + for hostname in ${hostlist} + do + hostname=`echo $hostname | tr A-Z a-z` + if [ "${hostname}" != "$h_target" ]; then + continue + fi + while [ 1 ] + do + check_kdump "$2" + ret=$? + if [ ${ret} -ne 255 ]; then + exit ${ret} + fi + #255 means ssh command itself is failed. + #For example, connection failure as if network doesn't start yet + #in 2nd kernel on the target node. + #So, retry to check after a little while. + sleep 1 + done + done + exit ${ret} + ;; +status) + check_hostlist + for hostname in ${hostlist} + do + if ping -w1 -c1 "${hostname}" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + get_username + check_user_existence + check_identity_file + exit 0 + ;; +getconfignames) + echo "hostlist identity_file" + exit 0 + ;; +getinfo-devid) + echo "kdump check STONITH device" + exit 0 + ;; +getinfo-devname) + echo "kdump check STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based kdump checker" + echo "To check whether a target node is dumping or not." + exit 0 + ;; +getinfo-devurl) + echo "kdump -> http://lse.sourceforge.net/kdump/" + echo "ssh -> http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="identity_file" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Identity file's full path for kdump check user +</shortdesc> +<longdesc lang="en"> +The full path of kdump check user's identity file for ssh command. +The identity in the specified file have to be restricted to execute +only the following command. +"test -s /proc/vmcore" +Default: kdump check user's default identity file path. +NOTE: You can specify kdump check user name in /etc/kdump.conf. + The parameter name is "kdump_check_user". + Default user is "kdumpchecker". +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/libvirt b/lib/plugins/stonith/external/libvirt new file mode 100644 index 0000000..494b048 --- /dev/null +++ b/lib/plugins/stonith/external/libvirt @@ -0,0 +1,298 @@ +#!/bin/sh +# +# External STONITH module for a libvirt managed hypervisor (kvm/Xen). +# Uses libvirt as a STONITH device to control guest. +# +# Copyright (c) 2010 Holger Teutsch <holger.teutsch@web.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# start a domain +libvirt_start() { + out=$($VIRSH -c $hypervisor_uri start $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was started" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*(running|idle)|already active' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already active" + return 0 + fi + + ha_log.sh err "Failed to start domain $domain_id" + ha_log.sh err "$out" + return 1 +} +# reboot a domain +# return +# 0: success +# 1: error +libvirt_reboot() { + local rc out + out=$($VIRSH -c $hypervisor_uri reboot $domain_id 2>&1) + rc=$? + if [ $rc -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was rebooted" + return 0 + fi + ha_log.sh err "Failed to reboot domain $domain_id (exit code: $rc)" + ha_log.sh err "$out" + return 1 +} + +# stop a domain +# return +# 0: success +# 1: error +# 2: was already stopped +libvirt_stop() { + out=$($VIRSH -c $hypervisor_uri destroy $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was stopped" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*shut off|not found|not running' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already stopped" + return 2 + fi + + ha_log.sh err "Failed to stop domain $domain_id" + ha_log.sh err "$out" + return 1 +} + +# get status of stonith device (*NOT* of the domain). +# If we can retrieve some info from the hypervisor +# the stonith device is OK. +libvirt_status() { + out=$($VIRSH -c $hypervisor_uri version 2>&1) + if [ $? -eq 0 ] + then + return 0 + fi + + ha_log.sh err "Failed to get status for $hypervisor_uri" + ha_log.sh err "$out" + return 1 +} + +# check config and set variables +# does not return on error +libvirt_check_config() { + VIRSH=`which virsh 2>/dev/null` + + if [ ! -x "$VIRSH" ] + then + ha_log.sh err "virsh not installed" + exit 1 + fi + + if [ -z "$hostlist" -o -z "$hypervisor_uri" ] + then + ha_log.sh err "hostlist or hypervisor_uri missing; check configuration" + exit 1 + fi + + case "$reset_method" in + power_cycle|reboot) : ;; + *) + ha_log.sh err "unrecognized reset_method: $reset_method" + exit 1 + ;; + esac +} + +# set variable domain_id for the host specified as arg +libvirt_set_domain_id () +{ + for h in $hostlist + do + case $h in + $1:*) + domain_id=`expr $h : '.*:\(.*\)'` + return + ;; + + $1) + domain_id=$1 + return + esac + done + + ha_log.sh err "Should never happen: Called for host $1 but $1 is not in $hostlist." + exit 1 +} + +libvirt_info() { +cat << LVIRTXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +List of hostname[:domain_id].. +</shortdesc> +<longdesc lang="en"> +List of controlled hosts: hostname[:domain_id].. +The optional domain_id defaults to the hostname. +</longdesc> +</parameter> + +<parameter name="hypervisor_uri" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hypervisor URI +</shortdesc> +<longdesc lang="en"> +URI for connection to the hypervisor. +driver[+transport]://[username@][hostlist][:port]/[path][?extraparameters] +e.g. +qemu+ssh://my_kvm_server.mydomain.my/system (uses ssh for root) +xen://my_kvm_server.mydomain.my/ (uses TLS for client) + +virsh must be installed (e.g. libvir-client package) and access control must +be configured for your selected URI. +</longdesc> +</parameter> + +<parameter name="reset_method" required="0"> +<content type="string" default="power_cycle"/> +<shortdesc lang="en"> +How to reset a guest. +</shortdesc> +<longdesc lang="en"> +A guest reset may be done by a sequence of off and on commands +(power_cycle) or by the reboot command. Which method works +depend on the hypervisor and guest configuration management. +</longdesc> +</parameter> +</parameters> +LVIRTXML +exit 0 +} + +############# +# Main code # +############# + +# don't fool yourself when testing with stonith(8) +# and transport ssh +unset SSH_AUTH_SOCK + +# support , as a separator as well +hostlist=`echo $hostlist| sed -e 's/,/ /g'` + +reset_method=${reset_method:-"power_cycle"} + +case $1 in + gethosts) + hostnames=`echo $hostlist|sed -e 's/:[^ ]*//g'` + for h in $hostnames + do + echo $h + done + exit 0 + ;; + + on) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_start + exit $? + ;; + + off) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_stop + [ $? = 1 ] && exit 1 + exit 0 + ;; + + reset) + libvirt_check_config + libvirt_set_domain_id $2 + + if [ "$reset_method" = "power_cycle" ]; then + libvirt_stop + [ $? = 1 ] && exit 1 + sleep 2 + libvirt_start + else + libvirt_reboot + fi + exit $? + ;; + + status) + libvirt_check_config + libvirt_status + exit $? + ;; + + getconfignames) + echo "hostlist hypervisor_uri reboot_method" + exit 0 + ;; + + getinfo-devid) + echo "libvirt STONITH device" + exit 0 + ;; + + getinfo-devname) + echo "libvirt STONITH external device" + exit 0 + ;; + + getinfo-devdescr) + echo "libvirt-based host reset for Xen/KVM guest domain through hypervisor" + exit 0 + ;; + + getinfo-devurl) + echo "http://libvirt.org/uri.html http://linux-ha.org/wiki" + exit 0 + ;; + + getinfo-xml) + libvirt_info + echo 0; + ;; + + *) + exit 1 + ;; +esac + +# vi:et:ts=4:sw=4 diff --git a/lib/plugins/stonith/external/nut b/lib/plugins/stonith/external/nut new file mode 100644 index 0000000..9e51bb8 --- /dev/null +++ b/lib/plugins/stonith/external/nut @@ -0,0 +1,302 @@ +#!/bin/sh + +# External STONITH module that uses the NUT daemon to control an external UPS. +# See the comments below, and the various NUT man pages, for how this +# script works. It should work unchanged with most modern "smart" APC UPSes in +# a Redhat/Fedora/RHEL-style distribution with the nut package installed. + +# Author: William Seligman <seligman@nevis.columbia.edu> +# License: GPLv2 + +# As you're designing your UPS and STONITH set-up, it may help to consider that +# there can be potentially three computers involved: +# 1) the machine running this STONITH module; +# 2) the machine being controlled by this STONITH module ($hostname); +# 3) the machine that can send commands to the UPS. + +# On my cluster, all the UPSes have SNMP smartcards, so every host can communicate +# with every UPS; in other words, machines (1) and (3) are the same. If your UPSes +# are controlled via serial or USB connections, then you might have a +# situation in which $hostname is plugged into a UPS, which has a serial connection +# to some master "power-control" computer, and can potentially be STONITHed +# by any other machine in your cluster. + +# In general, you'll probably need the nut daemon running on both the hosts (1) and +# (3) in the above list. The NUT daemon will also have to run on (2) if you want the +# reset command to gracefully reboot $hostname. + +# The NUT command default locations. In the RHEL-type nut packages, these binaries +# are in /usr/bin. +RHELUPSCMD="/usr/bin/upscmd" +RHELUPSC="/usr/bin/upsc" + +# Defaults for APC smart UPSes: + +# Reset = reboot $hostname; this will be a graceful reboot if the host +# is running NUT and monitoring $ups. +APCRESET="shutdown.return" + +# Poweroff = turn off $hostname immediately by cutting the power on $ups. +# For a graceful shutdown, use shutdown.stayoff instead of load.off, +# but it might take a few minutes to shutdown in this way. +APCPOWEROFF="load.off" + +# Poweron = turn on the power to $ups, which will presumably turn on $hostname. +# (Did you set $hostname's BIOS to boot up on AC power restore, as opposed to +# "last state"?) +APCPOWERON="load.on" + +# Status = returns a short string with the $ups status; OL = on-line, OFF = off-line, etc. +APCSTATUSVAR="ups.status" + + +# Stick in the defaults, if needed. +if [ -z "${poweron}" ]; then + poweron=${APCPOWERON} +fi +if [ -z "${poweroff}" ]; then + poweroff=${APCPOWEROFF} +fi +if [ -z "${reset}" ]; then + reset=${APCRESET} +fi +if [ -z "${statusvar}" ]; then + statusvar=${APCSTATUSVAR} +fi +if [ -z "${upscmd}" ]; then + upscmd=${RHELUPSCMD} +fi +if [ -z "${upsc}" ]; then + upsc=${RHELUPSC} +fi + + +# Define the command to fetch the UPS status. +STATUSCMD="${upsc} ${ups} ${statusvar}" + +usage() { + echo "Usage: $0 {on|off|reset|status|gethosts|getconfignames|getinfo-devid|getinfo-devname|getinfo-devdescr|getinfo-devurl|getinfo-xml}" +} + +# Can we find the NUT binary? +have_nut() { + test -x "${upscmd}" +} +have_upsc() { + test -x "${upsc}" +} + +do_nut() { + have_nut || { + echo "Can't find NUT upscmd command" + return 1 + } + if [ -z "${username}" -o -z "${password}" -o -z "${ups}" ]; then + echo "username, password or ups name missing; check configuration" + return 1 + fi + # Execute the command given in argument 1. + ${upscmd} -u ${username} -p ${password} ${ups} ${1} || { + echo "error executing nut command" + return 1 + } +} + +case ${1} in +gethosts) + echo ${hostname} + exit 0 + ;; +on) + result=1 + do_nut "${poweron}" + result=$? + exit ${result} + ;; +off) + result=1 + do_nut "${poweroff}" + result=$? + exit ${result} + ;; +reset) + result=1 + do_nut "${reset}" + result=$? + exit $result + ;; +status) + have_upsc || { + echo "Can't find NUT upsc command" + exit 1 + } + ${STATUSCMD} + exit $? + ;; +getconfignames) + echo "hostname ups username password poweron poweroff reset statusvar upscmd upsc" + exit 0 + ;; +getinfo-devid) + echo "NUT STONITH device" + exit 0 + ;; +getinfo-devname) + echo "NUT STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "A STONITH device based on NUT (Network UPS Tools)." + echo " " + echo "For this STONITH script to work, the following conditions have" + echo "to be met:" + echo " " + echo "- NUT has to be installed on both the host running this script" + echo " and the host that controls the UPS (on RHEL systems, NUT is" + echo " in packages nut and nut-client) and the nut daemon services" + echo " (normally called the ups or upsd service) must be running" + echo " on both systems." + echo " " + echo "- The UPS name has to be defined in ups.conf on the host" + echo " that controls the UPS." + echo " " + echo "- The username/password to access the UPS must be defined in" + echo " upsd.users on the host that controls the UPS, with the instcmds" + echo " for poweron, poweroff, and reset allowed." + echo " " + echo "- The host that is running this script must be allowed access" + echo " via upsd.conf and upsd.users on the host the controls the UPS." + echo " " + echo "On RHEL systems, the files listed above are in /etc/ups." + echo " " + echo "The defaults will probably work with APC UPS devices. It might" + echo "work on others; 'upscmd -l (ups)' and 'upsc (ups)' will list" + echo "the commands and variables, and you can change the values" + echo "for poweron, poweroff, reset, and statusvar to suit your UPS." + echo "Change upscmd and upsc if your NUT binaries are not in /usr/bin." + exit 0 + ;; +getinfo-devurl) + echo "http://www.networkupstools.org/" + exit 0 + ;; +getinfo-xml) +cat << nutXML +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Hostname</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +The nut daemon must be running on the host controllng the +UPS _and_ on the host running this script; this script does +not start/stop the daemons for you. +</longdesc> +</parameter> + +<parameter name="ups" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">UPS name</shortdesc> +<longdesc lang="en"> +The name of the UPS as defined in ups.conf on the host +controlling the UPS. The format for this option is +upsname[@controlhost[:port]]. The default controlhost is +"localhost". +</longdesc> +</parameter> + +<parameter name="username" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Username</shortdesc> +<longdesc lang="en"> +The username used for accessing the UPS. This is defined in +upsd.conf on the host controlling the UPS. +</longdesc> +</parameter> + +<parameter name="password" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password used for logging in to the UPS for the host +controlling the UPS, as defined in upsd.conf on that host. +</longdesc> +</parameter> + +<parameter name="poweron"> +<content type="string" default="$APCPOWERON" /> +<shortdesc lang="en">UPS Power On command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn on the UPS. The default +should work for most "smart" APC UPSes. For a list of +commands that your UPS can support, type 'upscmd -l (ups)' +on the command line.</longdesc> +</parameter> + +<parameter name="poweroff"> +<content type="string" default="$APCPOWEROFF" /> +<shortdesc lang="en">UPS Power Off command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn off the UPS. On most APC +"smart" UPSes, the command shutdown.stayoff will result +in a graceful shutdown, provided the host is running the +nut daemon, but this might take a few minutes; load.off +will cut the power immediately. For a list of commands that +your UPS can support, type 'upscmd -l (ups)' on the command +line. +</longdesc> +</parameter> + +<parameter name="reset"> +<content type="string" default="$APCRESET" /> +<shortdesc lang="en">UPS Reset command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to reset the host. On most APC +"smart" UPSes, the command shutdown.return will result +in a graceful shutdown, with power restored after perhaps +a short interval. For a list of commands that your UPS can + support, type 'upscmd -l (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="statusvar"> +<content type="string" default="$APCSTATUSVAR" /> +<shortdesc lang="en">UPS Status variable</shortdesc> +<longdesc lang="en"> +The NUT variable that returns the status of the UPS. On APC +UPSes, the value of ups.status will be "OL" if the UPS is +"on-line." For a list of variables that your UPS supports, +type 'upsc (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="upscmd"> +<content type="string" default="$RHELUPSCMD" /> +<shortdesc lang="en">upscmd binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upscmd'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upscmd. +</longdesc> +</parameter> + +<parameter name="upsc"> +<content type="string" default="$RHELUPSC" /> +<shortdesc lang="en">upsc binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upsc'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upsc. +</longdesc> +</parameter> + +</parameters> +nutXML +exit 0 +;; +*) + usage + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/rackpdu b/lib/plugins/stonith/external/rackpdu new file mode 100644 index 0000000..7d0e20b --- /dev/null +++ b/lib/plugins/stonith/external/rackpdu @@ -0,0 +1,280 @@ +#!/bin/sh +# +# External STONITH module for APC Switched Rack PDU +# +# Copyright (c) 2008 Sergey Maznichenko <msergeyb@gmail.com> <inbox@it-consultant.su> +# Version 1.2 +# +# See http://www.it-consultant.su/rackpdu +# for additional information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SWITCH_ON="1" +SWITCH_OFF="2" +SWITCH_RESET="3" + +DEFAULT_NAMES_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" +DEFAULT_COMMAND_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4" + +if [ -z "$oid" ]; then + oid=$DEFAULT_COMMAND_OID +fi + +if [ -z "$names_oid" ]; then + names_oid=$DEFAULT_NAMES_OID +fi + +if [ -z "$outlet_config" ]; then + outlet_config="none" +fi + +GetOutletNumber() { + local nodename=$1 + + if [ "$outlet_config" != "none" ]; then + # Get outlet number from file + + if [ -f "$outlet_config" ]; then + local outlet_num=`grep $nodename $outlet_config | tr -d ' ' | cut -f2 -d'='` + if [ -z "$outlet_num" ]; then + ha_log.sh err "Outlet number not found for node $nodename. Check configuration file $outlet_config" + return 0 + fi + return $outlet_num + else + ha_log.sh err "File $outlet_config not found." + return 0 + fi + else + # Get outlet number from device + + local outlet_num=1 + local snmp_result + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + return 0 + fi + + local names + names=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + for name in $names; do + if [ "$name" != "$nodename" ]; then + local outlet_num=`expr $outlet_num + 1` + continue + fi + + return $outlet_num + done + + ha_log.sh err "Outlet number not found for node $nodename. Result: $snmp_result" + return 0 + fi +} + +SendCommand() { + + local host=$1 + local command=$2 + + GetOutletNumber $host + local outlet=$? + + if [ $outlet -gt 0 ]; then + local set_result + set_result=`snmpset -v1 -c $community $pduip $oid.$outlet i $command 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command failed. Result: $set_result" + return 1 + fi + if echo "$set_result" | grep -qs "Timeout"; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command timed out. Result: $set_result" + return 1 + fi + return 0 + else + return 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +incommand=$1 +innode=$2 + +case $incommand in +gethosts) + if [ "$hostlist" = "AUTO" ]; then + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + exit 1 + fi + if echo "$snmp_result" | grep -qs "Timeout"; then + ha_log.sh err "snmpwalk $community $pduip $names_oid timed out. Result: $snmp_result" + exit 1 + else + hostlist=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + fi + fi + + for h in $hostlist ; do + echo $h + done + + exit 0 + ;; +on) + if + SendCommand $innode $SWITCH_ON + then + exit 0 + else + exit 1 + fi + ;; +off) + if + SendCommand $innode $SWITCH_OFF + then + exit 0 + else + exit 1 + fi + ;; +reset) + if + SendCommand $innode $SWITCH_RESET + then + exit 0 + else + exit 1 + fi + ;; +status) + if [ -z "$pduip" ]; then + exit 1 + fi + + if ping -w1 -c1 $pduip >/dev/null 2>&1; then + exit 0 + else + exit 1 + fi + ;; +getconfignames) + echo "hostlist pduip community" + exit 0 + ;; +getinfo-devid) + echo "rackpdu STONITH device" + exit 0 + ;; +getinfo-devname) + echo "rackpdu STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "APC Switched Rack PDU" + exit 0 + ;; +getinfo-devurl) + echo "http://www.apcc.com/products/family/index.cfm?id=30" + exit 0 + ;; +getinfo-xml) + cat << PDUXML +<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string" default="AUTO" /> + <shortdesc lang="en">Hostlist</shortdesc> + <longdesc lang="en"> +The list of hosts that the STONITH device controls (comma or space separated). +If you set value of this parameter to AUTO, list of hosts will be get from Rack PDU device. + </longdesc> + </parameter> + + <parameter name="pduip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">Name or IP address of Rack PDU device.</shortdesc> + <longdesc lang="en">Name or IP address of Rack PDU device.</longdesc> + </parameter> + + <parameter name="community" unique="1" required="1"> + <content type="string" default="private" /> + <shortdesc lang="en">Name of write community.</shortdesc> + <longdesc lang="en">Name of write community.</longdesc> + </parameter> + + <parameter name="oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en"> + The OID without the outlet number. + </shortdesc> + <longdesc lang="en"> +The SNMP OID for the PDU. minus the outlet number. +Try .1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="names_oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">The OID for getting names of outlets.</shortdesc> + <longdesc lang="en"> +The SNMP OID for getting names of outlets. +It is required to recognize outlet number by nodename. +Try ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Names of nodes must be equal names of outlets, in other way use outlet_config parameter. +If you set 'names_oid' parameter then parameter outlet_config must not be use. +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="outlet_config" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">Configuration file. Other way to recognize outlet number by nodename.</shortdesc> + <longdesc lang="en"> +Configuration file. Other way to recognize outlet number by nodename. +Configuration file which contains +node_name=outlet_number +strings. + +Example: +server1=1 +server2=2 + +If you use outlet_config parameter then names_oid parameter can have any value and it is not uses. + </longdesc> + </parameter> + +</parameters> +PDUXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/riloe b/lib/plugins/stonith/external/riloe new file mode 100644 index 0000000..ce98847 --- /dev/null +++ b/lib/plugins/stonith/external/riloe @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# +# Stonith module for RILOE Stonith device +# +# Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> +# +# Modified by Alan Robertson <alanr@unix.sh> for STONITH external compatibility. +# +# Extended and merged by Tijl Van den broeck <subspawn@gmail.com> +# with ilo-v2 script from Guy Coates +# +# Cleanup by Andrew Beekhof <abeekhof@suse.de> +# +# Rewritten by Dejan Muhamedagic <dejan@suse.de> +# Now, the plugin actually reads replies from iLO. +# +# Extended by Jochen Roeder <jochen.roeder@novell.com> +# to enable access via proxies +# +# 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 +# +import sys +import os +import socket +import subprocess +import xml.dom.minidom +import httplib +import time +import re + +def log_msg(level,msg): + subprocess.call("ha_log.sh %s '%s'" % (level,msg), shell=True) +def my_err(msg): + log_msg("err", msg) +def my_warn(msg): + log_msg("warn", msg) +def my_debug(msg): + log_msg("debug", msg) +def fatal(msg): + my_err(msg) + sys.exit(1) + +argv = sys.argv + +try: + cmd = argv[1] +except IndexError: + my_err("Not enough arguments") + sys.exit(1) + +legacy_RI_HOST = os.environ.get('RI_HOST', '') +legacy_RI_HOSTRI = os.environ.get('RI_HOSTRI', '') +legacy_RI_LOGIN = os.environ.get('RI_LOGIN', 'Administrator') +legacy_RI_PASSWORD = os.environ.get('RI_PASSWORD', '') + +reset_ok = os.environ.get('ilo_can_reset', '0') +ilo_protocol = os.environ.get('ilo_protocol', '1.2') +power_method = os.environ.get('ilo_powerdown_method', 'power') + +realhost = os.environ.get('hostlist', legacy_RI_HOST) +rihost = os.environ.get('ilo_hostname', legacy_RI_HOSTRI) +ilouser = os.environ.get('ilo_user', legacy_RI_LOGIN) +ilopass = os.environ.get('ilo_password', legacy_RI_PASSWORD) +iloproxyhost = os.environ.get('ilo_proxyhost', '') +try: + iloproxyport = int(os.environ.get('ilo_proxyport', 3128)) +except ValueError: + my_err("ilo_proxyport is not a number") + sys.exit(1) + +xmlinfo = '''<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo target hostname</shortdesc> + <longdesc lang="en"> + Contains the hostname that the ilo controls + </longdesc> + </parameter> +<parameter name="ilo_hostname" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo device hostname</shortdesc> + <longdesc lang="en"> + The hostname of the ilo device + </longdesc> + </parameter> +<parameter name="ilo_user" unique="0" required="1"> + <content type="string" default="Administrator"/> + <shortdesc lang="en">ilo user</shortdesc> + <longdesc lang="en"> + The user for connecting to the ilo device + </longdesc> + </parameter> +<parameter name="ilo_password" unique="0" required="1"> + <content type="string" default=""/> + <shortdesc lang="en">password</shortdesc> + <longdesc lang="en"> + The password for the ilo device user + </longdesc> + </parameter> +<parameter name="ilo_can_reset" unique="0" required="0"> + <content type="string" default="0"/> + <shortdesc lang="en">Device can reset</shortdesc> + <longdesc lang="en"> + Does the ILO device support RESET commands (hint: older ones cannot) + </longdesc> + </parameter> +<parameter name="ilo_protocol" unique="0" required="0"> + <content type="string" default="1.2"/> + <shortdesc lang="en">ILO Protocol</shortdesc> + <longdesc lang="en"> + Protocol version supported by the ILO device. + Known supported versions: 1.2, 2.0 + </longdesc> + </parameter> +<parameter name="ilo_powerdown_method" unique="0" required="0"> + <content type="string" default="power"/> + <shortdesc lang="en">Power down method</shortdesc> + <longdesc lang="en"> + The method to powerdown the host in question. + * button - Emulate holding down the power button + * power - Emulate turning off the machines power + + NB: A button request takes around 20 seconds. The power method + about half a minute. + </longdesc> + </parameter> +<parameter name="ilo_proxyhost" unique="0" required="0"> + <content type="string" default=""/> + <shortdesc lang="en">Proxy hostname</shortdesc> + <longdesc lang="en"> + proxy hostname if required to access ILO board + </longdesc> + </parameter> +<parameter name="ilo_proxyport" unique="0" required="0"> + <content type="string" default="3128"/> + <shortdesc lang="en">Proxy port</shortdesc> + <longdesc lang="en"> + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + </longdesc> + </parameter> + +</parameters>''' + +info = { + 'getinfo-devid': 'iLO2', + 'getinfo-devname': 'ilo2 ' + rihost, + 'getinfo-devdescr': 'HP/COMPAQ iLO2 STONITH device', + 'getinfo-devurl': 'http://www.hp.com/', + 'gethosts': realhost, + 'getinfo-xml': xmlinfo +} + +if cmd in info: + print info[cmd] + sys.exit(0) + +if cmd == 'getconfignames': + for arg in [ "hostlist", "ilo_hostname", "ilo_user", "ilo_password", "ilo_can_reset", "ilo_protocol", "ilo_powerdown_method", "ilo_proxyhost", "ilo_proxyport"]: + print arg + sys.exit(0) + +if not rihost: + fatal("ILO device hostname not specified") + +if not realhost: + fatal("Host controlled by this ILO device not specified") + +if not power_method in ("power","button"): + my_err('unknown power method %s, setting to "power"') + power_method = "power" + +# XML elements +E_RIBCL = "RIBCL" +E_LOGIN = "LOGIN" +E_SERVER_INFO = "SERVER_INFO" + +# power mgmt methods +E_RESET = "RESET_SERVER" # error if powered off +E_COLD_BOOT = "COLD_BOOT_SERVER" # error if powered off +E_WARM_BOOT = "WARM_BOOT_SERVER" # error if powered off +E_PRESS_BUTTON = "PRESS_PWR_BTN" +E_HOLD_BUTTON = "HOLD_PWR_BTN" + +# get/set status elements +E_SET_POWER = "SET_HOST_POWER" +E_GET_PSTATUS = "GET_HOST_POWER_STATUS" + +# whatever this means, but we have to use it to get good XML +E_LOCFG = "LOCFG" +LOCFG_VER = '2.21' + +# attributes +A_VERSION = "VERSION" # ilo_protocol +A_USER = "USER_LOGIN" +A_PWD = "PASSWORD" +A_MODE = "MODE" # info mode (read or write) +A_POWER_SW = "HOST_POWER" # "Y" or "N" +A_POWER_STATE = "HOST_POWER" # "ON" or "OFF" + +def new_power_req(tag, name = None, value = None): + ''' + Create a new RIBCL request (as XML). + ''' + my_debug("creating power request: %s,%s,%s"%(tag,name,value)) + doc = xml.dom.minidom.Document() + locfg = doc.createElement(E_LOCFG) + locfg.setAttribute(A_VERSION,LOCFG_VER) + ribcl = doc.createElement(E_RIBCL) + ribcl.setAttribute(A_VERSION,ilo_protocol) + login = doc.createElement(E_LOGIN) + login.setAttribute(A_USER,ilouser) + login.setAttribute(A_PWD,ilopass) + serv_info = doc.createElement(E_SERVER_INFO) + # read or write, it doesn't really matter, i.e. even if we + # say "write" that doesn't mean we can't read + serv_info.setAttribute(A_MODE,"write") + doc.appendChild(locfg) + locfg.appendChild(ribcl) + ribcl.appendChild(login) + login.appendChild(serv_info) + el_node = doc.createElement(tag) + if name: + el_node.setAttribute(name,value) + serv_info.appendChild(el_node) + s = doc.toprettyxml() + doc.unlink() + # work around an iLO bug: last line containing "</LOCFG>" + # produces a syntax error + lines = s.split('\n') + return '\n'.join(lines[:-2]) + +E_RESPONSE = "RESPONSE" +E_HOST_POWER = "GET_HOST_POWER" +A_STATUS = "STATUS" +# documentation mentions both; better safe than sorry +A_MSG = "MSG" +A_MSG2 = "MESSAGE" + +def is_element(xmlnode): + return xmlnode.nodeType == xmlnode.ELEMENT_NODE + +def read_resp(node): + ''' + Check if the RESPONSE XML is OK. + ''' + msg = "" + str_status = "" + for attr in node.attributes.keys(): + if attr == A_STATUS: + str_status = node.getAttribute(attr) + elif attr == A_MSG: + msg = node.getAttribute(attr) + elif attr == A_MSG2: + msg = node.getAttribute(attr) + else: + my_warn("unexpected attribute %s in %s" % (attr,E_RESPONSE)) + if not str_status: + my_err("no status in response") + return -1 + try: + status = int(str_status,16) + except ValueError: + my_err("unexpected status %s in response" % str_status) + return -1 + if status != 0: + my_err("%s (rc: %s)"%(msg,str_status)) + return -1 + return 0 + +def read_power(node): + ''' + Read the power from the XML node. Set the global power + variable correspondingly. + ''' + global power + for attr in node.attributes.keys(): + if attr == A_POWER_STATE: + power_state = node.getAttribute(attr).upper() + else: + my_warn("unexpected attribute %s in %s" % (attr,node.tagName)) + if not power_state: + my_err("no %s attribute in %s" % (A_POWER_STATE,node.tagName)) + return -1 + if power_state not in ("ON","OFF"): + my_err("unexpected value for %s: %s" % (A_POWER_STATE,power_state)) + return -1 + power = (power_state == "ON") + my_debug("Host has power: %s"%power) + return 0 + +el_parsers = { + E_RESPONSE:read_resp, + E_HOST_POWER:read_power +} +def proc_resp(doc): + ''' + Process one iLO reply. Real work is done in el_parsers. + ''' + ribcl = doc.childNodes[0] + if not is_element(ribcl) or ribcl.tagName != E_RIBCL: + my_err("unexpected top element in response") + return -1 + for child in ribcl.childNodes: + if not is_element(child): + continue + if child.tagName in el_parsers: + rc = el_parsers[child.tagName](child) + if rc != 0: + return -1 + else: + my_warn("unexpected element in response: %s" % child.toxml()) + return 0 + +def open_ilo(host): + # open https connection + try: + if iloproxyhost != "" and iloproxyport != 0: + proxy=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + proxy.connect((iloproxyhost, iloproxyport)) + proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,443) + user_agent='User-Agent: python\r\n' + proxy_pieces=proxy_connect+user_agent+'\r\n' + proxy.sendall(proxy_pieces) + response=proxy.recv(8192) + status=response.split()[1] + if status!=str(200): + fatal("Error status=: %s" %(response)) + import ssl + sock = ssl.wrap_socket(proxy) + h=httplib.HTTPConnection('localhost') + h.sock=sock + return h + else: + return httplib.HTTPSConnection(host) + except socket.gaierror, msg: + fatal("%s: %s" %(msg,host)) + except socket.sslerror, msg: + fatal("%s for %s" %(msg,host)) + except socket.error, msg: + fatal("%s while talking to %s" %(msg,host)) + except ImportError, msg: + fatal("ssl support missing (%s)" %msg) + +def send_request(req,proc_f): + ''' + 1. After every request, the iLO closes the connection. + 2. For every request, there are multiple replies. Each reply + is an XML document. Most of replies are just a kind of + (verbose) XML "OK". + ''' + t_begin = time.time() + c = open_ilo(rihost) + try: + c.send(req+'\r\n') + except socket.error, msg: + fatal("%s, while talking to %s" %(msg,rihost)) + t_end = time.time() + my_debug("request sent in %0.2f s" % ((t_end-t_begin))) + + t_begin = time.time() + result = [] + while True: + try: + reply = c.sock.recv(1024) + if not reply: + break + result.append(reply) + except socket.error, msg: + if msg[0] == 6: # connection closed + break + my_err("%s, while talking to %s" %(msg,rihost)) + return -1 + c.close() + t_end = time.time() + + if not result: + fatal("no response from %s within %0.2f s"%(rihost,(t_end-t_begin))) + for reply in result: + # work around the iLO bug, i.e. element RIBCL closed twice + if re.search("</RIBCL", reply) and re.search("<RIBCL.*/>", reply): + reply = re.sub("<(RIBCL.*)/>", r"<\1>", reply) + try: + doc = xml.dom.minidom.parseString(reply) + except xml.parsers.expat.ExpatError,msg: + fatal("malformed response: %s\n%s"%(msg,reply)) + rc = proc_f(doc) + doc.unlink() + if rc != 0: + break + my_debug("iLO processed request (rc=%d) in %0.2f s" % (rc,(t_end-t_begin))) + return rc + +def manage_power(cmd): + ''' + Before trying to send a request we have to check the power + state. + ''' + rc = 0 + req = '' + # it won't do to turn it on if it's already on! + if cmd == "on" and not power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + # also to turn it off if it's already off + elif cmd == "off" and power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"N") + elif cmd == "cold_boot" and power: + req = new_power_req(E_COLD_BOOT) + elif cmd == "warm_boot" and power: + req = new_power_req(E_WARM_BOOT) + elif cmd == "reset": + if power: + req = new_power_req(E_RESET) + # reset doesn't work if the host's off + else: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + if req: + rc = send_request(req,proc_resp) + return rc +def power_on(): + ''' + Update the power variable without checking the power state. + The iLO is slow at times to report the actual power state, so + we assume that it changed if the request succeeded. + ''' + rc = manage_power("on") + if rc == 0: + global power + power = True + return rc +def power_off(): + rc = manage_power("off") + if rc == 0: + global power + power = False + return rc +def cold_boot(): + rc = manage_power("cold_boot") + return rc +def warm_boot(): + rc = manage_power("warm_boot") + return rc +def reset(): + rc = manage_power("reset") + if rc == 0: + global power + power = True + return rc +def hold_button(): + ''' + Hold the power button. Got this error message when tried + without the TOGGLE attribute: + Command without TOGGLE="Yes" attribute is ignored + when host power is off. (rc: 0x0054) + Didn't find any documentation about TOGGLE. + ''' + if power: + req = new_power_req(E_HOLD_BUTTON) + else: + req = new_power_req(E_HOLD_BUTTON,"TOGGLE","Yes") + rc = send_request(req,proc_resp) + return rc +def read_power_state(): + req = new_power_req(E_GET_PSTATUS) + rc = send_request(req,proc_resp) + return rc + +def reset_power(): + ''' + Three methods to reset: + - hold power button + - reset (only if host has power and user said that reset is ok) + - power off/on + ''' + do_power_on = False + if power_method == 'button': + rc = hold_button() + elif reset_ok != '0': + if power: + return reset() + else: + return power_on() + else: + do_power_on = True + rc = power_off() + if rc == 0: + rc = read_power_state() + if do_power_on: + while rc == 0 and power: # wait for the power state to go off + time.sleep(5) + rc = read_power_state() + if rc == 0 and do_power_on and not power: + rc = power_on() + return rc + +# track state of host power +power = -1 + +todo = { +'reset':reset_power, +'on':power_on, +'off':power_off, +'cold':cold_boot, +'warm':warm_boot, +'status':lambda: 0 # just return 0, we already read the state +} + +rc = read_power_state() +if rc == 0: + if cmd in todo: + rc = todo[cmd]() + else: + fatal('Invalid command: %s' % cmd) +if rc != 0: + fatal("request failed") +sys.exit(rc) + +# vi:ts=4:sw=4:et: diff --git a/lib/plugins/stonith/external/ssh.in b/lib/plugins/stonith/external/ssh.in new file mode 100644 index 0000000..2a8eb73 --- /dev/null +++ b/lib/plugins/stonith/external/ssh.in @@ -0,0 +1,176 @@ +#!/bin/sh +# +# External STONITH module for ssh. +# +# Copyright (c) 2004 SUSE LINUX AG - Lars Marowsky-Bree <lmb@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n -l root" +#SSH_COMMAND="@SSH@ -q -x -n -l root" + +REBOOT_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Warning: If you select this poweroff command, it'll physically +# power-off the machine, and quite a number of systems won't be remotely +# revivable. +# TODO: Probably should touch a file on the server instead to just +# prevent heartbeat et al from being started after the reboot. +# POWEROFF_COMMAND="echo 'sleep 2; /sbin/poweroff -nf' | SHELL=/bin/sh at now >/dev/null 2>&1" +POWEROFF_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +is_host_up() { + for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + do + if + ping -w1 -c1 "$1" >/dev/null 2>&1 + then + sleep 1 + else + return 1 + fi + done + return 0 +} + + +case $1 in +gethosts) + for h in $hostlist ; do + echo $h + done + exit 0 + ;; +on) + # Can't really be implemented because ssh cannot power on a system + # when it is powered off. + exit 1 + ;; +off) + # Shouldn't really be implemented because if ssh cannot power on a + # system, it shouldn't be allowed to power it off. + exit 1 + ;; +reset) + h_target=`echo $2 | tr A-Z a-z` + for h in $hostlist + do + h=`echo $h | tr A-Z a-z` + [ "$h" != "$h_target" ] && + continue + if + case ${livedangerously} in + [Yy]*) is_host_up $h;; + *) true;; + esac + then + $SSH_COMMAND "$2" "$REBOOT_COMMAND" + # Good thing this is only for testing... + if + is_host_up $h + then + exit 1 + else + exit 0 + fi + else + # well... Let's call it successful, after all this is only for testing... + exit 0 + fi + done + exit 1 + ;; +status) + if + [ -z "$hostlist" ] + then + exit 1 + fi + for h in $hostlist + do + if + ping -w1 -c1 "$h" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist" + exit 0 + ;; +getinfo-devid) + echo "ssh STONITH device" + exit 0 + ;; +getinfo-devname) + echo "ssh STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset" + echo "Fine for testing, but not suitable for production!" + echo "Only reboot action supported, no poweroff, and, surprisingly enough, no poweron." + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="livedangerously" unique="0" required="0"> +<content type="enum" /> +<shortdesc lang="en"> +Live Dangerously!! +</shortdesc> +<longdesc lang="en"> +Set to "yes" if you want to risk your system's integrity. +Of course, since this plugin isn't for production, using it +in production at all is a bad idea. On the other hand, +setting this parameter to yes makes it an even worse idea. +Viva la Vida Loca! +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/vcenter b/lib/plugins/stonith/external/vcenter new file mode 100755 index 0000000..71a6302 --- /dev/null +++ b/lib/plugins/stonith/external/vcenter @@ -0,0 +1,280 @@ +#!/usr/bin/env perl +# +# External STONITH module for VMWare vCenter/ESX +# +# Author: Nhan Ngo Dinh +# License: GNU General Public License (GPL) +# + +require 5.010; + +use strict; +use warnings; + +sub dielog { + my $msg = "["; + $msg .= "$ARGV[0]" if defined($ARGV[0]); + $msg .= " $ARGV[1]" if defined($ARGV[1]); + $msg .= "]"; + ( $_ ) = @_; + $msg .= " $_"; + system("ha_log.sh", "err", "$msg"); + die(); +} + +# Define command groups +my @configCommands = qw{getconfignames getinfo-devid getinfo-devname getinfo-devdescr getinfo-devurl getinfo-xml}; +my @actionCommands = qw{reset on off}; +my @netCommands = (@actionCommands, qw{status gethosts listvms}); + +# Process command line arguments +my $command = $ARGV[0] || dielog("No command specified\n"); + +# Command belongs to the group of commands that do not require any connection to VMware vCenter +if ($command ~~ @configCommands) { + if ($command eq "getconfignames") { + print "VI_SERVER\nVI_PORTNUMBER\nVI_PROTOCOL\nVI_SERVICEPATH\nVI_CREDSTORE\nHOSTLIST\nRESETPOWERON\n"; + } + elsif ($command eq "getinfo-devid") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devname") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devdescr") { + print "VMWare vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devurl") { + print "http://www.vmware.com/\n"; + } + elsif ($command eq "getinfo-xml") { + print q{<parameters> +<parameter name="HOSTLIST" required="1"> +<content type="string"/> +<shortdesc lang="en">List of hosts and virtual machines (required)</shortdesc> +<longdesc lang="en"> +The list of hosts that the VMware vCenter STONITH device controls. +Syntax is: + hostname1[=VirtualMachineName1];hostname2[=VirtualMachineName2] + +NOTE: omit =VirtualMachineName if hostname and virtual machine names are identical + +Example: + cluster1=VMCL1;cluster2=VMCL2 +</longdesc> +</parameter> +<parameter name="VI_SERVER"> +<content type="string" default="localhost"/> +<shortdesc lang="en">VMware vCenter address</shortdesc> +<longdesc lang="en"> +The VMware vCenter address +</longdesc> +</parameter> +<parameter name="VI_PROTOCOL"> +<content type="string" default="https"/> +<shortdesc lang="en">VMware vCenter protocol</shortdesc> +<longdesc lang="en"> +The VMware vCenter protocol +</longdesc> +</parameter> +<parameter name="VI_PORTNUMBER"> +<content type="string" default="443"/> +<shortdesc lang="en">VMware vCenter port number</shortdesc> +<longdesc lang="en"> +The VMware vCenter port number +</longdesc> +</parameter> +<parameter name="VI_SERVICEPATH"> +<content type="string" default="/sdk"/> +<shortdesc lang="en">VMware vCenter service path</shortdesc> +<longdesc lang="en"> +The VMware vCenter services path +</longdesc> +</parameter> +<parameter name="VI_CREDSTORE" required="1"> +<content type="string"/> +<shortdesc lang="en">VMware vCenter credentials store file</shortdesc> +<longdesc lang="en"> +VMware vCenter credentials store file +</longdesc> +</parameter> +<parameter name="RESETPOWERON"> +<content type="string" default="1"/> +<shortdesc lang="en">PowerOnVM on reset</shortdesc> +<longdesc lang="en"> +Enable/disable a PowerOnVM on reset when the target virtual machine is off +Allowed values: 0, 1 +</longdesc> +</parameter> +<parameter name="PERL_LWP_SSL_VERIFY_HOSTNAME"> +<content type="string"/> +<shortdesc lang="en">Enable or disable SSL hostname verification</shortdesc> +<longdesc lang="en"> +To disable SSL hostname verification set this option to 0. +To enable hostname verification, set this option to 1. +This option is actually part of the LWP Perl library. +See LWP(3pm) for more information. +</longdesc> +</parameter> +</parameters>} . "\n"; + } + else { dielog("Invalid command specified: $command\n"); } +} + +# Command belongs to the group of commands that require connecting to VMware vCenter +elsif ($command ~~ @netCommands) { + + eval { require VMware::VIRuntime; } + or dielog("Missing perl module VMware::VIRuntime. Download and install 'VMware Infrastructure (VI) Perl Toolkit', available at http://www.vmware.com/support/developer/viperltoolkit/ \n"); + + # A valid VI_CREDSTORE is required to avoid interactive prompt + ( exists $ENV{'VI_CREDSTORE'} ) || dielog("VI_CREDSTORE not specified\n"); + + # HOSTLIST is mandatory + exists $ENV{'HOSTLIST'} || dielog("HOSTLIST not specified\n"); + + # Parse HOSTLIST to %host_to_vm and %vm_to_host + my @hostlist = split(';', $ENV{'HOSTLIST'}); + my %host_to_vm = (); + my %vm_to_host = (); + foreach my $host (@hostlist) { + my @config = split(/=/, $host); + my $key = $config[0]; my $value = $config[1]; + if (!defined($value)) { $value = $config[0]; } + $host_to_vm{$key} = $value; + $vm_to_host{(lc $value)} = $key; + } + + eval { + # VI API: reads options from the environment variables into appropriate data structures for validation. + Opts::parse(); + # VI API: ensures that input values from environment variable are complete, consistent and valid. + Opts::validate(); + # VI API: establishes a session with the VirtualCenter Management Server or ESX Server Web service + Util::connect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } + + # Command belongs to the group of commands that performs actions on Virtual Machines + if ($command ~~ @actionCommands) { + + my $targetHost = $ARGV[1] || dielog("No target specified\n"); + + # Require that specified target host exists in the specified HOSTLIST + if (exists $host_to_vm{$targetHost}) { + + my $vm; + my $esx; + eval { + # VI API: searches the inventory tree for a VirtualMachine managed entity whose name matches + # the name of the virtual machine assigned to the target host in HOSTLIST + $vm = Vim::find_entity_view(view_type => "VirtualMachine", filter => { name => qr/^\Q$host_to_vm{$targetHost}\E/i }); + if (!defined $vm) { + dielog("Machine $targetHost was not found"); + } + + # VI API: retrieves the properties of the managed object reference runtime.host of the VirtualMachine + # managed entity obtained by the previous command + # NOTE: This is essentially a workaround to vSphere Perl SDK + # to allow pointing to the right HostSystem. This is probably + # done by changing the current HostSystem in the Web Service + # session context. WARNING: Do not use the same session for any + # other concurrent operation. + $esx = Vim::get_view(mo_ref => $vm->{"runtime"}{"host"})->name; + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + my $powerState = $vm->get_property('runtime.powerState')->val; + if ($powerState eq "suspended") { + # This implementation assumes that suspending a cluster node can cause + # severe failures on shared resources, thus any failover operation should + # be blocked. + dielog("Machine $esx:$vm->{'name'} is in a suspended state\n"); + } + + eval { + if ($command eq "reset") { + if ($powerState eq "poweredOn") { + $vm->ResetVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been reset"); + } else { + system("ha_log.sh", "warn", "Tried to ResetVM $esx:$vm->{'name'} that was $powerState"); + # Start a virtual machine on reset only if explicitly allowed by RESETPOWERON + if ($powerState eq "poweredOff" && (! exists $ENV{'RESETPOWERON'} || $ENV{'RESETPOWERON'} ne 0)) { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + dielog("Could not complete $esx:$vm->{'name'} power cycle"); + } + } + } + elsif ($command eq "off") { + if ($powerState eq "poweredOn") { + $vm->PowerOffVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered off"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOffVM $esx:$vm->{'name'} that was $powerState"); + + } + } + elsif ($command eq "on") { + if ($powerState eq "poweredOff") { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOnVM $esx:$vm->{'name'} that was $powerState"); + } + } + else { dielog("Invalid command specified: $command\n"); } + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + } else { dielog("Invalid target specified\n"); } + } else { + # Command belongs to the group of commands that lookup the status of VMware vCenter and/or virtual machines + if ($command eq "status") { + # we already connect to the vcenter, no need to do + # anything else in status + ; + } + elsif ($command eq "gethosts") { + foreach my $key (keys(%host_to_vm)) { + print "$key \n"; + } + } + elsif ($command eq "listvms") { + eval { + # VI API: Searches the inventory tree for all VirtualMachine managed objects + my $vms = Vim::find_entity_views(view_type => "VirtualMachine"); + if (defined $vms) { + printf(STDERR "%-50s %-20s\n", "VM Name", "Power state"); + print STDERR "-" x 70 . "\n"; + foreach my $vm (@$vms) { + my $powerState = $vm->get_property('runtime.powerState')->val; + printf("%-50s %-20s\n", $vm->{name}, $powerState); + } + } + }; + } + else { dielog("Invalid command specified: $command\n"); } + } + eval { + Util::disconnect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } +} +else { dielog("Invalid command specified: $command\n"); } + +exit(0); diff --git a/lib/plugins/stonith/external/vmware b/lib/plugins/stonith/external/vmware new file mode 100644 index 0000000..55966ba --- /dev/null +++ b/lib/plugins/stonith/external/vmware @@ -0,0 +1,216 @@ +#!/usr/bin/perl +# External STONITH module for VMWare Server Guests +# +# Copyright (c) 2004 SUSE LINUX AG - Andrew Beekhof <abeekhof@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +sub supply_default +{ + my $name = $_[0]; + my $value = $_[1]; + + if ( defined $ENV{$name} ) { + #print "Set: $name=$ENV{$name}\n"; + } else { + $ENV{$name} = $value; + #print "Default: $name=$ENV{$name}\n"; + } +} + +sub vmware_command +{ + my $config = $_[0]; + my $action = $_[1]; + my @lines; + + my $device = $ENV{'device_host'}; + + if ( $device =~ /localhost/ ) { + @lines = readpipe "vmware-cmd $config $action"; + + } else { + @lines = readpipe "ssh $device \"vmware-cmd \\\"$config\\\" $action\""; + } + + #print @lines; + return @lines; +} + +sub is_host_active +{ + my $config = config_for_host($_[0]); + my @lines = vmware_command($config, "getstate"); + foreach $line (@lines) { + if ( $line =~ /getstate.* = on/ ) { + return 1; + } + } + return 0; +} + +sub supported_hosts +{ + my $line; + my @lines; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + @config = split(/=/, $line); + $host = $config[0]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + + } else { + @lines = vmware_command("-l"); + foreach $line (@lines){ + my @elements = split(/\//, $line); + $host = $elements[$#elements-1]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + } +} + +sub config_for_host +{ + my $line; + my @lines; + my $target = $_[0]; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + if ( $line =~ /^$target=/ ) { + @config = split(/=/, $line); + return $config[1]; + } + } + + } else { + @lines = vmware_command("-l"); + + foreach $line (@lines){ + if ( $line =~ /\/$target\// ) { + chop($line); + return $line; + } + } + } +} + +$command = $ARGV[0]; +if ( defined $ARGV[1] ) { + $targetHost = $ARGV[1]; +} + +supply_default("device_host", "localhost"); + +if ( $command =~ /^gethosts$/ ) { + supported_hosts; + +} elsif ( $command =~ /^getconfignames$/ ) { + print "device_host\n"; + +} elsif ( $command =~ /^getinfo-devid$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devname$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devdescr$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devurl$/ ) { + print "http://www.vmware.com/"; + +} elsif ( $command =~ /^on$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "start hard"); + print @lines; + +} elsif ( $command =~ /^off$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "stop hard"); + print @lines; + +} elsif ( $command =~ /^reset$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "reset hard"); + print @lines; + +} elsif ( $command =~ /^status$/ ) { + my $rc = 7; + my $device = $ENV{'device_host'}; + if ( $device =~ /localhost/ ) { + $rc = 0; + # TODO: Check for the vmware process + print "Local version: always running\n"; + + } else { + print "Remote version: running ping\n"; + @lines = readpipe "ping -c1 $device"; + print @lines; + + foreach $line ( @lines ) { + if ( $line =~ /0% packet loss/ ) { + $rc = 0; + last; + } + } + } + exit($rc); + +} elsif ( $command =~ /^getinfo-xml$/ ) { + $metadata = <<END; +<parameters> +<parameter name="host_map" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Host Map +</shortdesc> +<longdesc lang="en"> +A mapping of hostnames to config paths supported by this device. +Eg. host1=/config/path/1;host2=/config/path/2;host3=/config/path/3; +</longdesc> +</parameter> +<parameter name="device_host" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Device Host +</shortdesc> +<longdesc lang="en"> +The machine _hosting_ the virtual machines +</longdesc> +</parameter> +</parameters> +END + +print $metadata; + +} else { + print "Command $command: not supported\n"; + exit(3); # Not implemented +} + + +exit(0); diff --git a/lib/plugins/stonith/external/xen0 b/lib/plugins/stonith/external/xen0 new file mode 100644 index 0000000..ef1ee40 --- /dev/null +++ b/lib/plugins/stonith/external/xen0 @@ -0,0 +1,253 @@ +#!/bin/sh +# +# External STONITH module for Xen Dom0 through ssh. +# +# Description: Uses Xen Dom0 Domain as a STONITH device +# to control DomUs. +# +# +# Author: Serge Dubrouski (sergeyfd@gmail.com) +# Inspired by Lars Marowsky-Bree's external/ssh agent. +# +# Copyright 2007 Serge Dubrouski <sergeyfd@gmail.com> +# License: GNU General Public License (GPL) +# + +STOP_COMMAND="xm destroy" +START_COMMAND="xm create" +DUMP_COMMAND="xm dump-core" +DEFAULT_XEN_DIR="/etc/xen" +SSH_COMMAND="/usr/bin/ssh -q -x -n" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +CheckIfDead() { + for j in 1 2 3 4 5 + do + if ! ping -w1 -c1 "$1" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + + return 1 +} + +CheckHostList() { + if [ "x" = "x$hostlist" ] + then + ha_log.sh err "hostlist isn't set" + exit 1 + fi +} + +CheckDom0() { + if [ "x" = "x$dom0" ] + then + ha_log.sh err "dom0 isn't set" + exit 1 + fi +} + +RunCommand() { + CheckHostList + CheckDom0 + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + if [ "x" = "x$node" ] + then + ha_log.sh err "Syntax error in host list" + exit 1 + fi + + if [ "x" = "x$cfg" ] + then + cfg="${DEFAULT_XEN_DIR}/${node}.cfg" + fi + + if [ "$node" != "$1" ] + then + continue + fi + + case $2 in + stop) + kill_node=`$SSH_COMMAND $dom0 "grep ^[[:space:]]*name $cfg" | cut -f 2 -d '=' | sed -e 's,",,g'` + if [ "x" = "x$kill_node" ] + then + ha_log.sh err "Couldn't find a node name to stop" + exit 1 + fi + + if [ "x$run_dump" != "x" ] + then + #Need to run core dump + if [ "x$dump_dir" != "x" ] + then + #Dump with the specified core file + TIMESTAMP=`date +%Y-%m%d-%H%M.%S` + DOMAINNAME=`printf "%s" $kill_node` + COREFILE=$dump_dir/$TIMESTAMP-$DOMAINNAME.core + $SSH_COMMAND $dom0 "(mkdir -p $dump_dir; $DUMP_COMMAND $kill_node $COREFILE) >/dev/null 2>&1" + else + $SSH_COMMAND $dom0 "$DUMP_COMMAND $kill_node >/dev/null 2>&1" + fi + fi + $SSH_COMMAND $dom0 "(sleep 2; $STOP_COMMAND $kill_node) >/dev/null 2>&1 &" + break;; + start) + $SSH_COMMAND $dom0 "(sleep 2; $START_COMMAND $cfg) >/dev/null 2>&1 &" + break;; + esac + exit 0 + done +} + + +# Main code + +case $1 in +gethosts) + CheckHostList + + for h in $hostlist ; do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + done + exit 0 + ;; +on) + RunCommand $2 start + exit $? + ;; +off) + if RunCommand $2 stop + then + if CheckIfDead $2 + then + exit 0 + fi + fi + + exit 1 + ;; +reset) + RunCommand $2 stop + + if CheckIfDead $2 + then + RunCommand $2 start + exit 0 + fi + + exit 1 + ;; +status) + CheckHostList + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + if ping -w1 -c1 "$node" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist dom0" + exit 0 + ;; +getinfo-devid) + echo "xen0 STONITH device" + exit 0 + ;; +getinfo-devname) + echo "xen0 STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset for Xen DomU trough Dom0" + echo "Fine for testing, but not really suitable for production!" + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org http://www.xensource.com/ http://linux-ha.org/wiki" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled nodes in a format node[:config_file]. +For example: "node1:/opt/xen/node1.cfg node2" +If config file isn't set it defaults to /etc/xen/{node_name}.cfg +</longdesc> +</parameter> +<parameter name="dom0" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 +</shortdesc> +<longdesc lang="en"> +Name of the Dom0 Xen node. Root user shall be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="run_dump" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core +</shortdesc> +<longdesc lang="en"> +If set plugin will call "xm dump-core" before killing DomU +</longdesc> +</parameter> +<parameter name="dump_dir" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core with the specified directory +</shortdesc> +<longdesc lang="en"> +This parameter can indicate the dump destination. +Should be set as a full path format, ex.) "/var/log/dump" +The above example would dump the core, like; +/var/log/dump/2009-0316-1403.37-domU.core +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper new file mode 100755 index 0000000..b313f8b --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper @@ -0,0 +1,72 @@ +#!/bin/bash +# Author: Lars Marowsky-Bree +# +# Copyright 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) + +# This is not an external/stonith plugin by itself, but instead a helper +# script which gets installed in Dom0. + +# TODO: +# - Error handling +# - How to handle if the DomU resource doesn't exist? +# - Does this truly work with split-brain? +# - Is the handling of non-existent resources adequate? +# ... +# Basically: more testing. This is proof-of-concept and works, but deserves +# validation. + +CMD="$1" +DOMU="$2" +TIMEOUT="$3" + +# Make sure the timeout is an integer: +if [ "0$TIMEOUT" -eq 0 ]; then + TIMEOUT=300 +fi + +SetTargetRole() { + local new_role="$1" + crm_resource -r $DOMU --meta -p target_role -v $new_role + + local timeout="$TIMEOUT" + + # We only need to wait for "stopped". + if [ "$new_role" != "stopped" ]; then + return 0 + fi + + while [ $timeout -gt 0 ]; do + local rc + crm_resource -W -r $DOMU 2>&1 | grep -q "is NOT running" + rc=$? + if [ $rc -eq 0 ]; then + return 0 + fi + timeout=$[timeout-1]; + sleep 1 + done + return 1 +} + + +case $CMD in +on) SetTargetRole started + exit $? + ;; +off) SetTargetRole stopped + exit $? + ;; +reset) SetTargetRole stopped || exit 1 + SetTargetRole started + exit $? + ;; +status) exit 0 + ;; +*) ha_log.sh err "Called with unknown command: $CMD" + exit 1 + ;; +esac + +exit 1 + diff --git a/lib/plugins/stonith/external/xen0-ha.in b/lib/plugins/stonith/external/xen0-ha.in new file mode 100755 index 0000000..cb42cbc --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha.in @@ -0,0 +1,96 @@ +#!/bin/bash +# +# This STONITH script integrates a cluster running within DomUs +# with the CRM/Pacemaker cluster running in Dom0. +# +# Author: Lars Marowsky-Bree +# Copyright: 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) +# + +SSH_COMMAND="@SSH@ -q -x -n" +HVM_HELPER="@stonith_plugindir@/xen0-ha-dom0-stonith-helper" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +# Runs a command on the host, waiting for it to return +RunHVMCommand() { + $SSH_COMMAND $dom0_cluster_ip "$HVM_HELPER $1 $2 $stop_timeout" +} + +# Main code +case $1 in +gethosts) + echo $hostlist + exit 0 + ;; +on|off|reset|status) + RunHVMCommand $1 $2 + exit $? + ;; +getconfignames) + echo "hostlist dom0_cluster_ip timeout" + exit 0 + ;; +getinfo-devid) + echo "xen0-ha DomU/Dom0 device" + exit 0 + ;; +getinfo-devname) + echo "xen0-ha DomU/Dom0 external device" + exit 0 + ;; +getinfo-devdescr) + echo "Allows STONITH to control DomUs managed by a CRM/Pacemaker Dom0." + echo "Requires Xen + CRM/Pacemaker at both layers." + echo "Proof-of-concept code!" + exit 0 + ;; +getinfo-devurl) + echo "http://linux-ha.org/wiki/DomUClusters" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled DomUs, separated by whitespace. +These must be configured as Xen RA resources with a name with a matching +id. +For example: "xen-1 xen-2 xen-3" +</longdesc> +</parameter> +<parameter name="dom0_cluster_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 cluster ip +</shortdesc> +<longdesc lang="en"> +The cluster IP address associated with Dom0. +Root user must be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="stop_timeout"> +<content type="integer" /> +<shortdesc lang="en"> +Stop timeout +</shortdesc> +<longdesc lang="en"> +The timeout, in seconds, for which to wait for Dom0 to report that the +DomU has been stopped, before aborting with a failure. +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/ibmhmc.c b/lib/plugins/stonith/ibmhmc.c new file mode 100644 index 0000000..d33fea9 --- /dev/null +++ b/lib/plugins/stonith/ibmhmc.c @@ -0,0 +1,1261 @@ +/* + * Stonith module for IBM Hardware Management Console (HMC) + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Support for HMC V4+ added by Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2004 International Business Machines + * + * 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 + * + */ + +/* + * + * This code has been tested in following environment: + * + * Hardware Management Console (HMC): Release 3, Version 2.4 + * - Both FullSystemPartition and LPAR Partition: + * - p630 7028-6C4 two LPAR partitions + * - p650 7038-6M2 one LPAR partition and FullSystemPartition + * + * Hardware Management Console (HMC): Version 4, Release 2.1 + * - OP720 1000-6CA three LPAR partitions + * + * Note: Only SSH access to the HMC devices are supported. + * + * This command would make a nice status command: + * + * lshmc -r -F ssh + * + * The following V3 command will get the list of systems we control and their + * mode: + * + * lssyscfg -r sys -F name:mode --all + * + * 0 indicates full system partition + * 255 indicates the system is partitioned + * + * The following V4 command will get the list of systems we control: + * + * lssyscfg -r sys -F name + * + * The following V3 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name --all + * + * Note that we should probably only consider partitions whose boot mode + * is normal (1). (that's my guess, anyway...) + * + * The following V4 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name + * + * The following V3 commands provide the reset/on/off actions: + * + * FULL SYSTEM: + * on: chsysstate -m %1 -r sys -o on -n %1 -c full + * off: chsysstate -m %1 -r sys -o off -n %1 -c full -b norm + * reset:chsysstate -m %1 -r sys -o reset -n %1 -c full -b norm + * + * Partitioned SYSTEM: + * on: chsysstate -m %1 -r lpar -o on -n %2 + * off: reset_partition -m %1 -p %2 -t hard + * reset:do off action above, followed by on action... + * + * where %1 is managed-system, %2 is-lpar name + * + * The following V4 commands provide the reset/on/off actions: + * + * on: chsysstate -m %1 -r lpar -o on -n %2 -f %3 + * off: chsysstate -m %1 -r lpar -o shutdown -n %2 --immed + * reset:chsysstate -m %1 -r lpar -o shutdown -n %2 --immed --restart + * + * where %1 is managed-system, %2 is lpar-name, %3 is profile-name + * + * Of course, to do all this, we need to track which partition name goes with + * which managed system's name, and which systems on the HMC are partitioned + * and which ones aren't... + */ + +#include <lha_internal.h> + +#define DEVICE "IBM HMC" + +#include "stonith_plugin_common.h" + +#ifndef SSH_CMD +# define SSH_CMD "ssh" +#endif +#ifndef HMCROOT +# define HMCROOT "hscroot" +#endif + +#define PIL_PLUGIN ibmhmc +#define PIL_PLUGIN_S "ibmhmc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define MAX_HOST_NAME_LEN (256*4) +#define MAX_CMD_LEN 2048 +#define FULLSYSTEMPARTITION "FullSystemPartition" +#define MAX_POWERON_RETRY 10 +#define MAX_HMC_NAME_LEN 256 + +#define ST_MANSYSPAT "managedsyspat" +#define NOPASS "nopass" + +#define STATE_UNKNOWN -1 +#define STATE_OFF 0 +#define STATE_ON 1 +#define STATE_INVALID 2 + +#define HMCURL "http://publib-b.boulder.ibm.com/redbooks.nsf/RedbookAbstracts"\ + "/SG247038.html" + +static StonithPlugin * ibmhmc_new(const char *); +static void ibmhmc_destroy(StonithPlugin *); +static const char * ibmhmc_getinfo(StonithPlugin * s, int InfoType); +static const char * const * ibmhmc_get_confignames(StonithPlugin* p); +static int ibmhmc_status(StonithPlugin * ); +static int ibmhmc_reset_req(StonithPlugin * s,int request,const char* host); +static char ** ibmhmc_hostlist(StonithPlugin *); +static int ibmhmc_set_config(StonithPlugin *, StonithNVpair*); + +static struct stonith_ops ibmhmcOps = { + ibmhmc_new, /* Create new STONITH object */ + ibmhmc_destroy, /* Destroy STONITH object */ + ibmhmc_getinfo, /* Return STONITH info string */ + ibmhmc_get_confignames, /* Return configuration parameters */ + ibmhmc_set_config, /* Set configuration */ + ibmhmc_status, /* Return STONITH device status */ + ibmhmc_reset_req, /* Request a reset */ + ibmhmc_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 + , &ibmhmcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * hmc; + GList* hostlist; + int hmcver; + char * password; + char ** mansyspats; +}; + +static const char * pluginid = "HMCDevice-Stonith"; +static const char * NOTpluginID = "IBM HMC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_MANSYSPAT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MANSYSPAT \ + XML_PARM_SHORTDESC_END + +#define XML_MANSYSPAT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "White-space delimited list of patterns used to match managed system names; if last character is '*', all names that begin with the pattern are matched" \ + XML_PARM_LONGDESC_END + +#define XML_MANSYSPAT_PARM \ + XML_PARAMETER_BEGIN(ST_MANSYSPAT, "string", "0", "0") \ + XML_MANSYSPAT_SHORTDESC \ + XML_MANSYSPAT_LONGDESC \ + XML_PARAMETER_END + +#define XML_OPTPASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Password for " HMCROOT " if passwordless ssh access to HMC has NOT been setup (to do so, it is necessary to create a public/private key pair with empty passphrase - see \"Configure the OpenSSH Client\" in the redbook at " HMCURL " for more details)" \ + XML_PARM_LONGDESC_END + +#define XML_OPTPASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "0", "0") \ + XML_PASSWD_SHORTDESC \ + XML_OPTPASSWD_LONGDESC \ + XML_PARAMETER_END + +static const char *ibmhmcXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_MANSYSPAT_PARM + XML_OPTPASSWD_PARM + XML_PARAMETERS_END; + +static int get_hmc_hostlist(struct pluginDevice* dev); +static void free_hmc_hostlist(struct pluginDevice* dev); +static int get_hmc_mansyspats(struct pluginDevice* dev, const char* mansyspats); +static void free_hmc_mansyspats(struct pluginDevice* dev); +static char* do_shell_cmd(const char* cmd, int* status, const char* password); +static int check_hmc_status(struct pluginDevice* dev); +static int get_num_tokens(char *str); +static gboolean pattern_match(char **patterns, char *string); +/* static char* do_shell_cmd_fake(const char* cmd, int* status); */ + +static int +ibmhmc_status(StonithPlugin *s) +{ + struct pluginDevice* dev = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + dev = (struct pluginDevice*) s; + + return check_hmc_status(dev); +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +ibmhmc_hostlist(StonithPlugin *s) +{ + int j; + struct pluginDevice* dev; + int numnames = 0; + char** ret = NULL; + GList* node = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice*) s; + + /* refresh the hostlist */ + free_hmc_hostlist(dev); + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return NULL; + } + + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "unconfigured stonith object in %s" + , __FUNCTION__); + return(NULL); + } + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + + memset(ret, 0, (numnames+1)*sizeof(char*)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + char* host = strchr((char*)node->data, '/'); + ret[j] = STRDUP(++host); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "out of memory"); + stonith_free_hostlist(ret); + return NULL; + } + strdown(ret[j]); + } + return ret; +} + + +static const char * const * +ibmhmc_get_confignames(StonithPlugin* p) +{ + static const char * names[] = {ST_IPADDR, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + + +/* + * Reset the given host, and obey the request type. + * We should reset without power cycle for the non-partitioned case + */ + +static int +ibmhmc_reset_req(StonithPlugin * s, int request, const char * host) +{ + GList* node = NULL; + struct pluginDevice* dev = NULL; + char off_cmd[MAX_CMD_LEN]; + char on_cmd[MAX_CMD_LEN]; + char reset_cmd[MAX_CMD_LEN]; + gchar** names = NULL; + int i; + int is_lpar = FALSE; + int status; + char* pch; + char* output = NULL; + char state_cmd[MAX_CMD_LEN]; + int state = STATE_UNKNOWN; + + status = 0; + if(Debug){ + LOG(PIL_DEBUG, "%s: called, host=%s\n", __FUNCTION__, host); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (NULL == host) { + LOG(PIL_CRIT, "invalid argument to %s", __FUNCTION__); + return(S_OOPS); + } + + dev = (struct pluginDevice*) s; + + for (node = g_list_first(dev->hostlist) + ; NULL != node + ; node = g_list_next(node)) { + if(Debug){ + LOG(PIL_DEBUG, "%s: node->data=%s\n" + , __FUNCTION__, (char*)node->data); + } + + if ((pch = strchr((char*)node->data, '/')) != NULL + && 0 == strcasecmp(++pch, host)) { + break; + } + } + + if (!node) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module. " + "Please check your configuration information.", host); + return (S_OOPS); + } + + names = g_strsplit((char*)node->data, "/", 2); + /* names[0] will be the name of managed system */ + /* names[1] will be the name of the lpar partition */ + if(Debug){ + LOG(PIL_DEBUG, "%s: names[0]=%s, names[1]=%s\n" + , __FUNCTION__, names[0], names[1]); + } + + if (dev->hmcver < 4) { + if (0 == strcasecmp(names[1], FULLSYSTEMPARTITION)) { + is_lpar = FALSE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o off -n %s -c full" + , dev->hmc, dev->hmc, names[0]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o on -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o reset -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + *state_cmd = 0; + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s reset_partition" + " -m %s -p %s -t hard" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r lpar -m %s -o on -n %s" + , dev->hmc, names[0], names[1]); + + *reset_cmd = 0; + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -r lpar -m %s -F state -n %s" + , dev->hmc, names[0], names[1]); + } + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n \"%s\" --immed" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F \"default_profile\"" + " --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return (S_OOPS); + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o on -n %s -f %s" + , dev->hmc, names[0], names[1], output); + FREE(output); + output = NULL; + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n %s --immed --restart" + , dev->hmc, names[0], names[1]); + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F state --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + } + g_strfreev(names); + + if(Debug){ + LOG(PIL_DEBUG, "%s: off_cmd=%s, on_cmd=%s," + "reset_cmd=%s, state_cmd=%s\n" + , __FUNCTION__, off_cmd, on_cmd, reset_cmd, state_cmd); + } + + output = do_shell_cmd(state_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (strcmp(output, "Running") == 0 + || strcmp(output, "Starting") == 0 + || strcmp(output, "Open Firmware") == 0) { + state = STATE_ON; + }else if (strcmp(output, "Shutting Down") == 0 + || strcmp(output, "Not Activated") == 0 + || strcmp(output, "Ready") == 0) { + state = STATE_OFF; + }else if (strcmp(output, "Not Available") == 0 + || strcmp(output, "Error") == 0) { + state = STATE_INVALID; + } + FREE(output); + output = NULL; + + if (state == STATE_INVALID) { + LOG(PIL_CRIT, "host %s in invalid state", host); + return S_OOPS; + } + + switch (request) { + case ST_POWERON: + if (state == STATE_ON) { + LOG(PIL_INFO, "host %s already on", host); + return S_OK; + } + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + break; + case ST_POWEROFF: + if (state == STATE_OFF) { + LOG(PIL_INFO, "host %s already off", host); + return S_OK; + } + + output = do_shell_cmd(off_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", off_cmd); + return S_OOPS; + } + break; + case ST_GENERIC_RESET: + if (dev->hmcver < 4) { + if (is_lpar) { + if (state == STATE_ON) { + output = do_shell_cmd(off_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s " + "failed", off_cmd); + return S_OOPS; + } + } + for (i = 0; i < MAX_POWERON_RETRY; i++) { + char *output2; + output2 = do_shell_cmd(on_cmd + , &status, dev->password); + if (output2 != NULL) { + FREE(output2); + } + if (0 != status) { + sleep(1); + }else{ + break; + } + } + if (MAX_POWERON_RETRY == i) { + LOG(PIL_CRIT, "command %s failed" + , on_cmd); + return S_OOPS; + } + }else{ + output = do_shell_cmd(reset_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed" , reset_cmd); + return S_OOPS; + } + break; + } + }else{ + if (state == STATE_ON) { + output = do_shell_cmd(reset_cmd + , &status, dev->password); + }else{ + output = do_shell_cmd(on_cmd + , &status, dev->password); + } + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", reset_cmd); + return S_OOPS; + } + } + break; + default: + return S_INVAL; + } + + if (output != NULL) { + FREE(output); + } + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + + return S_OK; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +ibmhmc_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice* dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {NULL, NULL} + }; + int rc; + char get_hmcver[MAX_CMD_LEN]; + char firstchar; + int firstnum; + char* output = NULL; + int status; + const char *mansyspats; + int len; + + ERRIFWRONGDEV(s,S_OOPS); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + dev = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + if(Debug){ + LOG(PIL_DEBUG, "%s: ipaddr=%s\n", __FUNCTION__ + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for managedsyspat */ + mansyspats = OurImports->GetValue(list, ST_MANSYSPAT); + if (mansyspats != NULL) { + if (get_hmc_mansyspats(dev, mansyspats) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + /* look for password */ + dev->password = STRDUP(OurImports->GetValue(list, ST_PASSWD)); + dev->hmc = namestocopy[0].s_value; + }else{ + /* -p or -F option with args "ipaddr [managedsyspat]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over ipaddr and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (get_hmc_mansyspats(dev, pch) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + + dev->hmc = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + } + + /* check whether the HMC has ssh command enabled */ + if (check_hmc_status(dev) != S_OK) { + LOG(PIL_CRIT, "HMC %s does not have remote " + "command execution using the ssh facility enabled", dev->hmc); + return S_BADCONFIG; + } + + /* get the HMC's version info */ + snprintf(get_hmcver, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -v | grep RM", dev->hmc); + if (Debug) { + LOG(PIL_DEBUG, "%s: get_hmcver=%s", __FUNCTION__, get_hmcver); + } + + output = do_shell_cmd(get_hmcver, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: output=%s\n", __FUNCTION__ + , output ? output : "(nil)"); + } + if (output == NULL) { + return S_BADCONFIG; + } + + /* parse the HMC's version info (i.e. "*RM V4R2.1" or "*RM R3V2.6") */ + if ((sscanf(output, "*RM %c%1d", &firstchar, &firstnum) == 2) + && ((firstchar == 'V') || (firstchar == 'R'))) { + dev->hmcver = firstnum; + if(Debug){ + LOG(PIL_DEBUG, "%s: HMC %s version is %d" + , __FUNCTION__, dev->hmc, dev->hmcver); + } + }else{ + LOG(PIL_CRIT, "%s: unable to determine HMC %s version" + , __FUNCTION__, dev->hmc); + FREE(output); + return S_BADCONFIG; + } + + len = strlen(output+4) + sizeof(DEVICE) + 1; + if (dev->idinfo != NULL) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + dev->idinfo = MALLOC(len * sizeof(char)); + if (dev->idinfo == NULL) { + LOG(PIL_CRIT, "out of memory"); + FREE(output); + return S_OOPS; + } + snprintf(dev->idinfo, len, "%s %s", DEVICE, output+4); + FREE(output); + + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + return S_OK; +} + + +static const char* +ibmhmc_getinfo(StonithPlugin* s, int reqtype) +{ + struct pluginDevice* dev; + const char* ret; + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->hmc; + break; + + case ST_DEVICEDESCR: + ret = "IBM Hardware Management Console (HMC)\n" + "Use for IBM i5, p5, pSeries and OpenPower systems " + "managed by HMC\n" + " Optional parameter name " ST_MANSYSPAT " is " + "white-space delimited list of\n" + "patterns used to match managed system names; if last " + "character is '*',\n" + "all names that begin with the pattern are matched\n" + " Optional parameter name " ST_PASSWD " is password " + "for " HMCROOT " if passwordless\n" + "ssh access to HMC has NOT been setup (to do so, it " + "is necessary to create\n" + "a public/private key pair with empty passphrase - " + "see \"Configure the\n" + "OpenSSH client\" in the redbook for more details)"; + break; + + case ST_DEVICEURL: + ret = HMCURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ibmhmcXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +ibmhmc_destroy(StonithPlugin *s) +{ + struct pluginDevice* dev; + + if(Debug){ + LOG(PIL_DEBUG, "%s : called\n", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->hmc) { + FREE(dev->hmc); + dev->hmc = NULL; + } + if (dev->password) { + FREE(dev->password); + dev->password = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_hmc_hostlist(dev); + free_hmc_mansyspats(dev); + + FREE(dev); +} + + +static StonithPlugin * +ibmhmc_new(const char *subplugin) +{ + struct pluginDevice* dev = ST_MALLOCT(struct pluginDevice); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return(NULL); + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->hmc = NULL; + dev->password = NULL; + dev->hostlist = NULL; + dev->mansyspats = NULL; + dev->hmcver = -1; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return(NULL); + } + dev->sp.s_ops = &ibmhmcOps; + + if(Debug){ + LOG(PIL_DEBUG, "%s: returning successfully\n", __FUNCTION__); + } + + return((void *)dev); +} + +static int +get_hmc_hostlist(struct pluginDevice* dev) +{ + int i, j, status; + char* output = NULL; + char get_syslist[MAX_CMD_LEN]; + char host[MAX_HOST_NAME_LEN]; + gchar** syslist = NULL; + gchar** name_mode = NULL; + char get_lpar[MAX_CMD_LEN]; + gchar** lparlist = NULL; + char* pch; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, dev->hmc=%s\n", __FUNCTION__ + , dev->hmc); + } + + if (dev->hmc == NULL || *dev->hmc == 0){ + return S_BADCONFIG; + } + + /* get the managed system's names of the hmc */ + if (dev->hmcver < 4) { + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD " -l " HMCROOT + " %s lssyscfg -r sys -F name:mode --all", dev->hmc); + }else{ + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD + " -l " HMCROOT " %s lssyscfg -r sys -F name", dev->hmc); + } + if(Debug){ + LOG(PIL_DEBUG, "%s: get_syslist=%s", __FUNCTION__, get_syslist); + } + + output = do_shell_cmd(get_syslist, &status, dev->password); + if (output == NULL) { + return S_BADCONFIG; + } + syslist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each managed system */ + for (i = 0; syslist[i] != NULL && syslist[i][0] != 0; i++) { + if (dev->hmcver < 4) { + name_mode = g_strsplit(syslist[i], ":", 2); + if(Debug){ + LOG(PIL_DEBUG, "%s: name_mode0=%s, name_mode1=%s\n" + , __FUNCTION__, name_mode[0], name_mode[1]); + } + + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, name_mode[0])) { + continue; + } + + /* if it is in fullsystempartition */ + if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "0", 1)) { + /* add the FullSystemPartition */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/FullSystemPartition", name_mode[0]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + }else if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "255", 3)){ + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name --all" + , dev->hmc, name_mode[0]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar + , &status, dev->password); + if (output == NULL) { + g_strfreev(name_mode); + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* skip the full system partition */ + if (0 == strncmp(lparlist[j] + , FULLSYSTEMPARTITION + , strlen(FULLSYSTEMPARTITION))) { + continue; + } + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", name_mode[0] + , lparlist[j]); + dev->hostlist = + g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + g_strfreev(name_mode); + }else{ + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, syslist[i])) { + continue; + } + + /* get its state */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r sys -F state" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (!strcmp(output, "No Connection")){ + FREE(output); + continue; + } + FREE(output); + + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", syslist[i],lparlist[j]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + } + g_strfreev(syslist); + + return S_OK; +} + +static void +free_hmc_hostlist(struct pluginDevice* dev) +{ + if (dev->hostlist) { + GList* node; + while (NULL != (node=g_list_first(dev->hostlist))) { + dev->hostlist = g_list_remove_link(dev->hostlist, node); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } +} + +static int +get_hmc_mansyspats(struct pluginDevice * dev, const char *mansyspats) +{ + char *patscopy; + int numpats; + int i; + char *tmp; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, mansyspats=%s\n" + , __FUNCTION__, mansyspats); + } + + patscopy = STRDUP(mansyspats); + if (patscopy == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return S_OOPS; + } + + numpats = get_num_tokens(patscopy); + if (numpats > 0) { + dev->mansyspats = MALLOC((numpats+1)*sizeof(char *)); + if (dev->mansyspats == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + FREE(patscopy); + return S_OOPS; + } + + memset(dev->mansyspats, 0, (numpats+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(patscopy, WHITESPACE); + while (tmp != NULL) { + dev->mansyspats[i] = STRDUP(tmp); + if (dev->mansyspats[i] == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + free_hmc_mansyspats(dev); + dev->mansyspats = NULL; + FREE(patscopy); + return S_OOPS; + } + + if(Debug){ + LOG(PIL_DEBUG, "%s: adding pattern %s\n" + , __FUNCTION__, dev->mansyspats[i]); + } + + /* no patterns necessary if all specified */ + if (strcmp(dev->mansyspats[i], "*") == 0) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + break; + } + + i++; + tmp = strtok(NULL, WHITESPACE); + } + } + FREE(patscopy); + return S_OK; +} + +static void +free_hmc_mansyspats(struct pluginDevice* dev) +{ + if (dev->mansyspats) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + } +} + +static char* +do_shell_cmd(const char* cmd, int* status, const char* password) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char cmd_password[MAX_CMD_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + + FILE* file; + if (NULL == password) { + file = popen(cmd, "r"); + } else { + snprintf(cmd_password, MAX_CMD_LEN + ,"umask 077;" + "if [ ! -d " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc ];" + "then mkdir " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc 2>/dev/null;" + "fi;" + "export ibmhmc_tmp=`mktemp -p " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc/`;" + "echo \"echo '%s'\">$ibmhmc_tmp;" + "chmod +x $ibmhmc_tmp;" + "unset SSH_AGENT_SOCK SSH_AGENT_PID;" + "SSH_ASKPASS=$ibmhmc_tmp DISPLAY=ibmhmc_foo setsid %s;" + "rm $ibmhmc_tmp -f;" + "unset ibmhmc_tmp" + ,password, cmd); + file = popen(cmd_password, "r"); + } + if (NULL == file) { + return NULL; + } + + 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 != NULL) { + 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); + *status = pclose(file); + return data; +} + +static int +check_hmc_status(struct pluginDevice* dev) +{ + int status; + char check_status[MAX_CMD_LEN]; + char* output = NULL; + int rc = S_OK; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, hmc=%s\n", __FUNCTION__, dev->hmc); + } + + snprintf(check_status, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -r -F ssh", dev->hmc); + if(Debug){ + LOG(PIL_DEBUG, "%s: check_status %s\n", __FUNCTION__ + , check_status); + } + + output = do_shell_cmd(check_status, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: status=%d, output=%s\n", __FUNCTION__ + , status, output ? output : "(nil)"); + } + + if (NULL == output || strncmp(output, "enable", 6) != 0) { + rc = S_BADCONFIG; + } + if (NULL != output) { + FREE(output); + } + 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 gboolean +pattern_match(char **patterns, char *string) +{ + char **pattern; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, string=%s\n", __FUNCTION__, string); + } + + for (pattern = patterns; *pattern; pattern++) { + int patlen = strlen(*pattern); + + if (pattern[0][patlen-1] == '*') { + /* prefix match */ + if (strncmp(string, *pattern, patlen-1) == 0) { + return TRUE; + } + }else{ + /* exact match */ + if (strcmp(string, *pattern) == 0) { + return TRUE; + } + } + } + + return FALSE; +} + +/* +static char* +do_shell_cmd_fake(const char* cmd, int* status) +{ + printf("%s()\n", __FUNCTION__); + printf("cmd:%s\n", cmd); + *status=0; + return NULL; +} +*/ diff --git a/lib/plugins/stonith/ipmi_os_handler.c b/lib/plugins/stonith/ipmi_os_handler.c new file mode 100644 index 0000000..bdb6d6e --- /dev/null +++ b/lib/plugins/stonith/ipmi_os_handler.c @@ -0,0 +1,257 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.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 <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <OpenIPMI/os_handler.h> +#include <OpenIPMI/selector.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + + +#include <OpenIPMI/ipmi_int.h> + +#include <time.h> + +extern selector_t *os_sel; + +#if 0 +static void check_no_locks(os_handler_t *handler); +#define CHECK_NO_LOCKS(handler) check_no_locks(handler) +#else +#define CHECK_NO_LOCKS(handler) do {} while(0) +#endif + +struct os_hnd_fd_id_s +{ + int fd; + void *cb_data; + os_data_ready_t data_ready; + os_handler_t *handler; +}; + +static void +fd_handler(int fd, void *data) +{ + + os_hnd_fd_id_t *fd_data = (os_hnd_fd_id_t *) data; + + CHECK_NO_LOCKS(fd_data->handler); + fd_data->data_ready(fd, fd_data->cb_data, fd_data); + CHECK_NO_LOCKS(fd_data->handler); +} + +static int +add_fd(os_handler_t *handler, + int fd, + os_data_ready_t data_ready, + void *cb_data, + os_hnd_fd_id_t **id) +{ + os_hnd_fd_id_t *fd_data; + + fd_data = ipmi_mem_alloc(sizeof(*fd_data)); + if (!fd_data) + return ENOMEM; + + fd_data->fd = fd; + fd_data->cb_data = cb_data; + fd_data->data_ready = data_ready; + fd_data->handler = handler; + sel_set_fd_handlers(os_sel, fd, fd_data, fd_handler, NULL, NULL, NULL); + sel_set_fd_read_handler(os_sel, fd, SEL_FD_HANDLER_ENABLED); + sel_set_fd_write_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + sel_set_fd_except_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + + *id = fd_data; + return 0; +} + +static int +remove_fd(os_handler_t *handler, os_hnd_fd_id_t *fd_data) +{ + sel_clear_fd_handlers(os_sel, fd_data->fd); + sel_set_fd_read_handler(os_sel, fd_data->fd, SEL_FD_HANDLER_DISABLED); + ipmi_mem_free(fd_data); + return 0; +} + +struct os_hnd_timer_id_s +{ + void *cb_data; + os_timed_out_t timed_out; + sel_timer_t *timer; + int running; + os_handler_t *handler; +}; + +static void +timer_handler(selector_t *sel, + sel_timer_t *timer, + void *data) +{ + os_hnd_timer_id_t *timer_data = (os_hnd_timer_id_t *) data; + void *cb_data; + os_timed_out_t timed_out; + + CHECK_NO_LOCKS(timer_data->handler); + timed_out = timer_data->timed_out; + cb_data = timer_data->cb_data; + timer_data->running = 0; + timed_out(cb_data, timer_data); + CHECK_NO_LOCKS(timer_data->handler); +} + +static int +start_timer(os_handler_t *handler, + os_hnd_timer_id_t *id, + struct timeval *timeout, + os_timed_out_t timed_out, + void *cb_data) +{ + struct timeval now; + + if (id->running) + return EBUSY; + + id->running = 1; + id->cb_data = cb_data; + id->timed_out = timed_out; + + gettimeofday(&now, NULL); + now.tv_sec += timeout->tv_sec; + now.tv_usec += timeout->tv_usec; + while (now.tv_usec >= 1000000) { + now.tv_usec -= 1000000; + now.tv_sec += 1; + } + + return sel_start_timer(id->timer, &now); +} + +static int +stop_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + return sel_stop_timer(timer_data->timer); +} + +static int +alloc_timer(os_handler_t *handler, + os_hnd_timer_id_t **id) +{ + os_hnd_timer_id_t *timer_data; + int rv; + + timer_data = ipmi_mem_alloc(sizeof(*timer_data)); + if (!timer_data) + return ENOMEM; + + timer_data->running = 0; + timer_data->timed_out = NULL; + timer_data->handler = handler; + + rv = sel_alloc_timer(os_sel, timer_handler, timer_data, + &(timer_data->timer)); + if (rv) { + ipmi_mem_free(timer_data); + return rv; + } + + *id = timer_data; + return 0; +} + +static int +free_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + sel_free_timer(timer_data->timer); + ipmi_mem_free(timer_data); + return 0; +} + +static int +get_random(os_handler_t *handler, void *data, unsigned int len) +{ + int fd = open("/dev/urandom", O_RDONLY); + int rv; + + if (fd == -1) + return errno; + + rv = read(fd, data, len); + + close(fd); + return rv; +} + +static void +sui_log(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + ...) +{ + return; +} + +static void +sui_vlog(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + va_list ap) +{ + return; +} + + +os_handler_t ipmi_os_cb_handlers = +{ + .add_fd_to_wait_for = add_fd, + .remove_fd_to_wait_for = remove_fd, + + .start_timer = start_timer, + .stop_timer = stop_timer, + .alloc_timer = alloc_timer, + .free_timer = free_timer, + + .create_lock = NULL, + .destroy_lock = NULL, + .is_locked = NULL, + .lock = NULL, + .unlock = NULL, + .create_rwlock = NULL, + .destroy_rwlock = NULL, + .read_lock = NULL, + .write_lock = NULL, + .read_unlock = NULL, + .write_unlock = NULL, + .is_readlocked = NULL, + .is_writelocked = NULL, + + .get_random = get_random, + + .log = sui_log, + .vlog = sui_vlog +}; + + diff --git a/lib/plugins/stonith/ipmilan.c b/lib/plugins/stonith/ipmilan.c new file mode 100644 index 0000000..1efdfee --- /dev/null +++ b/lib/plugins/stonith/ipmilan.c @@ -0,0 +1,587 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.com> + * + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005. + * And passed the compiling with OpenIPMI-1.4.8. + * + * 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 + * + */ + + +/* + * See README.ipmi for information regarding this plugin. + * + */ + +#define DEVICE "IPMI Over LAN" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ipmilan +#define PIL_PLUGIN_S "ipmilan" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <OpenIPMI/ipmi_types.h> +#include <OpenIPMI/ipmi_auth.h> + +#include "ipmilan.h" + +static StonithPlugin * ipmilan_new(const char *); +static void ipmilan_destroy(StonithPlugin *); +static const char * const * ipmilan_get_confignames(StonithPlugin *); +static int ipmilan_set_config(StonithPlugin *, StonithNVpair *); +static const char * ipmilan_getinfo(StonithPlugin * s, int InfoType); +static int ipmilan_status(StonithPlugin * ); +static int ipmilan_reset_req(StonithPlugin * s, int request, const char * host); +static char ** ipmilan_hostlist(StonithPlugin *); + +static struct stonith_ops ipmilanOps ={ + ipmilan_new, /* Create new STONITH object */ + ipmilan_destroy, /* Destroy STONITH object */ + ipmilan_getinfo, /* Return STONITH info string */ + ipmilan_get_confignames,/* Get configuration parameter names */ + ipmilan_set_config, /* Set configuration */ + ipmilan_status, /* Return STONITH device status */ + ipmilan_reset_req, /* Request a reset */ + ipmilan_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +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 + , &ipmilanOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * ipmilan STONITH device. + * + * ipmilanHostInfo is a double linked list. Where the prev of the head always + * points to the tail. This is a little wierd. But it saves me from looping + * around to find the tail when destroying the list. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + int hostcount; + struct ipmilanHostInfo * hostlist; +}; + +static const char * pluginid = "IPMI-LANDevice-Stonith"; +static const char * NOTpluginid = "IPMI-LAN device has been destroyed"; + +#define ST_HOSTNAME "hostname" +#define ST_PORT "port" +#define ST_AUTH "auth" +#define ST_PRIV "priv" +#define ST_RESET_METHOD "reset_method" + +#include "stonith_config_xml.h" + +#define XML_HOSTNAME_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOSTNAME \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTNAME_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOSTNAME_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTNAME, "string", "1", "1") \ + XML_HOSTNAME_SHORTDESC \ + XML_HOSTNAME_LONGDESC \ + XML_PARAMETER_END + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number to where the IPMI message is sent" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_AUTH_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_AUTH \ + XML_PARM_SHORTDESC_END + +#define XML_AUTH_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The authorization type of the IPMI session (\"none\", \"straight\", \"md2\", or \"md5\")" \ + XML_PARM_LONGDESC_END + +#define XML_AUTH_PARM \ + XML_PARAMETER_BEGIN(ST_AUTH, "string", "1", "0") \ + XML_AUTH_SHORTDESC \ + XML_AUTH_LONGDESC \ + XML_PARAMETER_END + +#define XML_PRIV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PRIV \ + XML_PARM_SHORTDESC_END + +#define XML_PRIV_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The privilege level of the user (\"operator\" or \"admin\")" \ + XML_PARM_LONGDESC_END + +#define XML_PRIV_PARM \ + XML_PARAMETER_BEGIN(ST_PRIV, "string", "1", "0") \ + XML_PRIV_SHORTDESC \ + XML_PRIV_LONGDESC \ + XML_PARAMETER_END + +#define XML_RESET_METHOD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_RESET_METHOD \ + XML_PARM_SHORTDESC_END + +#define XML_RESET_METHOD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "How to reset the host (\"power_cycle\" or \"hard_reset\")" \ + XML_PARM_LONGDESC_END + +#define XML_RESET_METHOD_PARM \ + XML_PARAMETER_BEGIN(ST_RESET_METHOD, "string", "0", "0") \ + XML_RESET_METHOD_SHORTDESC \ + XML_RESET_METHOD_LONGDESC \ + XML_PARAMETER_END + +static const char *ipmilanXML = + XML_PARAMETERS_BEGIN + XML_HOSTNAME_PARM + XML_IPADDR_PARM + XML_PORT_PARM + XML_AUTH_PARM + XML_PRIV_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* + * Check the status of the IPMI Lan STONITH device. + * + * NOTE: not sure what we should do here since each host is configured + * seperately. + * + * Two options: + * 1) always return S_OK. + * 2) using IPMI ping to confirm the status for every host that's + * configured. + * + * For now I choose the option 1 hoping that I can get by. Maybe we should + * change it to option 2 later. + */ + +static int +ipmilan_status(StonithPlugin *s) +{ + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int ret, rv; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + ret = S_OK; + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + rv = do_ipmi_cmd(node, ST_IPMI_STATUS); + if (rv) { + LOG(PIL_INFO, "Host %s ipmilan status failure." + , node->hostname); + ret = S_ACCESS; + } else { + LOG(PIL_INFO, "Host %s ipmilan status OK." + , node->hostname); + } + + } + + return ret; +} + +/* + * This function returns the list of hosts that's configured. + * + * The detailed configuration is disabled because the STONITH command can be + * run by anyone so there is a security risk if that to be exposed. + */ + +static char * +get_config_string(struct pluginDevice * nd, int index) +{ + struct ipmilanHostInfo * host; + int i; + + if (index >= nd->hostcount || index < 0) { + return (NULL); + } + + host = nd->hostlist; + for (i = 0; i < index; i++) { + host = host->next; + } + + return STRDUP(host->hostname); +} + + +/* + * Return the list of hosts configured for this ipmilan device + * + */ + +static char ** +ipmilan_hostlist(StonithPlugin *s) +{ + int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nd; + int j; + + ERRIFWRONGDEV(s,NULL); + + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in ipmi_hostlist"); + return(NULL); + } + numnames = nd->hostcount; + + ret = (char **)MALLOC((numnames + 1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (ret); + } + + memset(ret, 0, (numnames + 1)*sizeof(char*)); + + for (j = 0; j < numnames; ++j) { + ret[j] = get_config_string(nd, j); + if (!ret[j]) { + stonith_free_hostlist(ret); + ret = NULL; + break; + } + strdown(ret[j]); + } + + return(ret); +} + +/* + * Parse the config information, and stash it away... + * + * The buffer for each string is MAX_IPMI_STRING_LEN bytes long. + * Right now it is set to 64. Hope this is enough. + * + */ + +#define MAX_IPMI_STRING_LEN 64 + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +ipmilan_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + if (strcasecmp(node->hostname, host) == 0) { + break; + } + } + + if (i >= nd->hostcount) { + LOG(PIL_CRIT, "Host %s is not configured in this STONITH " + " module. Please check your configuration file.", host); + return (S_OOPS); + } + + rc = do_ipmi_cmd(node, request); + if (!rc) { + LOG(PIL_INFO, "Host %s ipmilan-reset.", host); + } else { + LOG(PIL_INFO, "Host %s ipmilan-reset error. Error = %d." + , host, rc); + } + return rc; +} + +/* + * Get configuration parameter names + */ +static const char * const * +ipmilan_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = + { ST_HOSTNAME, ST_IPADDR, ST_PORT, ST_AUTH, + ST_PRIV, ST_LOGIN, ST_PASSWD, ST_RESET_METHOD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +ipmilan_set_config(StonithPlugin* s, StonithNVpair * list) +{ + struct pluginDevice* nd; + int rc; + struct ipmilanHostInfo * tmp; + const char *reset_opt; + + StonithNamesToGet namestocopy [] = + { {ST_HOSTNAME, NULL} + , {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_AUTH, NULL} + , {ST_PRIV, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice *)s; + + ERRIFWRONGDEV(s, S_OOPS); + if (nd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + tmp = ST_MALLOCT(struct ipmilanHostInfo); + tmp->hostname = namestocopy[0].s_value; + tmp->ipaddr = namestocopy[1].s_value; + tmp->portnumber = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + if (namestocopy[3].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan auth type is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[3].s_value, "none") == 0) { + tmp->authtype = 0; + } else if (strcmp(namestocopy[3].s_value, "md2") == 0) { + tmp->authtype = 1; + } else if (strcmp(namestocopy[3].s_value, "md5") == 0) { + tmp->authtype = 2; + } else if (strcmp(namestocopy[3].s_value, "key") == 0 || + strcmp(namestocopy[3].s_value, "password") == 0 || + strcmp(namestocopy[3].s_value, "straight") == 0) { + tmp->authtype = 4; + } else { + LOG(PIL_CRIT, "ipmilan auth type '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[3].s_value); + return S_OOPS; + } + FREE(namestocopy[3].s_value); + if (namestocopy[4].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan priv value is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[4].s_value, "operator") == 0) { + tmp->privilege = 3; + } else if (strcmp(namestocopy[4].s_value, "admin") == 0) { + tmp->privilege = 4; + } else { + LOG(PIL_CRIT, "ipmilan priv value '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[4].s_value); + return(S_OOPS); + } + FREE(namestocopy[4].s_value); + tmp->username = namestocopy[5].s_value; + tmp->password = namestocopy[6].s_value; + reset_opt = OurImports->GetValue(list, ST_RESET_METHOD); + if (!reset_opt || !strcmp(reset_opt, "power_cycle")) { + tmp->reset_method = 0; + } else if (!strcmp(reset_opt, "hard_reset")) { + tmp->reset_method = 1; + } else { + LOG(PIL_CRIT, "ipmilan reset_method '%s' invalid", reset_opt); + return S_OOPS; + } + + if (nd->hostlist == NULL ) { + nd->hostlist = tmp; + nd->hostlist->prev = tmp; + nd->hostlist->next = tmp; + } else { + tmp->prev = nd->hostlist->prev; + tmp->next = nd->hostlist; + nd->hostlist->prev->next = tmp; + nd->hostlist->prev = tmp; + } + nd->hostcount++; + + return(S_OK); +} + +static const char * +ipmilan_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = nd->hostlist ? nd->hostlist->hostname : NULL; + break; + + case ST_DEVICEDESCR: + ret = "IPMI LAN STONITH device\n"; + break; + + case ST_DEVICEURL: + ret = "http://www.intel.com/design/servers/ipmi/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ipmilanXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * ipmilan StonithPlugin destructor... + * + * The hostlist is a link list. So have to iterate through. + */ +static void +ipmilan_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + struct ipmilanHostInfo * host; + int i; + + VOIDERRIFWRONGDEV(s); + + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginid; + + if (nd->hostlist) { + host = nd->hostlist->prev; + for (i = 0; i < nd->hostcount; i++) { + struct ipmilanHostInfo * host_prev = host->prev; + + FREE(host->hostname); + FREE(host->ipaddr); + FREE(host->username); + FREE(host->password); + + FREE(host); + host = host_prev; + } + } + + nd->hostcount = -1; + FREE(nd); + ipmi_leave(); +} + +/* Create a new ipmilan StonithPlugin device. Too bad this function can't be static */ +static StonithPlugin * +ipmilan_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + LOG(PIL_WARN, "The ipmilan stonith plugin is deprecated! Please use external/ipmi."); + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = 0; + nd->idinfo = DEVICE; + nd->sp.s_ops = &ipmilanOps; + return(&(nd->sp)); +} diff --git a/lib/plugins/stonith/ipmilan.h b/lib/plugins/stonith/ipmilan.h new file mode 100644 index 0000000..fb548f0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan.h @@ -0,0 +1,41 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.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 + * + */ + +#define ST_IPMI_STATUS 4 +#include <time.h> + +struct ipmilanHostInfo { + char * hostname; + char * ipaddr; + int portnumber; + int authtype; + int privilege; + char * username; + char * password; + int reset_method; + + struct ipmilanHostInfo * prev; + struct ipmilanHostInfo * next; +}; + +int do_ipmi_cmd(struct ipmilanHostInfo * host, int request); +void ipmi_leave(void); diff --git a/lib/plugins/stonith/ipmilan_command.c b/lib/plugins/stonith/ipmilan_command.c new file mode 100644 index 0000000..a3de493 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_command.c @@ -0,0 +1,399 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.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 <stdio.h> + +#include <stdlib.h> /* malloc() */ +#include <unistd.h> /* getopt() */ +#include <string.h> /* strerror() */ +#include <netdb.h> /* gethostbyname() */ +#include <sys/types.h> +#include <sys/socket.h> + +#include <OpenIPMI/ipmiif.h> +#include <OpenIPMI/selector.h> +#include <OpenIPMI/ipmi_conn.h> +#include <OpenIPMI/ipmi_lan.h> +#include <OpenIPMI/ipmi_smi.h> +#include <OpenIPMI/ipmi_auth.h> +#include <OpenIPMI/ipmi_msgbits.h> +#include <OpenIPMI/ipmi_posix.h> +#include <OpenIPMI/ipmi_debug.h> + +#include "ipmilan.h" +#include <stonith/stonith.h> +#include <clplumbing/cl_log.h> + +#include <pils/plugin.h> +extern const PILPluginImports* PluginImports; + +/* #define DUMP_MSG 0 */ +#define OPERATION_TIME_OUT 10 + +os_handler_t *os_hnd=NULL; +selector_t *os_sel; +static ipmi_con_t *con; +extern os_handler_t ipmi_os_cb_handlers; +static int reset_method; + +static int request_done = 0; +static int op_done = 0; + +typedef enum ipmi_status { + /* + IPMI_CONNECTION_FAILURE, + IPMI_SEND_FAILURE, + IPMI_BAD_REQUEST, + IPMI_REQUEST_FAILED, + IPMI_TIME_OUT, + */ + IPMI_RUNNING = 99, +} ipmi_status_t; + +static ipmi_status_t gstatus; + +typedef enum chassis_control_request { + POWER_DOWN = 0X00, + POWER_UP = 0X01, + POWER_CYCLE = 0X02, + HARD_RESET = 0X03, + PULSE_DIAGNOSTIC_INTERRUPT = 0X04, + SOFT_SHUTDOWN = 0X05 +} chassis_control_request_t; + +void dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type); +int rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi); + +void send_ipmi_cmd(ipmi_con_t *con, int request); + +void timed_out(selector_t *sel, sel_timer_t *timer, void *data); + +void +timed_out(selector_t *sel, sel_timer_t *timer, void *data) +{ + PILCallLog(PluginImports->log,PIL_CRIT, "IPMI operation timed out... :(\n"); + gstatus = S_TIMEOUT; +} + +void +dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type) +{ + ipmi_system_interface_addr_t *smi_addr = NULL; + int i; + ipmi_ipmb_addr_t *ipmb_addr = NULL; + + if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { + smi_addr = (struct ipmi_system_interface_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + smi_addr->lun, + msg->cmd); + } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE) + || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { + ipmb_addr = (struct ipmi_ipmb_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + ipmb_addr->lun, + msg->cmd); + } + + for (i = 0; i < msg->data_len; i++) { + if (((i%16) == 0) && (i != 0)) { + printf("\n "); + } + fprintf(stderr, "%2.2x ", msg->data[i]); + } + fprintf(stderr, "\n"); +} + +/* + * This function gets called after the response comes back + * from the IPMI device. + * + * Some IPMI device does not return success, 0x00, to the + * remote node when the power-reset was issued. + * + * The host who sent the ipmi cmd might get a 0xc3, + * a timeout instead. This creates problems for + * STONITH operation, where status is critical. :( + * + * Right now I am only checking 0xc3 as the return. + * If your IPMI device returns some wired code after + * reset, you might want to add it in this code block. + * + */ + +int +rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi) +{ + int rv; + long request; + + /*dump_msg_data(&rspi->msg, &rspi->addr, "response");*/ + request = (long) rspi->data1; + + op_done = 1; + if( !rspi || !(rspi->msg.data) ) { + PILCallLog(PluginImports->log,PIL_CRIT, "No data received\n"); + gstatus = S_RESETFAIL; + return IPMI_MSG_ITEM_NOT_USED; + } + rv = rspi->msg.data[0]; + /* some IPMI device might not issue 0x00, success, for reset command. + instead, a 0xc3, timeout, is returned. */ + if (rv == 0x00) { + gstatus = S_OK; + } else if((rv == 0xc3 || rv == 0xff) && request == ST_GENERIC_RESET) { + PILCallLog(PluginImports->log,PIL_WARN , + "IPMI reset request failed: %x, but we assume that it succeeded\n", rv); + gstatus = S_OK; + } else { + PILCallLog(PluginImports->log,PIL_INFO + , "IPMI request %ld failed: %x\n", request, rv); + gstatus = S_RESETFAIL; + } + return IPMI_MSG_ITEM_NOT_USED; +} + +void +send_ipmi_cmd(ipmi_con_t *con, int request) +{ + ipmi_addr_t addr; + unsigned int addr_len; + ipmi_msg_t msg; + struct ipmi_system_interface_addr *si; + int rv; + ipmi_msgi_t *rspi; + /* chassis control command request is only 1 byte long */ + unsigned char cc_data = POWER_CYCLE; + + si = (void *) &addr; + si->lun = 0x00; + si->channel = IPMI_BMC_CHANNEL; + si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + addr_len = sizeof(*si); + + msg.netfn = IPMI_CHASSIS_NETFN; + msg.cmd = IPMI_CHASSIS_CONTROL_CMD; + msg.data = &cc_data; + msg.data_len = 1; + + switch (request) { + case ST_POWERON: + cc_data = POWER_UP; + break; + + case ST_POWEROFF: + cc_data = POWER_DOWN; + break; + + case ST_GENERIC_RESET: + cc_data = (reset_method ? POWER_CYCLE : HARD_RESET); + break; + + case ST_IPMI_STATUS: + msg.netfn = IPMI_APP_NETFN; + msg.cmd = IPMI_GET_DEVICE_ID_CMD; + msg.data_len = 0; + break; + + default: + gstatus = S_INVAL; + return; + } + + gstatus = S_ACCESS; + rspi = calloc(1, sizeof(ipmi_msgi_t)); + if (NULL == rspi) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: Out of memory\n"); + } else { + rspi->data1 = (void *) (long) request; + rv = con->send_command(con, &addr, addr_len, &msg, rsp_handler, rspi); + if (rv == -1) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: %x\n", rv); + } else { + request_done = 1; + } + } + + return; +} + +static void +con_changed_handler(ipmi_con_t *ipmi, int err, unsigned int port_num, + int still_connected, void *cb_data) +{ + int * request; + + if (err) { + PILCallLog(PluginImports->log,PIL_CRIT, "Unable to setup connection: %x\n", err); + return; + } + + if( !request_done ) { + request = (int *) cb_data; + send_ipmi_cmd(ipmi, *request); + } +} + +static int +setup_ipmi_conn(struct ipmilanHostInfo * host, int *request) +{ + int rv; + + struct hostent *ent; + struct in_addr lan_addr[2]; + int lan_port[2]; + int num_addr = 1; + int authtype = 0; + int privilege = 0; + char username[17]; + char password[17]; + + /*DEBUG_MSG_ENABLE();*/ + + os_hnd = ipmi_posix_get_os_handler(); + if (!os_hnd) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_smi_setup_con: Unable to allocate os handler"); + return 1; + } + + rv = sel_alloc_selector(os_hnd, &os_sel); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not allocate selector\n"); + return rv; + } + + ipmi_posix_os_handler_set_sel(os_hnd, os_sel); + + rv = ipmi_init(os_hnd); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_init erro: %d ", rv); + return rv; + } + + ent = gethostbyname(host->ipaddr); + if (!ent) { + PILCallLog(PluginImports->log,PIL_CRIT, "gethostbyname failed: %s\n", strerror(h_errno)); + return 1; + } + + memcpy(&lan_addr[0], ent->h_addr_list[0], ent->h_length); + lan_port[0] = host->portnumber; + lan_port[1] = 0; + + authtype = host->authtype; + privilege = host->privilege; + + memcpy(username, host->username, sizeof(username)); + memcpy(password, host->password, sizeof(password)); + + reset_method = host->reset_method; + + rv = ipmi_lan_setup_con(lan_addr, lan_port, num_addr, + authtype, privilege, + username, strlen(username), + password, strlen(password), + os_hnd, os_sel, + &con); + + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_lan_setup_con: %s\n", strerror(rv)); + return S_ACCESS; + } + +#if OPENIPMI_VERSION_MAJOR < 2 + con->set_con_change_handler(con, con_changed_handler, request); +#else + con->add_con_change_handler(con, con_changed_handler, request); +#endif + + gstatus = IPMI_RUNNING; + + rv = con->start_con(con); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not start IPMI connection: %x\n", rv); + gstatus = S_BADCONFIG; + return rv; + } + return S_OK; +} + +void +ipmi_leave() +{ + if( con && con->close_connection ) { + con->close_connection(con); + con = NULL; + } + if( os_sel ) { + sel_free_selector(os_sel); + os_sel = NULL; + } +} + +int +do_ipmi_cmd(struct ipmilanHostInfo * host, int request) +{ + int rv; + sel_timer_t * timer; + struct timeval timeout; + + request_done = 0; + op_done = 0; + + if( !os_hnd ) { + rv = setup_ipmi_conn(host, &request); + if( rv ) { + return rv; + } + } else { + send_ipmi_cmd(con, request); + } + + gettimeofday(&timeout, NULL); + timeout.tv_sec += OPERATION_TIME_OUT; + timeout.tv_usec += 0; + + sel_alloc_timer(os_sel, timed_out, NULL, &timer); + sel_start_timer(timer, &timeout); + + while (!op_done) { + rv = sel_select(os_sel, NULL, 0, NULL, NULL); + if (rv == -1) { + break; + } + } + + sel_free_timer(timer); + return gstatus; +} + +#if OPENIPMI_VERSION_MAJOR < 2 +void +posix_vlog(char *format, enum ipmi_log_type_e log_type, va_list ap) +{ +} +#endif diff --git a/lib/plugins/stonith/ipmilan_test.c b/lib/plugins/stonith/ipmilan_test.c new file mode 100644 index 0000000..47859a0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_test.c @@ -0,0 +1,63 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.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 + * + */ + +/* + * A quick test program to verify that IPMI host is setup correctly. + * + * You will need to modify the values in user, pass, ip, and port. + */ + +#include <stdio.h> +#include <string.h> +#include "ipmilan.h" +#include <OpenIPMI/ipmi_auth.h> + +int main(int argc, char * argv[]) +{ + struct ipmilanHostInfo host; + int request = 2; + int rv; + + char user[] = "joe"; + char pass[] = "blow"; + char ip[] = "192.168.1.7"; + + host.hostname = NULL; + host.portnumber = 999; + host.authtype = IPMI_AUTHTYPE_NONE; + host.privilege = IPMI_PRIVILEGE_ADMIN; + + host.ipaddr = ip; + memcpy(host.username, user, sizeof(user)); + memcpy(host.password, pass, sizeof(pass)); + /* + memset(host.username, 0, sizeof(host.username)); + memset(host.password, 0, sizeof(host.password)); + */ + + rv = do_ipmi_cmd(&host, request); + if (rv) + printf("rv = %d, operation failed. \n", rv); + else + printf("operation succeeded. \n"); + return rv; +} diff --git a/lib/plugins/stonith/meatware.c b/lib/plugins/stonith/meatware.c new file mode 100644 index 0000000..029ba35 --- /dev/null +++ b/lib/plugins/stonith/meatware.c @@ -0,0 +1,351 @@ +/* + * Stonith module for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder <gbinder@sysfive.com> + * + * This module is largely based on the "NULL Stonith device", written + * by Alan Robertson <alanr@unix.sh>, using code by David C. Teigland + * <teigland@sistina.com> originally appeared in the GFS stomith + * meatware agent. + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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> + +#define DEVICE "Meatware STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN meatware +#define PIL_PLUGIN_S "meatware" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * meatware_new(const char *); +static void meatware_destroy(StonithPlugin *); +static int meatware_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * meatware_get_confignames(StonithPlugin *); +static const char * meatware_getinfo(StonithPlugin * s, int InfoType); +static int meatware_status(StonithPlugin * ); +static int meatware_reset_req(StonithPlugin * s, int request, const char * host); +static char ** meatware_hostlist(StonithPlugin *); + +static struct stonith_ops meatwareOps ={ + meatware_new, /* Create new STONITH object */ + meatware_destroy, /* Destroy STONITH object */ + meatware_getinfo, /* Return STONITH info string */ + meatware_get_confignames,/* Return STONITH info string */ + meatware_set_config, /* Get configuration from NVpairs */ + meatware_status, /* Return STONITH device status */ + meatware_reset_req, /* Request a reset */ + meatware_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 + , &meatwareOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Meatware STONITH device. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "MeatwareDevice-Stonith"; +static const char * NOTpluginID = "Meatware device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *meatwareXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +meatware_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this Meat device + */ + +static char ** +meatware_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in Meatware_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +Meat_parse_config_info(struct pluginDevice* nd, const char * info) +{ + LOG(PIL_INFO , "parse config info info=%s",info); + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Indicate that host must be power cycled manually. + */ +static int +meatware_reset_req(StonithPlugin * s, int request, const char * host) +{ + int fd, rc; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatclient.c as well */ + + char line[256], meatpipe[256]; + char resp_addr[50], resp_mw[50], resp_result[50]; + + + ERRIFWRONGDEV(s,S_OOPS); + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, host); + umask(0); + unlink(meatpipe); + + rc = mkfifo(meatpipe, (S_IRUSR | S_IWUSR)); + + if (rc < 0) { + LOG(PIL_CRIT, "cannot create FIFO for Meatware_reset_host"); + return S_OOPS; + } + + LOG(PIL_CRIT, "OPERATOR INTERVENTION REQUIRED to reset %s.", host); + LOG(PIL_CRIT, "Run \"meatclient -c %s\" AFTER power-cycling the " + "machine.", host); + + fd = open(meatpipe, O_RDONLY); + + if (fd < 0) { + LOG(PIL_CRIT, "cannot open FIFO for Meatware_reset_host"); + return S_OOPS; + } + + alarm(600); + memset(line, 0, 256); + rc = read(fd, line, 256); + alarm(0); + + if (rc < 0) { + LOG(PIL_CRIT, "read error on FIFO for Meatware_reset_host"); + return S_OOPS; + } + + memset(resp_mw, 0, 50); + memset(resp_result, 0, 50); + memset(resp_addr, 0, 50); + + if (sscanf(line, "%s %s %s", resp_mw, resp_result, resp_addr) < 3) { + LOG(PIL_CRIT, "Format error - failed to Meatware-reset node %s", + host); + return S_RESETFAIL; + } + + strdown(resp_addr); + + if (strncmp(resp_mw, "meatware", 8) || + strncmp(resp_result, "reply", 5) || + strncasecmp(resp_addr, host, strlen(resp_addr))) { + LOG(PIL_CRIT, "failed to Meatware-reset node %s", host); + return S_RESETFAIL; + }else{ + LOG(PIL_INFO, "node Meatware-reset: %s", host); + unlink(meatpipe); + return S_OK; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +meatware_set_config(StonithPlugin* s, StonithNVpair *list) +{ + + struct pluginDevice* nd; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + rc = Meat_parse_config_info(nd, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +meatware_get_confignames(StonithPlugin* p) +{ + static const char * MeatwareParams[] = {ST_HOSTLIST, NULL }; + return MeatwareParams; +} + +/* + * Return STONITH info string + */ +static const char * +meatware_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICENAME: + ret = "Your Name Here"; + break; + case ST_DEVICEDESCR: + ret = "Human (meatware) intervention STONITH device.\n" + "This STONITH agent prompts a human to reset a machine.\n" + "The human tells it when the reset was completed."; + break; + case ST_CONF_XML: /* XML metadata */ + ret = meatwareXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Meat Stonith destructor... + */ +static void +meatware_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Meatware Stonith device. */ + +static StonithPlugin * +meatware_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &meatwareOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/null.c b/lib/plugins/stonith/null.c new file mode 100644 index 0000000..0d0cf04 --- /dev/null +++ b/lib/plugins/stonith/null.c @@ -0,0 +1,260 @@ +/* + * Stonith module for NULL Stonith device + * + * 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> + +#define DEVICE "NULL STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN null +#define PIL_PLUGIN_S "null" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static StonithPlugin* null_new(const char *); +static void null_destroy(StonithPlugin *); +static int null_set_config(StonithPlugin* +, StonithNVpair*); +static const char * const * null_get_confignames(StonithPlugin*); +static const char * null_getinfo(StonithPlugin * s, int InfoType); +static int null_status(StonithPlugin * ); +static int null_reset_req(StonithPlugin * s +, int request, const char * host); +static char ** null_hostlist(StonithPlugin *); + +static struct stonith_ops nullOps ={ + null_new, /* Create new STONITH object */ + null_destroy, /* Destroy STONITH object */ + null_getinfo, /* Return STONITH info string */ + null_get_confignames, /* Return list of config params */ + null_set_config, /* configure fron NV pairs */ + null_status, /* Return STONITH device status */ + null_reset_req, /* Request a reset */ + null_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 + , &nullOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Null STONITH device. We are very agreeable, but don't do much :-) + */ + + +static const char * pluginid = "nullDevice-Stonith"; +static const char * NOTpluginID = "Null device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nullXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +null_status(StonithPlugin *s) +{ + + ERRIFWRONGDEV(s, S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this NULL device + */ + +static char ** +null_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +null_reset_req(StonithPlugin * s, int request, const char * host) +{ + + ERRIFWRONGDEV(s,S_OOPS); + + /* Real devices need to pay attention to the "request" */ + /* (but we don't care ;-)) */ + + LOG(PIL_INFO, "Host null-reset: %s", host); + return S_OK; +} + + +static const char * const * +null_get_confignames(StonithPlugin* p) +{ + static const char * NullParams[] = {ST_HOSTLIST, NULL }; + return NullParams; +} + +/* + * Parse the config information in the given string, + * and stash it away... + */ +static int +null_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nd->hostlist = OurImports->StringToHostList(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount] + ; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return nd->hostcount ? S_OK : S_BADCONFIG; +} + +static const char * +null_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = "(nil)"; + break; + + case ST_DEVICEDESCR: + ret = "Dummy (do-nothing) STONITH device\n" + "FOR TESTING ONLY!"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = nullXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * NULL Stonith destructor... + */ +static void +null_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(s); +} + +/* Create a new Null Stonith device. + * Too bad this function can't be static + */ +static StonithPlugin * +null_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->idinfo = DEVICE; + nd->sp.s_ops = &nullOps; + return (StonithPlugin *)nd; +} diff --git a/lib/plugins/stonith/nw_rpc100s.c b/lib/plugins/stonith/nw_rpc100s.c new file mode 100644 index 0000000..5ba0827 --- /dev/null +++ b/lib/plugins/stonith/nw_rpc100s.c @@ -0,0 +1,779 @@ +/* + * Stonith module for Night/Ware RPC100S + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for NW RPC100S + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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> +#define DEVICE "NW RPC100S Power Switch" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN nw_rpc100s +#define PIL_PLUGIN_S "nw_rpc100s" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_CFGLINE 256 +#include <pils/plugin.h> + +static StonithPlugin * nw_rpc100s_new(const char *); +static void nw_rpc100s_destroy(StonithPlugin *); +static int nw_rpc100s_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * nw_rpc100s_get_confignames(StonithPlugin *); +static const char * nw_rpc100s_getinfo(StonithPlugin * s, int InfoType); +static int nw_rpc100s_status(StonithPlugin * ); +static int nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host); +static char ** nw_rpc100s_hostlist(StonithPlugin *); + +static struct stonith_ops nw_rpc100sOps ={ + nw_rpc100s_new, /* Create new STONITH object */ + nw_rpc100s_destroy, /* Destroy STONITH object */ + nw_rpc100s_getinfo, /* Return STONITH info string */ + nw_rpc100s_get_confignames,/* Return STONITH info string */ + nw_rpc100s_set_config, /* Get configuration from NVpairs */ + nw_rpc100s_status, /* Return STONITH device status */ + nw_rpc100s_reset_req, /* Request a reset */ + nw_rpc100s_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; + +#include "stonith_signal.h" + +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +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 + , &nw_rpc100sOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + The Nightware RPS-100S is manufactured by: + + Micro Energetics Corp + +1 703 250-3000 + http://www.nightware.com/ + + Thank you to David Hicks of Micro Energetics Corp. for providing + a demo unit to write this software. + + This switch has a very simple protocol, + You issue a command and it gives a response. + Sample commands are conveniently documented on a sticker on the + bottom of the device. + + The switch accepts a single command of the form + + //0,yyy,zzz[/m][/h]<CR> + + Where yyy is the wait time before activiting the relay. + zzz is the relay time. + + The default is that the relay is in a default state of ON, which + means that usually yyy is the number of seconds to wait + before shutting off the power and zzz is the number of seconds the + power remains off. There is a dip switch to change the default + state to 'OFF'. Don't set this switch. It will screw up this code. + + An asterisk can be used for zzz to specify an infinite switch time. + The /m /and /h command options will convert the specified wait and + switch times to either minutewes or hours. + + A response is either + <cr><lf>OK<cr><lf> + or + <cr><lf>Invalid Entry<cr><lf> + + + As far as THIS software is concerned, we have to implement 4 commands: + + status --> //0,0,BOGUS; # Not a real command, this is just a + # probe to see if switch is alive + open(on) --> //0,0,0; # turn power to default state (on) + close(off) --> //0,0,*; # leave power off indefinitely + reboot --> //0,0,10; # immediately turn power off for 10 seconds. + + and expect the response 'OK' to confirm that the unit is operational. +*/ + + + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + + char * node; /* Name of the node that this is controlling */ + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "NW_RPC100S"; +static const char * NOTrpcid = "NW RPC100S device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nw_rpc100sXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * Different expect strings that we get from the NW_RPC100S + * Remote Power Controllers... + */ + +static struct Etoken NWtokOK[] = { {"OK", 0, 0}, {NULL,0,0}}; +static struct Etoken NWtokInvalidEntry[] = { {"Invalid Entry", 0, 0}, {NULL,0,0}}; +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken NWtokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPCConnect(struct pluginDevice * ctx); +static int RPCDisconnect(struct pluginDevice * ctx); + +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +#if defined(ST_POWERON) +static int RPCOn(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPCOff(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +static int RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +/*static int RPC_parse_config_info(struct pluginDevice* ctx, const char * info);*/ + + +#define SENDCMD(cmd, timeout) { \ + int return_val = RPCSendCommand(ctx, cmd, timeout); \ + if (return_val != S_OK) { \ + return return_val; \ + } \ + } + +/* + * RPCSendCommand - send a command to the specified outlet + */ +static int +RPCSendCommand (struct pluginDevice *ctx, const char *command, int timeout) +{ + char writebuf[64]; /* All commands are short. + They should be WAY LESS + than 64 chars long! + */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + /* list of FDs for select() */ + struct timeval tv; /* */ + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s\r", command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s" + , pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPCSendCommand() */ + +/* + * RPCReset - Reset (power-cycle) the given outlet number + * + * This device can only control one power outlet - unitnum is ignored. + * + */ +static int +RPCReset(struct pluginDevice* ctx, int unitnum, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "Calling RPCReset (%s)", pluginid); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD("//0,0,10;\r\n", 12); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got OK"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); + +} /* end RPCReset() */ + + +#if defined(ST_POWERON) +/* + * RPCOn - Turn OFF the given outlet number + */ +static int +RPCOn(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD("//0,0,0;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPCOff - Turn Off the given outlet number + */ +static int +RPCOff(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD("//0,0,*;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOff() */ +#endif + + +/* + * nw_rpc100s_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +nw_rpc100s_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_status (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPCConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPCDisconnect(ctx)); +} + +/* + * nw_rpc100s_hostlist - API entry point to return the list of hosts + * for the devices on this NW_RPC100S unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +nw_rpc100s_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_hostlist (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + ret = OurImports->StringToHostList(ctx->node); + if (ret == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(ret[0]); + } + + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash it away... + * + * <info> contains the parameters specific to this type of object + * + * The format of <parameters> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * stonith nodea NW_RPC100S /dev/ttyS0 nodeb 0 + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices accessible + * through 2 different serial ports on nodeb: + * + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 + * stonith nodeb NW_RPC100S /dev/ttyS1 nodec 0 + */ + +/*static int +RPC_parse_config_info(struct pluginDevice* ctx, const char * info) +{ +}*/ + + +/* + * RPCConnect - + * + * Connect to the given NW_RPC100S device. + * Side Effects + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPCConnect(struct pluginDevice * ctx) +{ + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + + /* Send a BOGUS string */ + SENDCMD("//0,0,BOGUS;\r\n", 10); + + /* Should reply with "Invalid Command" */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for \"Invalid Entry\""); + } + EXPECT(ctx->fd, NWtokInvalidEntry, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got Invalid Entry"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); +} + +static int +RPCDisconnect(struct pluginDevice * ctx) +{ + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the characters + and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPCNametoOutlet - Map a hostname to an outlet number on this stonith device. + * + * Returns: + * 0 on success ( the outlet number on the RPS10 - there is only one ) + * -1 on failure (host not found in the config file) + * + */ +static int +RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int rc = -1; + + if (!strcasecmp(ctx->node, host)) { + rc = 0; + } + + return rc; +} + + +/* + * nw_rpc100s_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + int outletnum = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_reset (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPCConnect(ctx)) != S_OK) { + return(rc); + } + + outletnum = RPCNametoOutlet(ctx, host); + LOG(PIL_DEBUG, "zk:outletname=%d", outletnum); + + if (outletnum < 0) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ctx->device, host); + RPCDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPCOn(ctx, outletnum, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPCOff(ctx, outletnum, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPCReset(ctx, outletnum, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPCDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +nw_rpc100s_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + ctx = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ctx->device = namestocopy[0].s_value; + ctx->node = namestocopy[1].s_value; + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +nw_rpc100s_get_confignames(StonithPlugin* p) +{ + static const char * RpcParams[] = {ST_TTYDEV , ST_HOSTLIST, NULL }; + return RpcParams; +} + + + +/* + * nw_rpc100s_getinfo - API entry point to retrieve something from the handle + */ +static const char * +nw_rpc100s_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Micro Energetics Night/Ware RPC100S"; + break; + case ST_DEVICEURL: + ret = "http://www.microenergeticscorp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = nw_rpc100sXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * nw_rpc100s_destroy - API entry point to destroy a NW_RPC100S Stonith object. + */ +static void +nw_rpc100s_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTrpcid; + + /* close the fd if open and set ctx->fd to invalid */ + RPCDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->node != NULL) { + FREE(ctx->node); + ctx->node = NULL; + } + FREE(ctx); +} + +/* + * nw_rpc100s_new - API entry point called to create a new NW_RPC100S Stonith + * device object. + */ +static StonithPlugin * +nw_rpc100s_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->device = NULL; + ctx->node = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &nw_rpc100sOps; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/rcd_serial.c b/lib/plugins/stonith/rcd_serial.c new file mode 100644 index 0000000..f1396a7 --- /dev/null +++ b/lib/plugins/stonith/rcd_serial.c @@ -0,0 +1,602 @@ +/* + * Stonith module for RCD_SERIAL Stonith device + * + * Original code from null.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Copious borrowings from nw_rpc100s.c by + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * and from apcsmart.c by + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * + * Modifications for RC Delayed Serial Ciruit by + * Copyright (c) 2002 John Sutton <john@scl.co.uk> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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> + +#define DEVICE "RC Delayed Serial" +#include "stonith_plugin_common.h" +#include "stonith_signal.h" + +#define PIL_PLUGIN rcd_serial +#define PIL_PLUGIN_S "rcd_serial" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#define ST_DTRRTS "dtr_rts" +#define ST_MSDURATION "msduration" +#define MAX_RCD_SERIALLINE 512 + +#include <pils/plugin.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +static StonithPlugin* rcd_serial_new(const char *); +static void rcd_serial_destroy(StonithPlugin *); +static int rcd_serial_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rcd_serial_get_confignames(StonithPlugin *); +static const char * rcd_serial_getinfo(StonithPlugin * s, int InfoType); +static int rcd_serial_status(StonithPlugin * ); +static int rcd_serial_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rcd_serial_hostlist(StonithPlugin *); + +static struct stonith_ops rcd_serialOps ={ + rcd_serial_new, /* Create new STONITH object */ + rcd_serial_destroy, /* Destroy STONITH object */ + rcd_serial_getinfo, /* Return STONITH info string */ + rcd_serial_get_confignames,/* Return STONITH info string */ + rcd_serial_set_config, /* Get configuration from NVpairs */ + rcd_serial_status, /* Return STONITH device status */ + rcd_serial_reset_req, /* Request a reset */ + rcd_serial_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 + , &rcd_serialOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* ------------------- RCD specific stuff -------------- */ + +/* + A diagram of a circuit suitable for use with this plugin is in + README.rcd_serial which should be somewhere in the distribution (if Alan + includes it ;-) and/or at http://www.scl.co.uk/rcd_serial/ (if I remember + to put it there ;-). + + Once you've got this built, you can test things using the stonith command + as follows: + + stonith -L + will show a list of plugin types, including rcd_serial + + stonith -t rcd_serial testhost + will show required parameters + + In these 3 you can either pass the params after the -p option or you can + put them in a config file and use -F configname instead of -p "param ...". + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -S + will show the status of the device + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -l + will list the single host testhost + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" testhost + will reset testhost (provided testhost has its reset pins + suitably wired to the RTS signal coming out of port /dev/ttyS0 + and that 1.5s is enough time to cause a reset ;-) +*/ + +/* + Define RCD_NOPAUSE if you are using the serial port for some purpose + _in_addition_ to using it as a stonith device. For example, I use one + of the input pins on the same serial port for monitoring the state of a + power supply. Periodically, a cron job has to open the port to read the + state of this input and thus has to clear down the output pins DTR and RTS + in order to avoid causing a spurious stonith reset. Now, if it should + happen that just at the same time as we are _really_ trying to do a stonith + reset, this cron job starts up, then the stonith reset won't occur ;-(. + To avoid this (albeit unlikely) outcome, you should #define RCD_NOPAUSE. + The effect of this is that instead of setting the line high just once and + then falling into a pause until an alarm goes off, rather, the program falls + into a loop which is continuously setting the line high. That costs us a bit + of CPU as compared with sitting in a pause, but hey, how often is this code + going to get exercised! Never, we hope... +*/ +#undef RCD_NOPAUSE + +#ifdef RCD_NOPAUSE +static int RCD_alarmcaught; +#endif + +/* + * own prototypes + */ + +static void RCD_alarm_handler(int sig); +static int RCD_open_serial_port(char *device); +static int RCD_close_serial_port(char *device, int fd); + +static void +RCD_alarm_handler(int sig) { +#if !defined(HAVE_POSIX_SIGNALS) + if (sig) { + signal(sig, SIG_DFL); + }else{ + signal(sig, RCD_alarm_handler); + } +#else + struct sigaction sa; + sigset_t sigmask; + + /* Maybe a bit naughty but it works and it saves duplicating all */ + /* this setup code - if handler called with 0 for sig, we install */ + /* ourself as handler. */ + if (sig) { + sa.sa_handler = (void (*)(int))SIG_DFL; + }else{ + sa.sa_handler = RCD_alarm_handler; + } + + sigemptyset(&sigmask); + sa.sa_mask = sigmask; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +#endif + +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 1; +#endif + return; +} + +static int +RCD_open_serial_port(char *device) { + int fd; + int status; + int bothbits; + + if (OurImports->TtyLock(device) < 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: ttylock failed.", __FUNCTION__); + } + return -1; + } + + bothbits = TIOCM_RTS | TIOCM_DTR; + + if ((fd = open(device, O_RDONLY | O_NDELAY)) != -1) { + /* + Opening the device always sets DTR & CTS high. + Clear them down immediately. + */ + status = ioctl(fd, TIOCMBIC, &bothbits); + /* If there was an error clearing bits, set the fd to -1 + * ( indicates error ) */ + if (status != 0 ) { + fd = -1; + } + } + + return fd; +} + +static int +RCD_close_serial_port(char *device, int fd) { + int rc = close(fd); + if (device != NULL) { + OurImports->TtyUnlock(device); + } + return rc; +} + +/* + * RCD_Serial STONITH device. + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; /* name of single host we can reset */ + int hostcount; /* i.e. 1 after initialisation */ + char * device; /* serial device name */ + char * signal; /* either rts or dtr */ + long msduration; /* how long (ms) to assert the signal */ +}; + +static const char * pluginid = "RCD_SerialDevice-Stonith"; +static const char * NOTrcd_serialID = "RCD_Serial device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_DTRRTS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_DTRRTS \ + XML_PARM_SHORTDESC_END + +#define XML_DTRRTS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hardware handshaking technique to use with " ST_TTYDEV "(\"dtr\" or \"rts\")" \ + XML_PARM_LONGDESC_END + +#define XML_DTRRTS_PARM \ + XML_PARAMETER_BEGIN(ST_DTRRTS, "string", "1", "0") \ + XML_DTRRTS_SHORTDESC \ + XML_DTRRTS_LONGDESC \ + XML_PARAMETER_END + +#define XML_MSDURATION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MSDURATION \ + XML_PARM_SHORTDESC_END + +#define XML_MSDURATION_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The delay duration (in milliseconds) between the assertion of the control signal on " ST_TTYDEV " and the closing of the reset switch" \ + XML_PARM_LONGDESC_END + +#define XML_MSDURATION_PARM \ + XML_PARAMETER_BEGIN(ST_MSDURATION, "string", "1", "0") \ + XML_MSDURATION_SHORTDESC \ + XML_MSDURATION_LONGDESC \ + XML_PARAMETER_END + +static const char *rcd_serialXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_TTYDEV_PARM + XML_DTRRTS_PARM + XML_MSDURATION_PARM + XML_PARAMETERS_END; + +static int +rcd_serial_status(StonithPlugin *s) +{ + struct pluginDevice* rcd; + int fd; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice*) s; + + /* + All we can do is make sure the serial device exists and + can be opened and closed without error. + */ + + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + return S_OK; +} + + +/* + * Return the list of hosts configured for this RCD_SERIAL device + */ +static char ** +rcd_serial_hostlist(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + ERRIFWRONGDEV(s,NULL); + rcd = (struct pluginDevice*) s; + if (rcd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in RCD_SERIAL_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)rcd->hostlist); +} + +/* + * At last, we really do it! I don't know what the request argument + * is so am just ignoring it... + */ +static int +rcd_serial_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* rcd; + int fd; + int sigbit; + struct itimerval timer; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice *) s; + + /* check that host matches */ + if (strcasecmp(host, rcd->hostlist[0])) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist.", + __FUNCTION__, host); + return(S_BADHOST); + } + + /* Set the appropriate bit for the signal */ + sigbit = *(rcd->signal)=='r' ? TIOCM_RTS : TIOCM_DTR; + + /* Set up the timer */ + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + timer.it_value.tv_sec = rcd->msduration / 1000; + timer.it_value.tv_usec = (rcd->msduration % 1000) * 1000; + + /* Open the device */ + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { +#ifdef HAVE_STRERROR + err = strerror(errno); +#else + err = sys_errlist[errno]; +#endif + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + /* Start the timer */ + RCD_alarm_handler(0); +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 0; +#endif + setitimer(ITIMER_REAL, &timer, 0); + + /* Set the line high */ + ioctl(fd, TIOCMBIS, &sigbit); + + /* Wait for the alarm signal */ +#ifdef RCD_NOPAUSE + while(!RCD_alarmcaught) ioctl(fd, TIOCMBIS, &sigbit); +#else + pause(); +#endif + + /* Clear the line low */ + ioctl(fd, TIOCMBIC, &sigbit); + + /* Close the port */ + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + LOG(PIL_INFO,"Host rcd_serial-reset: %s", host); + return S_OK; +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rcd_serial_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* rcd; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {ST_TTYDEV, NULL} + , {ST_DTRRTS, NULL} + , {ST_MSDURATION, NULL} + , {NULL, NULL} + }; + char *endptr; + int rc = 0; + + LOG(PIL_DEBUG, "%s:called", __FUNCTION__); + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + rcd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if ((rcd->hostlist = (char **)MALLOC(2*sizeof(char*))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + FREE(namestocopy[0].s_value); + FREE(namestocopy[1].s_value); + FREE(namestocopy[2].s_value); + FREE(namestocopy[3].s_value); + return S_OOPS; + } + rcd->hostlist[0] = namestocopy[0].s_value; + strdown(rcd->hostlist[0]); + rcd->hostlist[1] = NULL; + rcd->hostcount = 1; + rcd->device = namestocopy[1].s_value; + rcd->signal = namestocopy[2].s_value; + if (strcmp(rcd->signal, "rts") && strcmp(rcd->signal, "dtr")) { + LOG(PIL_CRIT, "%s: Invalid signal name '%s'", + pluginid, rcd->signal); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + + errno = 0; + rcd->msduration = strtol(namestocopy[3].s_value, &endptr, 0); + if (((errno == ERANGE) + && (rcd->msduration == LONG_MIN || rcd->msduration == LONG_MAX)) + || *endptr != 0 || rcd->msduration < 1) { + LOG(PIL_CRIT, "%s: Invalid msduration '%s'", + pluginid, namestocopy[3].s_value); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + FREE(namestocopy[3].s_value); + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rcd_serial_get_confignames(StonithPlugin* p) +{ + static const char * RcdParams[] = {ST_HOSTLIST, ST_TTYDEV + , ST_DTRRTS, ST_MSDURATION, NULL }; + return RcdParams; +} + +/* + * Return STONITH info string + */ +static const char * +rcd_serial_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* rcd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + rcd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = rcd->idinfo; + break; + case ST_DEVICENAME: + ret = rcd->device; + break; + case ST_DEVICEDESCR: + ret = "RC Delayed Serial STONITH Device\n" + "This device can be constructed cheaply from" + " readily available components,\n" + "with sufficient expertise and testing.\n" + "See README.rcd_serial for circuit diagram.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.scl.co.uk/rcd_serial/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rcd_serialXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RCD_SERIAL Stonith destructor... + */ +static void +rcd_serial_destroy(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + VOIDERRIFWRONGDEV(s); + + rcd = (struct pluginDevice *)s; + + rcd->pluginid = NOTrcd_serialID; + if (rcd->hostlist) { + stonith_free_hostlist(rcd->hostlist); + rcd->hostlist = NULL; + } + rcd->hostcount = -1; + if (rcd->device) { + FREE(rcd->device); + } + if (rcd->signal) { + FREE(rcd->signal); + } + FREE(rcd); +} + +/* + * Create a new RCD_Serial Stonith device. + * Too bad this function can't be static. (Hmm, weird, it _is_ static?) + */ +static StonithPlugin * +rcd_serial_new(const char *subplugin) +{ + struct pluginDevice* rcd = ST_MALLOCT(struct pluginDevice); + + if (rcd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(rcd, 0, sizeof(*rcd)); + + rcd->pluginid = pluginid; + rcd->hostlist = NULL; + rcd->hostcount = -1; + rcd->device = NULL; + rcd->signal = NULL; + rcd->msduration = 0; + rcd->idinfo = DEVICE; + rcd->sp.s_ops = &rcd_serialOps; + + return &(rcd->sp); +} diff --git a/lib/plugins/stonith/rhcs.c b/lib/plugins/stonith/rhcs.c new file mode 100644 index 0000000..293a081 --- /dev/null +++ b/lib/plugins/stonith/rhcs.c @@ -0,0 +1,1035 @@ +/* + * Stonith module for RedHat Cluster Suite fencing plugins + * + * 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> + * Modified for rhcs.c: Dejan Muhamedagic <dejan@suse.de> + * + * 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 <libxml/xmlmemory.h> +#include <libxml/xmlreader.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN rhcs +#define PIL_PLUGIN_S "rhcs" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include <pils/plugin.h> + +static StonithPlugin * rhcs_new(const char *); +static void rhcs_destroy(StonithPlugin *); +static int rhcs_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rhcs_get_confignames(StonithPlugin *); +static const char * rhcs_getinfo(StonithPlugin * s, int InfoType); +static int rhcs_status(StonithPlugin * ); +static int rhcs_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rhcs_hostlist(StonithPlugin *); + +static struct stonith_ops rhcsOps ={ + rhcs_new, /* Create new STONITH object */ + rhcs_destroy, /* Destroy STONITH object */ + rhcs_getinfo, /* Return STONITH info string */ + rhcs_get_confignames, /* Return STONITH info string */ + rhcs_set_config, /* Get configuration from NVpairs */ + rhcs_status, /* Return STONITH device status */ + rhcs_reset_req, /* Request a reset */ + rhcs_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 + , &rhcsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * RHCS STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * hostlist; + char * outputbuf; + xmlDoc * metadata; +}; + +static const char * pluginid = "RHCSDevice-Stonith"; +static const char * NOTpluginID = "RHCS device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int rhcs_run_cmd(struct pluginDevice *sd, const char *op, + const char *host, char **output); +/* Just free up the configuration and the memory, if any */ +static void rhcs_unconfig(struct pluginDevice *sd); + +static int +rhcs_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "monitor"; + int rc; + char * output = NULL; + + 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 = rhcs_run_cmd(sd, op, NULL, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + if (output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + if (!str) + return namecount; + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +rhcs_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int i, namecount; + char ** ret; + 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); + } + + namecount = get_num_tokens(sd->hostlist); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the sd->hostlist here */ + i = 0; + tmp = strtok(sd->hostlist, 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__); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + 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 +rhcs_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host rhcs-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 = "reboot"; + 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; + } + + rc = rhcs_run_cmd(sd, op, host, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (output) { + LOG(PIL_INFO, "plugin output: %s", output); + FREE(output); + } + return S_OK; + } + +} + +static int +rhcs_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->hostlist = NULL; + 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++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + if (!strcmp(key,"hostlist")) { + sd->hostlist = value; + FREE(key); + } else { + g_hash_table_insert(sd->cmd_opts, key, value); + } + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + rhcs_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 +rhcs_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; + } + if (sd->hostlist) { + FREE(sd->hostlist); + sd->hostlist = NULL; + } + if (sd->metadata) { + xmlFreeDoc(sd->metadata); + xmlCleanupParser(); + sd->metadata = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rhcs_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + + 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 0 + /* the required parameters may be acquired from the metadata + * */ + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (rhcs_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); + } + } + } +#endif + + return rhcs_parse_config_info(sd, list); +} + + +/* Only interested in regular files starting with fence_ that are also executable */ +static int +rhcs_exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_RHCS_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; +} + +static xmlDoc * +load_metadata(struct pluginDevice * sd) +{ + xmlDoc *doc = NULL; + const char *op = "metadata"; + int rc; + char *ret = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + rc = rhcs_run_cmd(sd, op, NULL, &ret); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (ret) { + LOG(PIL_CRIT, "plugin output: %s", ret); + FREE(ret); + } + goto err; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + doc = xmlParseMemory(ret, strlen(ret)); + if (!doc) { + LOG(PIL_CRIT, "%s: could not parse metadata", + __FUNCTION__); + goto err; + } + sd->metadata = doc; + +err: + if (ret) { + FREE(ret); + } + return doc; +} + +static const char *skip_attrs[] = { + "action", "verbose", "debug", "version", "help", "separator", + NULL +}; +/* XML stuff */ +typedef int (*node_proc) + (xmlNodeSet *nodes, struct pluginDevice *sd); + +static int +proc_xpath(const char *xpathexp, struct pluginDevice *sd, node_proc fun) +{ + xmlXPathObject *xpathObj = NULL; + xmlXPathContext *xpathCtx = NULL; + int rc = 1; + + if (!sd->metadata && !load_metadata(sd)) { + LOG(PIL_INFO, "%s: no metadata", __FUNCTION__); + return 1; + } + + /* Create xpath evaluation context */ + xpathCtx = xmlXPathNewContext(sd->metadata); + if(xpathCtx == NULL) { + LOG(PIL_CRIT, "%s: unable to create new XPath context", __FUNCTION__); + return 1; + } + /* Evaluate xpath expression */ + xpathObj = xmlXPathEvalExpression((const xmlChar*)xpathexp, xpathCtx); + if(xpathObj == NULL) { + LOG(PIL_CRIT, "%s: unable to evaluate expression %s", + __FUNCTION__, xpathexp); + goto err; + } + + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + rc = fun(xpathObj->nodesetval, sd); +err: + if (xpathObj) + xmlXPathFreeObject(xpathObj); + if (xpathCtx) + xmlXPathFreeContext(xpathCtx); + return rc; +} + +static int +load_confignames(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *attr; + const char * const*skip; + xmlNode *cur; + int i, j, namecount; + + namecount = nodes->nodeNr; + if (!namecount) { + LOG(PIL_INFO, "%s: no configuration parameters", __FUNCTION__); + return 1; + } + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return 1; + } + + /* now copy over confignames */ + j = 0; + for (i = 0; i < nodes->nodeNr; i++) { + cur = nodes->nodeTab[i]; + attr = xmlGetProp(cur, (const xmlChar*)"name"); + for (skip = skip_attrs; *skip; skip++) { + if (!strcmp(*skip,(char *)attr)) + goto skip; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, (char *)attr); + } + sd->confignames[j++] = strdup((char *)attr); + xmlFree(attr); + skip: + continue; + } + sd->confignames[j] = NULL; + + return 0; +} + +static int +dump_content(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *content = NULL; + xmlNode *cur; + int rc = 1; + + if (!nodes || !nodes->nodeTab || !nodes->nodeTab[0]) { + LOG(PIL_WARN, "%s: %s no nodes", + __FUNCTION__, sd->subplugin); + return 1; + } + cur = nodes->nodeTab[0]; + content = xmlNodeGetContent(cur); + if (content && strlen((char *)content) > 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s found content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + sd->outputbuf = STRDUP((char *)content); + rc = !(*sd->outputbuf); + } else { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s no content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + rc = 1; + } + + if (content) + xmlFree(content); + return rc; +} + +static int +dump_params_xml(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + int len = 0; + xmlNode *cur; + xmlBuffer *xml_buffer = NULL; + int rc = 0; + + xml_buffer = xmlBufferCreate(); + if (!xml_buffer) { + LOG(PIL_CRIT, "%s: failed to create xml buffer", __FUNCTION__); + return 1; + } + cur = nodes->nodeTab[0]; + len = xmlNodeDump(xml_buffer, sd->metadata, cur, 0, TRUE); + if (len <= 0) { + LOG(PIL_CRIT, "%s: could not dump xml for %s", + __FUNCTION__, (char *)xmlGetProp(cur, (const xmlChar*)"name")); + rc = 1; + goto err; + } + sd->outputbuf = STRDUP((char *)xml_buffer->content); +err: + xmlBufferFree(xml_buffer); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rhcs_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + proc_xpath("/resource-agent/parameters/parameter", sd, load_confignames); + } else { + /* return list of subplugins in rhcs directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the rhcs plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_RHCS_PLUGINDIR, &files, + SCANSEL_CAST rhcs_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+strlen("fence_")); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +fake_op(struct pluginDevice * sd, const char *op) +{ + const char *pfx = "RHCS plugin "; + char *ret = NULL; + + LOG(PIL_INFO, "rhcs plugins don't really support %s", op); + ret = MALLOC(strlen(pfx) + strlen(op) + 1); + strcpy(ret, pfx); + strcat(ret, op); + sd->outputbuf = ret; + return(ret); +} + +static const char * +rhcs_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + const char * op; + + 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); + } + + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + return fake_op(sd, op); + break; + + case ST_DEVICENAME: + if (!proc_xpath("/resource-agent/shortdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devname"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEDESCR: + if (!proc_xpath("/resource-agent/longdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devdescr"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + return fake_op(sd, op); + break; + + case ST_CONF_XML: + if (!proc_xpath("/resource-agent/parameters", sd, dump_params_xml)) { + return sd->outputbuf; + } + break; + + default: + return NULL; + } + return NULL; +} + +/* + * RHCS Stonith destructor... + */ +static void +rhcs_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; + rhcs_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 rhcs Stonith device */ +static StonithPlugin * +rhcs_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 = &rhcsOps; + return &(sd->sp); +} + +#define MAXLINE 512 + +static void +printparam_to_fd(int fd, const char *key, const char *value) +{ + char arg[MAXLINE]; + int cnt; + + cnt = snprintf(arg, MAXLINE, "%s=%s\n", key, value); + if (cnt <= 0 || cnt >= MAXLINE) { + LOG(PIL_CRIT, "%s: param/value pair too large", __FUNCTION__); + return; + } + if (Debug) { + LOG(PIL_DEBUG, "set rhcs plugin param '%s=%s'", key, value); + } + if (write(fd, arg, cnt) < 0) { + LOG(PIL_CRIT, "%s: write: %m", __FUNCTION__); + } +} + +static void +rhcs_print_var(gpointer key, gpointer value, gpointer user_data) +{ + printparam_to_fd(GPOINTER_TO_UINT(user_data), (char *)key, (char *)value); +} + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ + +static int +rhcs_run_cmd(struct pluginDevice *sd, const char *op, const char *host, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int rc; + char * data = NULL; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + int pid, status; + int fd1[2]; /* our stdout/their stdin */ + int fd2[2]; /* our stdin/their stdout and stderr */ + + rc = snprintf(cmd, FILENAME_MAX, "%s/fence_%s", + STONITH_RHCS_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; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + + if (pipe(fd1) || pipe(fd2)) + goto err; + + pid = fork(); + if (pid < 0) { + LOG(PIL_CRIT, "%s: fork: %m", __FUNCTION__); + goto err; + } + if (pid) { /* parent */ + close(fd1[0]); + close(fd2[1]); + + if (sd->cmd_opts) { + printparam_to_fd(fd1[1], "agent", sd->subplugin); + printparam_to_fd(fd1[1], "action", op); + if( host ) + printparam_to_fd(fd1[1], "nodename", host); + g_hash_table_foreach(sd->cmd_opts, rhcs_print_var, + GUINT_TO_POINTER(fd1[1])); + } + close(fd1[1]); /* we have nothing more to say */ + + fcntl(fd2[0], F_SETFL, fcntl(fd2[0], F_GETFL, 0) | O_NONBLOCK); + data = NULL; + slen=0; + data = MALLOC(1); + /* read stdout/stderr from the fence agent */ + do { + data[slen]=EOS; + read_len = read(fd2[0], buff, BUFF_LEN); + if (read_len > 0) { + data=REALLOC(data, slen+read_len+1); + if (data == NULL) { + goto err; + } + memcpy(data+slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + } else if (read_len < 0) { + if (errno == EAGAIN) + continue; + LOG(PIL_CRIT, "%s: read from pipe: %m", __FUNCTION__); + goto err; + } + } while (read_len); + + if (!data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + goto err; + } + close(fd2[0]); + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) { + LOG(PIL_CRIT, "%s: fence agent failed: %m", __FUNCTION__); + goto err; + } else { + rc = WEXITSTATUS(status); + if (rc) { + LOG(PIL_CRIT, "%s: fence agent exit code: %d", + __FUNCTION__, rc); + goto err; + } + } + } else { /* child */ + close(fd1[1]); + close(fd2[0]); + close(STDIN_FILENO); + if (dup(fd1[0]) < 0) + goto err; + close(fd1[0]); + close(STDOUT_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(STDERR_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(fd2[1]); + rc = sd->cmd_opts ? + execlp(cmd, cmd, NULL) : execlp(cmd, cmd, "-o", op, NULL); + if (rc < 0) { + LOG(PIL_CRIT, "%s: Calling '%s' failed: %m", + __FUNCTION__, cmd); + } + goto err; + } + + if (Debug && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + + if (output) { + *output = data; + } else { + FREE(data); + } + + return 0; + +err: + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + + return(-1); + +} diff --git a/lib/plugins/stonith/ribcl.py.in b/lib/plugins/stonith/ribcl.py.in new file mode 100644 index 0000000..14e070c --- /dev/null +++ b/lib/plugins/stonith/ribcl.py.in @@ -0,0 +1,101 @@ +#!@PYTHON@ + + +# +# 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 +# + +import sys +import socket +from httplib import * +from time import sleep + + +argv = sys.argv + + +try: + host = argv[1].split('.')[0]+'-rm' + cmd = argv[2] +except IndexError: + print "Not enough arguments" + sys.exit(1) + + +login = [ '<RIBCL VERSION="1.2">', + '<LOGIN USER_LOGIN="Administrator" PASSWORD="********">' ] + + +logout = [ '</LOGIN>', '</RIBCL>' ] + + +status = [ '<SERVER_INFO MODE="read">', '<GET_HOST_POWER_STATUS/>', + '</SERVER_INFO>' ] + + +reset = [ '<SERVER_INFO MODE="write">', '<RESET_SERVER/>', '</SERVER_INFO>' ] + + +off = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "N"/>', + '</SERVER_INFO>' ] + + +on = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "Y"/>', + '</SERVER_INFO>' ] + + +todo = { 'reset':reset, 'on':on, 'off':off, 'status':status } + + +acmds=[] +try: + if cmd == 'reset' and host.startswith('gfxcl'): + acmds.append(login + todo['off'] + logout) + acmds.append(login + todo['on'] + logout) + else: + acmds.append(login + todo[cmd] + logout) +except KeyError: + print "Invalid command: "+ cmd + sys.exit(1) + + +try: + for cmds in acmds: + + + c=HTTPSConnection(host) + c.send('<?xml version="1.0"?>\r\n') + c.sock.recv(1024) + + + for line in cmds: + c.send(line+'\r\n') + c.sock.recv(1024) + + + c.close() + sleep(1) + + +except socket.gaierror, msg: + print msg + sys.exit(1) +except socket.sslerror, msg: + print msg + sys.exit(1) +except socket.error, msg: + print msg + sys.exit(1) + diff --git a/lib/plugins/stonith/riloe.c b/lib/plugins/stonith/riloe.c new file mode 100644 index 0000000..a4a8312 --- /dev/null +++ b/lib/plugins/stonith/riloe.c @@ -0,0 +1,338 @@ +/* + * Stonith module for RILOE Stonith device + * + * Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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 + * + */ + +#define DEVICE "Compaq RILOE" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN riloe +#define PIL_PLUGIN_S "riloe" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * riloe_new(const char *); +static void riloe_destroy(StonithPlugin *); +static int riloe_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * riloe_get_confignames(StonithPlugin * ); +static const char * riloe_getinfo(StonithPlugin * s, int InfoType); +static int riloe_status(StonithPlugin * ); +static int riloe_reset_req(StonithPlugin * s, int request, const char * host); +static char ** riloe_hostlist(StonithPlugin *); + +static struct stonith_ops riloeOps ={ + riloe_new, /* Create new STONITH object */ + riloe_destroy, /* Destroy STONITH object */ + riloe_getinfo, /* Return STONITH info string */ + riloe_get_confignames, /* Return STONITH info string */ + riloe_set_config, /* Get configuration from NVpairs */ + riloe_status, /* Return STONITH device status */ + riloe_reset_req, /* Request a reset */ + riloe_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 + , &riloeOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define RILOE_COMMAND STONITH_MODULES "/ribcl.py" + +/* + * Riloe STONITH device. We are very agreeable, but don't do much :-) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "RiloeDevice-Stonith"; +static const char * NOTriloeID = "Riloe device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *riloeXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +riloe_status(StonithPlugin *s) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this RILOE device + */ + +static char ** +riloe_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const*)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +RILOE_parse_config_info(struct pluginDevice* nd, const char * info) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +riloe_reset_req(StonithPlugin * s, int request, const char * host) +{ + char cmd[4096]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + snprintf(cmd, sizeof(cmd), "%s %s reset", RILOE_COMMAND, host); + + if (Debug) { + LOG(PIL_DEBUG, "command %s will be executed", cmd); + } + + if (system(cmd) == 0) { + return S_OK; + } else { + LOG(PIL_CRIT, "command %s failed", cmd); + return(S_RESETFAIL); + } +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +riloe_set_config(StonithPlugin* s, StonithNVpair *list) +{ + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + struct pluginDevice* nd; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + rc = RILOE_parse_config_info(nd , namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + */ +static const char* const * +riloe_get_confignames(StonithPlugin* p) +{ + static const char * RiloeParams[] = {ST_HOSTLIST, NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return RiloeParams; +} + +/* + * Return STONITH info string + */ + +static const char * +riloe_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICEDESCR: + ret = "Compaq RILOE STONITH device\n" + "Very early version!"; + break; + case ST_DEVICEURL: + ret = "http://www.hp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = riloeXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RILOE Stonith destructor... + */ +static void +riloe_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTriloeID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Riloe Stonith device. Too bad this function can't be static */ +static StonithPlugin * +riloe_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &riloeOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/rps10.c b/lib/plugins/stonith/rps10.c new file mode 100644 index 0000000..08d9873 --- /dev/null +++ b/lib/plugins/stonith/rps10.c @@ -0,0 +1,1070 @@ +/* + * Stonith module for WTI Remote Power Controllers (RPS-10M device) + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for WTI RPS10 + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * 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> + +#define DEVICE "WTI RPS10 Power Switch" +#include "stonith_plugin_common.h" + +#include <termios.h> +#define PIL_PLUGIN rps10 +#define PIL_PLUGIN_S "rps10" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define ST_RPS10 "serial_to_targets" +#define MAX_PRSID 256 +#include <pils/plugin.h> + +static StonithPlugin * rps10_new(const char *); +static void rps10_destroy(StonithPlugin *); +static int rps10_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rps10_get_confignames(StonithPlugin *); +static const char * rps10_getinfo(StonithPlugin * s, int InfoType); +static int rps10_status(StonithPlugin * ); +static int rps10_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rps10_hostlist(StonithPlugin *); + +static struct stonith_ops rps10Ops ={ + rps10_new, /* Create new STONITH object */ + rps10_destroy, /* Destroy STONITH object */ + rps10_getinfo, /* Return STONITH info string */ + rps10_get_confignames, /* Return STONITH info string */ + rps10_set_config, /* Get configuration from NVpairs */ + rps10_status, /* Return STONITH device status */ + rps10_reset_req, /* Request a reset */ + rps10_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; + +#include "stonith_signal.h" +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +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 + , &rps10Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * This was written for a Western Telematic Inc. (WTI) + * Remote Power Switch - RPS-10M. + * + * It has a DB9 serial port, a Rotary Address Switch, + * and a pair of RJ-11 jacks for linking multiple switches + * together. The 'M' unit is a master unit which can control + * up to 9 additional slave units. (the master unit also has an + * A/C outlet, so you can control up to 10 devices) + * + * There are a set of dip switches. The default shipping configuration + * is with all dip switches down. I highly recommend that you flip + * switch #3 up, so that when the device is plugged in, the power + * to the unit comes on. + * + * The serial interface is fixed at 9600 BPS (well, you *CAN* + * select 2400 BPS with a dip switch, but why?) 8-N-1 + * + * The ASCII command string is: + * + * ^B^X^X^B^X^Xac^M + * + * ^B^X^X^B^X^X "fixed password" prefix (CTRL-B CTRL-X ... ) + * ^M the carriage return character + * + * a = 0-9 Indicates the address of the module to receive the command + * a = * Sends the command to all modules + * + * c = 0 Switch the AC outlet OFF + * Returns: + * Plug 0 Off + * Complete + * + * c = 1 Switch the AC outlet ON + * Returns: + * Plug 0 On + * Complete + * + * c = T Toggle AC OFF (delay) then back ON + * Returns: + * Plug 0 Off + * Plug 0 On + * Complete + * + * c = ? Read and display status of the selected module + * Returns: + * Plug 0 On # or Plug 0 Off + * Complete + * + * e.g. ^B^X^X^B^X^X0T^M toggles the power on plug 0 OFF and then ON + * + * 21 September 2000 + * Eric Z. Ayers + * Computer Generation, Inc. + */ + +struct cntrlr_str { + char outlet_id; /* value 0-9, '*' */ + char * node; /* name of the node attached to this outlet */ +}; + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + +#define WTI_NUM_CONTROLLERS 10 + struct cntrlr_str + controllers[WTI_NUM_CONTROLLERS]; + /* one master switch can address 10 controllers */ + + /* Number of actually configured units */ + int unit_count; + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "WTI_RPS10"; +static const char * NOTwtiid = "OBJECT DESTROYED: (WTI RPS-10)"; + +#include "stonith_config_xml.h" + +#define XML_RPS10_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Value in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_SHORTDESC_END + +#define XML_RPS10_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The RPS-10 STONITH device configuration information in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_LONGDESC_END + +#define XML_RPS10_PARM \ + XML_PARAMETER_BEGIN(ST_RPS10, "string", "1", "1") \ + XML_RPS10_SHORTDESC \ + XML_RPS10_LONGDESC \ + XML_PARAMETER_END + +static const char *rps10XML = + XML_PARAMETERS_BEGIN + XML_RPS10_PARM + XML_PARAMETERS_END; + +/* WTIpassword - The fixed string ^B^X^X^B^X^X */ +static const char WTIpassword[7] = {2,24,24,2,24,24,0}; + +/* + * Different expect strings that we get from the WTI_RPS10 + * Remote Power Controllers... + */ + +static struct Etoken WTItokReady[] = { {"RPS-10 Ready", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokComplete[] = { {"Complete", 0, 0} ,{NULL,0,0}}; +static struct Etoken WTItokPlug[] = { {"Plug", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokOutlet[] = { {"0", 0, 0}, + {"1", 0, 0}, + {"2", 0, 0}, + {"3", 0, 0}, + {"4", 0, 0}, + {"5", 0, 0}, + {"6", 0, 0}, + {"7", 0, 0}, + {"8", 0, 0}, + {"9", 0, 0}, + {NULL,0,0}}; + +static struct Etoken WTItokOff[] = { {"Off", 0, 0}, {NULL,0,0}}; + +/* + * Tokens currently not used because they don't show up on all RPS10 units: + * + */ +static struct Etoken WTItokOn[] = { {"On", 0, 0}, {NULL,0,0}}; + +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken WTItokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPSConnect(struct pluginDevice * ctx); +static int RPSDisconnect(struct pluginDevice * ctx); + +static int RPSReset(struct pluginDevice*, char unit_id, const char * rebootid); +#if defined(ST_POWERON) +static int RPSOn(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPSOff(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +static signed char RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +static int RPS_parse_config_info(struct pluginDevice* ctx, const char * info); + +#define SENDCMD(outlet, cmd, timeout) { \ + int ret_val = RPSSendCommand(ctx, outlet, cmd, timeout);\ + if (ret_val != S_OK) { \ + return ret_val; \ + } \ + } + +/* + * RPSSendCommand - send a command to the specified outlet + */ +static int +RPSSendCommand (struct pluginDevice *ctx, char outlet, char command, int timeout) +{ + char writebuf[10]; /* all commands are 9 chars long! */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + struct timeval tv; /* */ + + /* list of FDs for select() */ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s%c%c\r", + WTIpassword, outlet, command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s\n", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s", + pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPSSendCommand() */ + +/* + * RPSReset - Reset (power-cycle) the given outlet id + */ +static int +RPSReset(struct pluginDevice* ctx, char unit_id, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD(unit_id, 'T', 10); + + /* Expect "Plug 0 Off" */ + /* Note: If asked to control "*", the RPS10 will report all units it + * separately; however, we don't know how many, so we can only wait + * for the first unit to report something and then wait until the + * "Complete" */ + EXPECT(ctx->fd, WTItokPlug, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got Plug\n"); + } + EXPECT(ctx->fd, WTItokOutlet, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Outlet #\n"); + } + EXPECT(ctx->fd, WTItokOff, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Off\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 14); + if (Debug) { + LOG(PIL_DEBUG, "Got Complete\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); + +} /* end RPSReset() */ + + +#if defined(ST_POWERON) +/* + * RPSOn - Turn OFF the given outlet id + */ +static int +RPSOn(struct pluginDevice* ctx, char unit_id, const char * host) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD(unit_id, '1', 10); + + /* Expect "Plug 0 On" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOn, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPSOff - Turn Off the given outlet id + */ +static int +RPSOff(struct pluginDevice* ctx, char unit_id, const char * host) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD(unit_id, '0', 10); + + /* Expect "Plug 0 Off" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOff, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOff() */ +#endif + + +/* + * rps10_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +rps10_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPSConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPSDisconnect(ctx)); +} + +/* + * rps10_hostlist - API entry point to return the list of hosts + * for the devices on this WTI_RPS10 unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +rps10_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + int i; + int j; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + if (ctx->unit_count >= 1) { + ret = (char **)MALLOC((ctx->unit_count+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + ret[ctx->unit_count]=NULL; /* null terminate the array */ + for (i=0; i < ctx->unit_count; i++) { + ret[i] = STRDUP(ctx->controllers[i].node); + if (ret[i] == NULL) { + for(j=0; j<i; j++) { + FREE(ret[j]); + } + FREE(ret); ret = NULL; + break; + } + } /* end for each possible outlet */ + } /* end if any outlets are configured */ + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash + * it away... + * + * The format of <info> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * <assuming this is the heartbeat configuration syntax:> + * + * stonith nodea rps10 /dev/ttyS0 nodeb 0 + * stonith nodeb rps10 /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices + * accessible through 2 different serial ports on nodeb: + * + * stonith nodeb rps10 /dev/ttyS0 nodea 0 + * stonith nodeb rps10 /dev/ttyS1 nodec 0 + */ + +/* + * OOPS! + * + * Most of the large block of comments above is incorrect as far as this + * module is concerned. It is somewhat applicable to the heartbeat code, + * but not to this Stonith module. + * + * The format of parameter string for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + */ + +static int +RPS_parse_config_info(struct pluginDevice* ctx, const char * info) +{ + char *copy; + char *token; + char *outlet, *node; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* strtok() is nice to use to parse a string with + (other than it isn't threadsafe), but it is destructive, so + we're going to alloc our own private little copy for the + duration of this function. + */ + + copy = STRDUP(info); + if (!copy) { + LOG(PIL_CRIT, "out of memory"); + return S_OOPS; + } + + /* Grab the serial device */ + token = strtok (copy, " \t"); + + if (!token) { + LOG(PIL_CRIT, "%s: Can't find serial device on config line '%s'", + pluginid, info); + goto token_error; + } + + ctx->device = STRDUP(token); + if (!ctx->device) { + LOG(PIL_CRIT, "out of memory"); + goto token_error; + } + + /* Loop through the rest of the command line which should consist of */ + /* <nodename> <outlet> pairs */ + while ((node = strtok (NULL, " \t")) + && (outlet = strtok (NULL, " \t\n"))) { + char outlet_id; + + /* validate the outlet token */ + if ((sscanf (outlet, "%c", &outlet_id) != 1) + || !( ((outlet_id >= '0') && (outlet_id <= '9')) + || (outlet_id == '*') || (outlet_id == 'A') ) + ) { + LOG(PIL_CRIT + , "%s: the outlet_id %s must be between" + " 0 and 9 or '*' / 'A'", + pluginid, outlet); + goto token_error; + } + + if (outlet_id == 'A') { + /* Remap 'A' to '*'; in some configurations, + * a '*' can't be configured because it breaks + * scripts -- lmb */ + outlet_id = '*'; + } + + if (ctx->unit_count >= WTI_NUM_CONTROLLERS) { + LOG(PIL_CRIT, + "%s: Tried to configure too many controllers", + pluginid); + goto token_error; + } + + ctx->controllers[ctx->unit_count].node = STRDUP(node); + strdown(ctx->controllers[ctx->unit_count].node); + ctx->controllers[ctx->unit_count].outlet_id = outlet_id; + ctx->unit_count++; + + } + + /* free our private copy of the string we've been destructively + * parsing with strtok() + */ + FREE(copy); + return ((ctx->unit_count > 0) ? S_OK : S_BADCONFIG); + +token_error: + FREE(copy); + if (ctx->device) { + FREE(ctx->device); + ctx->device = NULL; + } + return(S_BADCONFIG); +} + + +/* + * dtrtoggle - toggle DTR on the serial port + * + * snarfed from minicom, sysdep1.c, a well known POSIX trick. + * + */ +static void dtrtoggle(int fd) { + struct termios tty, old; + int sec = 2; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + tcgetattr(fd, &tty); + tcgetattr(fd, &old); + cfsetospeed(&tty, B0); + cfsetispeed(&tty, B0); + tcsetattr(fd, TCSANOW, &tty); + if (sec>0) { + sleep(sec); + tcsetattr(fd, TCSANOW, &old); + } + + if (Debug) { + LOG(PIL_DEBUG, "dtrtoggle Complete (%s)\n", pluginid); + } +} + +/* + * RPSConnect - + * + * Connect to the given WTI_RPS10 device. + * Side Effects + * DTR on the serial port is toggled + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPSConnect(struct pluginDevice * ctx) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + /* Toggle DTR - this 'resets' the controller serial port interface + In minicom, try CTRL-A H to hangup and you can see this behavior. + */ + dtrtoggle(ctx->fd); + + /* Wait for the switch to respond with "RPS-10 Ready". + Emperically, this usually takes 5-10 seconds... + ... If this fails, this may be a hint that you got + a broken serial cable, which doesn't connect hardware + flow control. + */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for READY\n"); + } + EXPECT(ctx->fd, WTItokReady, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got READY\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); +} + +static int +RPSDisconnect(struct pluginDevice * ctx) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the + * characters and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPSNametoOutlet - Map a hostname to an outlet on this stonith device. + * + * Returns: + * 0-9, * on success ( the outlet id on the RPS10 ) + * -1 on failure (host not found in the config file) + * + */ +static signed char +RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int i=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* scan the controllers[] array to see if this host is there */ + for (i=0;i<ctx->unit_count;i++) { + /* return the outlet id */ + if ( ctx->controllers[i].node + && !strcasecmp(host, ctx->controllers[i].node)) { + /* found it! */ + break; + } + } + + if (i == ctx->unit_count) { + return -1; + } else { + return ctx->controllers[i].outlet_id; + } +} + + +/* + * rps10_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +rps10_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + signed char outlet_id = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPSConnect(ctx)) != S_OK) { + return(rc); + } + + outlet_id = RPSNametoOutlet(ctx, host); + + if (outlet_id < 0) { + LOG(PIL_WARN, "%s: %s doesn't control host [%s]" + , pluginid, ctx->device, host ); + RPSDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPSOn(ctx, outlet_id, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPSOff(ctx, outlet_id, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPSReset(ctx, outlet_id, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPSDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +rps10_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_RPS10, NULL} + , {NULL, NULL} + }; + int rc=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (s->isconfigured) { + /* The module is already configured. */ + return(S_OOPS); + } + + ctx = (struct pluginDevice*) s; + + if((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK){ + LOG(PIL_DEBUG , "get all calues failed"); + return rc; + } + + rc = RPS_parse_config_info(ctx, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +rps10_get_confignames(StonithPlugin* p) +{ + static const char * Rps10Params[] = {ST_RPS10 ,NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return Rps10Params; +} + +/* + * rps10_getinfo - API entry point to retrieve something from the handle + */ +static const char * +rps10_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic Inc. (WTI) " + "Remote Power Switch - RPS-10M.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rps10XML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * rps10_destroy - API entry point to destroy a WTI_RPS10 Stonith object. + */ +static void +rps10_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTwtiid; + + /* close the fd if open and set ctx->fd to invalid */ + RPSDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->unit_count > 0) { + for (i = 0; i < ctx->unit_count; i++) { + if (ctx->controllers[i].node != NULL) { + FREE(ctx->controllers[i].node); + ctx->controllers[i].node = NULL; + } + } + } + FREE(ctx); +} + +/* + * rps10_new - API entry point called to create a new WTI_RPS10 Stonith device + * object. + */ +static StonithPlugin * +rps10_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->unit_count = 0; + ctx->device = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &rps10Ops; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/ssh.c b/lib/plugins/stonith/ssh.c new file mode 100644 index 0000000..e90c199 --- /dev/null +++ b/lib/plugins/stonith/ssh.c @@ -0,0 +1,351 @@ +/* + * Stonith module for SSH Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * + * Authors: Joachim Gleissner <jg@suse.de>, Lars Marowsky-Brée <lmb@suse.de> + * + * 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 <config.h> + +#define DEVICE "SSH STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ssh +#define PIL_PLUGIN_S "ssh" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * ssh_new(const char *); +static void ssh_destroy(StonithPlugin *); +static const char * const * ssh_get_confignames(StonithPlugin *); +static int ssh_set_config(StonithPlugin *, StonithNVpair*); +static const char * ssh_get_info(StonithPlugin * s, int InfoType); +static int ssh_status(StonithPlugin * ); +static int ssh_reset_req(StonithPlugin * s, int request +, const char * host); +static char ** ssh_hostlist(StonithPlugin *); + +static struct stonith_ops sshOps ={ + ssh_new, /* Create new STONITH object */ + ssh_destroy, /* Destroy STONITH object */ + ssh_get_info, /* Return STONITH info string */ + ssh_get_confignames, /* Return configuration parameters */ + ssh_set_config, /* set configuration */ + ssh_status, /* Return STONITH device status */ + ssh_reset_req, /* Request a reset */ + ssh_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 + , &sshOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* uncomment this if you have an ssh that can do what it claims +#define SSH_COMMAND "ssh -q -x -o PasswordAuthentication=no StrictHostKeyChecking=no" +*/ +/* use this if you have the (broken) OpenSSH 2.1.1 */ +/* sunjd@cn.ibm.com added the option -f to temporily work around the block issue + * in which the child process always stay in 'system' call. Please FIX this. + * Additonally, this issue seems related to both of 2.6 kernel and stonithd. + */ +#define SSH_COMMAND "ssh -q -x -n -l root" + +/* We need to do a real hard reboot without syncing anything to simulate a + * power cut. + * We have to do it in the background, otherwise this command will not + * return. + */ +#define REBOOT_COMMAND "nohup sh -c '(sleep 2; nohup " REBOOT " " REBOOT_OPTIONS ") </dev/null >/dev/null 2>&1' &" +#undef REBOOT_COMMAND +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" + +#define MAX_PING_ATTEMPTS 15 + +/* + * SSH STONITH device + * + * I used the null device as template, so I guess there is missing + * some functionality. + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "SSHDevice-Stonith"; +static const char * NOTpluginid = "SSH device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *sshXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +ssh_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return system(NULL) ? S_OK : S_OOPS; +} + + +/* + * Return the list of hosts configured for this SSH device + */ + +static char ** +ssh_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + + if (sd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)sd->hostlist); +} + + +/* + * Reset the given host on this Stonith device. + */ +static int +ssh_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + char cmd[4096]; + int i, status = -1; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + return S_INVAL; + } + + for (i = 0; i < sd->hostcount; i++) { + if (strcasecmp(host, sd->hostlist[i]) == 0) { + break; + } + } + + if (i >= sd->hostcount) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , sd->idinfo, host); + return(S_BADHOST); + } + + LOG(PIL_INFO, "Initiating ssh-%s on host: %s" + , request == ST_POWEROFF ? "poweroff" : "reset", host); + + snprintf(cmd, sizeof(cmd)-1, "%s \"%s\" \"%s\"", SSH_COMMAND + , host + , request == ST_POWEROFF ? POWEROFF_COMMAND : REBOOT_COMMAND); + + status = system(cmd); + if (WIFEXITED(status) && 0 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "checking whether %s stonith'd", host); + } + + snprintf(cmd, sizeof(cmd)-1 + , "ping -w1 -c1 %s >/dev/null 2>&1", host); + + for (i = 0; i < MAX_PING_ATTEMPTS; i++) { + status = system(cmd); + if (WIFEXITED(status) && 1 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "unable to ping %s" + " after %d tries, stonith did work" + , host, i); + } + return S_OK; + } + sleep(1); + } + + LOG(PIL_CRIT, "still able to ping %s after %d tries, stonith" + " did not work", host, MAX_PING_ATTEMPTS); + return S_RESETFAIL; + }else{ + LOG(PIL_CRIT, "command %s failed", cmd); + return S_RESETFAIL; + } +} + +static const char * const * +ssh_get_confignames(StonithPlugin* p) +{ + static const char * SshParams[] = {ST_HOSTLIST, NULL }; + return SshParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +ssh_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * hlist; + + ERRIFWRONGDEV(s,S_OOPS); + + if ((hlist = OurImports->GetValue(list, ST_HOSTLIST)) == NULL) { + return S_OOPS; + } + sd->hostlist = OurImports->StringToHostList(hlist); + if (sd->hostlist == NULL) { + LOG(PIL_CRIT, "out of memory"); + sd->hostcount = 0; + }else{ + for (sd->hostcount = 0; sd->hostlist[sd->hostcount] + ; sd->hostcount++) { + strdown(sd->hostlist[sd->hostcount]); + } + } + + return sd->hostcount ? S_OK : S_OOPS; +} + + +static const char * +ssh_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + + case ST_DEVICENAME: + ret = "ssh STONITH device"; + break; + + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "SSH-based host reset\n" + "Fine for testing, but not suitable for production!"; + break; + + + case ST_DEVICEURL: + ret = "http://openssh.org"; + break; + + + case ST_CONF_XML: /* XML metadata */ + ret = sshXML; + break; + + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * SSH Stonith destructor... + */ +static void +ssh_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + + VOIDERRIFWRONGDEV(s); + + sd->pluginid = NOTpluginid; + if (sd->hostlist) { + stonith_free_hostlist(sd->hostlist); + sd->hostlist = NULL; + } + sd->hostcount = -1; + FREE(sd); +} + +/* Create a new ssh Stonith device */ +static StonithPlugin* +ssh_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->hostlist = NULL; + sd->hostcount = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &sshOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/stonith_config_xml.h b/lib/plugins/stonith/stonith_config_xml.h new file mode 100644 index 0000000..ff04ae9 --- /dev/null +++ b/lib/plugins/stonith/stonith_config_xml.h @@ -0,0 +1,157 @@ +/* + * stonith_config_xml.h: common macros easing the writing of config + * XML for STONITH plugins. Only a STONITH + * plugin should include this header! + * + * Copyright (C) International Business Machines Corp., 2005 + * Author: Dave Blaschke <debltc@us.ibm.com> + * Support: linux-ha@lists.linux-ha.org + * + * 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 + * + */ +#ifndef _STONITH_CONFIG_XML_H +#define _STONITH_CONFIG_XML_H + +/* + * The generic constants for XML + */ + +/* <parameters>?</parameters> */ +#define XML_PARAMETERS_BEGIN "<parameters>" +#define XML_PARAMETERS_END "</parameters>" + +/* <parameter name="ipaddr" unique="?">?<content type="string" /></parameter> */ +#define XML_PARAMETER_BEGIN(name,type,req,uniq) \ + "<parameter name=\"" name "\" unique=\"" uniq "\" required=\"" req "\">" \ + "<content type=\"" type "\" />\n" +#define XML_PARAMETER_END "</parameter>\n" + +/* <shortdesc lang="en">?</shortdesc> */ +#define XML_PARM_SHORTDESC_BEGIN(lang) \ + "<shortdesc lang=\"" lang "\">\n" +#define XML_PARM_SHORTDESC_END "</shortdesc>\n" + +/* <longdesc lang="en">?</longdesc> */ +#define XML_PARM_LONGDESC_BEGIN(lang) \ + "<longdesc lang=\"" lang "\">\n" +#define XML_PARM_LONGDESC_END "</longdesc>\n" + +/* + * The short and long descriptions for the few standardized parameter names; + * these can be translated by appending different languages to these constants + * (must include XML_PARM_****DESC_BEGIN(), the translated description, and + * XML_PARM_****DESC_END for each language) + */ +#define XML_HOSTLIST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Hostlist" \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTLIST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The list of hosts that the STONITH device controls" \ + XML_PARM_LONGDESC_END + +#define XML_IPADDR_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "IP Address" \ + XML_PARM_SHORTDESC_END + +#define XML_IPADDR_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The IP address of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_LOGIN_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Login" \ + XML_PARM_SHORTDESC_END + +#define XML_LOGIN_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The username used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PASSWD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Password" \ + XML_PARM_SHORTDESC_END + +#define XML_PASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The password used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_COMMUNITY_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "SNMP Community" \ + XML_PARM_SHORTDESC_END + +#define XML_COMMUNITY_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The SNMP community string associated with the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_TTYDEV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "TTY Device" \ + XML_PARM_SHORTDESC_END + +#define XML_TTYDEV_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The TTY device used for connecting to the STONITH device" \ + XML_PARM_LONGDESC_END + +/* + * Complete parameter descriptions for the few standardized parameter names + */ +#define XML_HOSTLIST_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTLIST, "string", "1", "0") \ + XML_HOSTLIST_SHORTDESC \ + XML_HOSTLIST_LONGDESC \ + XML_PARAMETER_END + +#define XML_IPADDR_PARM \ + XML_PARAMETER_BEGIN(ST_IPADDR, "string", "1", "0") \ + XML_IPADDR_SHORTDESC \ + XML_IPADDR_LONGDESC \ + XML_PARAMETER_END + +#define XML_LOGIN_PARM \ + XML_PARAMETER_BEGIN(ST_LOGIN, "string", "1", "0") \ + XML_LOGIN_SHORTDESC \ + XML_LOGIN_LONGDESC \ + XML_PARAMETER_END + +#define XML_PASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "1", "0") \ + XML_PASSWD_SHORTDESC \ + XML_PASSWD_LONGDESC \ + XML_PARAMETER_END + +#define XML_COMMUNITY_PARM \ + XML_PARAMETER_BEGIN(ST_COMMUNITY, "string", "1", "0") \ + XML_COMMUNITY_SHORTDESC \ + XML_COMMUNITY_LONGDESC \ + XML_PARAMETER_END + +#define XML_TTYDEV_PARM \ + XML_PARAMETER_BEGIN(ST_TTYDEV, "string", "1", "0") \ + XML_TTYDEV_SHORTDESC \ + XML_TTYDEV_LONGDESC \ + XML_PARAMETER_END + +#endif diff --git a/lib/plugins/stonith/stonith_expect_helpers.h b/lib/plugins/stonith/stonith_expect_helpers.h new file mode 100644 index 0000000..f9eaa19 --- /dev/null +++ b/lib/plugins/stonith/stonith_expect_helpers.h @@ -0,0 +1,120 @@ +/* + * stonith_expect_helpers.h: Some common expect defines. + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * 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 + * + */ + +/* This is still somewhat ugly. It needs to be included after the PILS + * definitions so that it can access them, but the code reduction seemed + * to justify this. Hopefully it can be made somewhat more elegant + * eventually. */ + +/* + * Many expect/telnet plugins use these defines and functions. + */ + +#define SEND(fd,s) { \ + size_t slen = strlen(s); \ + if (Debug) { \ + LOG(PIL_DEBUG \ + , "Sending [%s] (len %d)" \ + , (s) \ + , (int)slen); \ + } \ + if (write((fd), (s), slen) != slen) { \ + LOG(PIL_CRIT \ + , "%s: write failed" \ + , __FUNCTION__); \ + } \ + } + +#define EXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(errno == ETIMEDOUT \ + ? S_TIMEOUT : S_OOPS); \ + } + +#define NULLEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(NULL); \ + } + +#define SNARF(fd,s, to) { \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK){ \ + return(S_OOPS); \ + } \ + } + +#define NULLSNARF(fd,s, to){ \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK) { \ + return(NULL); \ + } \ + } + +/* Look for any of the given patterns. We don't care which */ +static int +StonithLookFor(int fd, struct Etoken * tlist, int timeout) +{ + int rc; + char savebuf[512]; + + if ((rc = EXPECT_TOK(fd, tlist, timeout, savebuf, sizeof(savebuf) + , Debug)) < 0) { + LOG(PIL_CRIT, "Did not find string %s from " DEVICE "." + , tlist[0].string); + LOG(PIL_CRIT, "Received [%s]", savebuf); + } + return(rc); +} + +#ifndef DOESNT_USE_STONITHSCANLINE +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int +StonithScanLine(int fd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(fd, CRNL, timeout, buf, max, Debug) < 0) { + LOG(PIL_CRIT, "Could not read line from" DEVICE "."); + return(S_OOPS); + } + return(S_OK); +} +#endif + +#ifndef DOESNT_USE_STONITHKILLCOMM +static void +Stonithkillcomm(int *rdfd, int *wrfd, int *pid) +{ + if ((rdfd != NULL) && (*rdfd >= 0)) { + close(*rdfd); + *rdfd = -1; + } + if ((wrfd != NULL) && (*wrfd >= 0)) { + close(*wrfd); + *wrfd = -1; + } + if ((pid != NULL) && (*pid > 0)) { + STONITH_KILL(*pid, SIGKILL); + (void)waitpid(*pid, NULL, 0); + *pid = -1; + } +} +#endif diff --git a/lib/plugins/stonith/stonith_plugin_common.h b/lib/plugins/stonith/stonith_plugin_common.h new file mode 100644 index 0000000..dcdd7c8 --- /dev/null +++ b/lib/plugins/stonith/stonith_plugin_common.h @@ -0,0 +1,127 @@ +/* + * stonith_plugin_common.h: common macros easing the writing of STONITH + * plugins. Only a STONITH plugin should + * include this header! + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * 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 + * + */ +#ifndef _STONITH_PLUGIN_COMMON_H +#define _STONITH_PLUGIN_COMMON_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <libintl.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include <netdb.h> +#ifdef HAVE_TERMIO_H +# include <termio.h> +#endif +#ifdef HAVE_SYS_TERMIOS_H +#include <sys/termios.h> +#else +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#endif +#include <glib.h> + + +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +#define LOG(w...) PILCallLog(PluginImports->log, w) + +#define MALLOC PluginImports->alloc +#define REALLOC PluginImports->mrealloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree +#define EXPECT_TOK OurImports->ExpectToken +#define STARTPROC OurImports->StartProcess + +#ifdef MALLOCT +# undef MALLOCT +#endif +#define ST_MALLOCT(t) ((t *)(MALLOC(sizeof(t)))) + +#define N_(text) (text) +#define _(text) dgettext(ST_TEXTDOMAIN, text) + +#define WHITESPACE " \t\n\r\f" + +#ifndef MIN +/* some macros */ +# define MIN( i, j ) ( i > j ? j : i ) +#endif + +#define REPLSTR(s,v) { \ + if ((s) != NULL) { \ + FREE(s); \ + (s)=NULL; \ + } \ + (s) = STRDUP(v); \ + if ((s) == NULL) { \ + PILCallLog(PluginImports->log, \ + PIL_CRIT, "out of memory"); \ + } \ + } + +#ifndef DEVICE +#define DEVICE "Dummy" +#endif + +#define PIL_PLUGINTYPE STONITH_TYPE +#define PIL_PLUGINTYPE_S STONITH_TYPE_S + +#define ISCORRECTDEV(i) ((i)!= NULL \ + && ((struct pluginDevice *)(i))->pluginid == pluginid) + +#define ERRIFWRONGDEV(s, retval) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFWRONGDEV(s) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return; \ + } + +#define ISCONFIGED(i) (i->isconfigured) + +#define ERRIFNOTCONFIGED(s,retval) ERRIFWRONGDEV(s,retval); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFNOTCONFIGED(s) VOIDERRIFWRONGDEV(s); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return; \ + } + +#endif + diff --git a/lib/plugins/stonith/stonith_signal.h b/lib/plugins/stonith/stonith_signal.h new file mode 100644 index 0000000..99513f5 --- /dev/null +++ b/lib/plugins/stonith/stonith_signal.h @@ -0,0 +1,68 @@ +/* + * stonith_signal.h: signal handling routines to be used by stonith + * plugin libraries + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * 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 + * + */ +#ifndef _STONITH_SIGNAL_H +#define _STONITH_SIGNAL_H + +#include <signal.h> +#include <sys/signal.h> + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + (void)stonith_signal_set_simple_handler; + if(sigemptyset(&mask) < 0) { + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + return(-1); + } + + return(0); +} + +#define STONITH_SIGNAL(_sig, _handler) \ + stonith_signal_set_simple_handler((_sig), (_handler), NULL) +#ifdef HAVE_SIGIGNORE +#define STONITH_IGNORE_SIG(_sig) \ + sigignore((_sig)) +#else +#define STONITH_IGNORE_SIG(_sig) \ + STONITH_SIGNAL((_sig), SIG_IGN) +#endif +#define STONITH_DEFAULT_SIG(_sig) STONITH_SIGNAL((_sig), SIG_DFL) + +#define STONITH_KILL(_pid, _sig) kill((_pid), (_sig)) + +#endif /* _STONITH_SIGNAL_H */ diff --git a/lib/plugins/stonith/suicide.c b/lib/plugins/stonith/suicide.c new file mode 100644 index 0000000..b9d1db4 --- /dev/null +++ b/lib/plugins/stonith/suicide.c @@ -0,0 +1,274 @@ +/* File: suicide.c + * Description: Stonith module for suicide + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * 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 <config.h> +#include <sys/utsname.h> + +#define DEVICE "Suicide STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN suicide +#define PIL_PLUGIN_S "suicide" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * suicide_new(const char *); +static void suicide_destroy(StonithPlugin *); +static const char * const * suicide_get_confignames(StonithPlugin *); +static int suicide_set_config(StonithPlugin *, StonithNVpair*); +static const char * suicide_get_info(StonithPlugin * s, int InfoType); +static int suicide_status(StonithPlugin * ); +static int suicide_reset_req(StonithPlugin * s, int request + , const char * host); +static char ** suicide_hostlist(StonithPlugin *); + +static struct stonith_ops suicideOps ={ + suicide_new, /* Create new STONITH object */ + suicide_destroy, /* Destroy STONITH object */ + suicide_get_info, /* Return STONITH info string */ + suicide_get_confignames, /* Return configuration parameters */ + suicide_set_config, /* Set configuration */ + suicide_status, /* Return STONITH device status */ + suicide_reset_req, /* Request a reset */ + suicide_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 + , &suicideOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define REBOOT_COMMAND "nohup sh -c 'sleep 2; " REBOOT " " REBOOT_OPTIONS " </dev/null >/dev/null 2>&1' &" +#define POWEROFF_COMMAND "nohup sh -c 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS " </dev/null >/dev/null 2>&1' &" +/* +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +*/ + +/* + * Suicide STONITH device + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; +}; + +static const char * pluginid = "SuicideDevice-Stonith"; +static const char * NOTpluginid = "Suicide device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *suicideXML = + XML_PARAMETERS_BEGIN + XML_PARAMETERS_END; + +static int +suicide_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return S_OK; +} + +/* + * Return the list of hosts configured for this Suicide device + */ +static char ** +suicide_hostlist(StonithPlugin *s) +{ + char** ret = NULL; + struct utsname name; + + ERRIFWRONGDEV(s, NULL); + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return ret; + } + + ret = OurImports->StringToHostList(name.nodename); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + strdown(ret[0]); + + return ret; +} + +/* + * Suicide - reset or poweroff itself. + */ +static int +suicide_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = -1; + struct utsname name; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + LOG(PIL_CRIT, "As for suicide virtual stonith device, " + "reset request=%d is not supported", request); + return S_INVAL; + } + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return S_RESETFAIL ; + } + + if (strcmp(name.nodename, host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , name.nodename, host); + return S_RESETFAIL ; + } + + LOG(PIL_INFO, "Initiating suicide on host %s", host); + + rc = system( + request == ST_GENERIC_RESET ? REBOOT_COMMAND : POWEROFF_COMMAND); + + if (rc == 0) { + LOG(PIL_INFO, "Suicide stonith succeeded."); + return S_OK; + } else { + LOG(PIL_CRIT, "Suicide stonith failed."); + return S_RESETFAIL ; + } +} + +static const char * const * +suicide_get_confignames(StonithPlugin* p) +{ + /* Donnot need to initialize from external. */ + static const char * SuicideParams[] = { NULL }; + return SuicideParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +suicide_set_config(StonithPlugin* s, StonithNVpair* list) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + +static const char * +suicide_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + case ST_DEVICENAME: + ret = "suicide STONITH device"; + break; + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "Virtual device to reboot/powerdown itself.\n"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = suicideXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Suicide Stonith destructor... + */ +static void +suicide_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginid; + FREE(sd); +} + +/* Create a new suicide Stonith device */ +static StonithPlugin* +suicide_new(const char * subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->idinfo = DEVICE; + sd->sp.s_ops = &suicideOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/vacm.c b/lib/plugins/stonith/vacm.c new file mode 100644 index 0000000..ce6d041 --- /dev/null +++ b/lib/plugins/stonith/vacm.c @@ -0,0 +1,485 @@ + +/****************************************************************************** +* +* Copyright 2000 Sistina Software, Inc. +* Tiny bits Copyright 2000 Alan Robertson <alanr@unix.sh> +* Tiny bits Copyright 2000 Zac Sprackett, VA Linux Systems +* Tiny bits Copyright 2005 International Business Machines +* Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This is free software released under the GNU General Public License. +* There is no warranty for this software. See the file COPYING for +* details. +* +* See the file CONTRIBUTORS for a list of contributors. +* +* This file is maintained by: +* Michael C Tilstra <conrad@sistina.com> +* +* Becasue I have no device to test, now I just make it pass the compiling +* with vacm-2.0.5a. Please review before using. +* Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This module provides a driver for the VA Linux Cluster Manager. +* For more information on VACM, see http://vacm.sourceforge.net/ +* +* This module is rather poorly commented. But if you've read the +* VACM Manual, and looked at the code example they have, this +* should make pretty clean sense. (You obiviously should have +* looked at the other stonith source too) +* +*/ + +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define DEVICE "VA Linux Cluster Manager" + +#include "stonith_plugin_common.h" +#include "vacmclient_api.h" + +#define PIL_PLUGIN vacm +#define PIL_PLUGIN_S "vacm" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * vacm_new(const char *); +static void vacm_destroy(StonithPlugin *); +static const char * const * vacm_get_confignames(StonithPlugin *); +static int vacm_set_config(StonithPlugin *, StonithNVpair *); +static const char * vacm_getinfo(StonithPlugin * s, int InfoType); +static int vacm_status(StonithPlugin * ); +static int vacm_reset_req(StonithPlugin * s, int request, const char * host); +static char ** vacm_hostlist(StonithPlugin *); + +static struct stonith_ops vacmOps ={ + vacm_new, /* Create new STONITH object */ + vacm_destroy, /* Destroy STONITH object */ + vacm_getinfo, /* Return STONITH info string */ + vacm_get_confignames, /* Return configuration parameters */ + vacm_set_config, /* Set configuration */ + vacm_status, /* Return STONITH device status */ + vacm_reset_req, /* Request a reset */ + vacm_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 + , &vacmOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/*structs*/ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + void *h; /* a handle to the nexxus. */ + char * nexxus; + char * user; + char * passwd; +}; + +#define ST_NEXXUS "nexxus" + +static const char * pluginid = "VACMDevice-Stonith"; +static const char * NOTpluginid = "VACM device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_NEXXUS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_NEXXUS \ + XML_PARM_SHORTDESC_END + +#define XML_NEXXUS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The Nexxus component of the VA Cluster Manager" \ + XML_PARM_LONGDESC_END + +#define XML_NEXXUS_PARM \ + XML_PARAMETER_BEGIN(ST_NEXXUS, "string", "1", "1") \ + XML_NEXXUS_SHORTDESC \ + XML_NEXXUS_LONGDESC \ + XML_PARAMETER_END + +static const char *vacmXML = + XML_PARAMETERS_BEGIN + XML_NEXXUS_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/*funcs*/ +int +vacm_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:VERSION"; + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + /* If grabbing the nexxus version works, then the status must be ok. + * right? + */ + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + break; + } + if (!(tk = strtok(rcv,":"))) { /*NEXXUS*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of the below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; /* YEAH!! */ + }else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if(!strcmp(tk, "VERSION")) { + free(rcv); + continue; + } else { + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_OOPS; +} + +/* Better make sure the current group is correct. + * Can't think of a good way to do this. + */ +char ** +vacm_hostlist(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:NODE_LIST"; + char *rcv,*tk; + int rcvlen; + char ** hlst=NULL; + int hacnt=0, hrcnt=0; +#define MSTEP 20 + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice*)s; + + hlst = (char **)MALLOC(MSTEP * sizeof(char*)); + if (hlst == NULL) { + LOG(PIL_CRIT, "out of memory"); + return NULL; + } + hacnt=MSTEP; + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if(api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + goto HL_cleanup; + } + if(!(tk=strtok(rcv, ":"))) { /* NEXXUS */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* Job ID */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* JOB_* or NODELIST */ + goto HL_cleanup; + }else if( !strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return hlst; + }else if( !strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if( !strcmp(tk, "NODELIST")) { + if(!(tk = strtok(NULL,":"))) { /* group */ + goto HL_cleanup; + }else if((tk = strtok(NULL," \t\n\r"))) { /*Finally, a machine name.*/ + if( hrcnt >= (hacnt-1)) { /* grow array. */ + char **oldhlst = hlst; + hlst = (char **)REALLOC(hlst, (hacnt +MSTEP)*sizeof(char*)); + if( !hlst ) { + stonith_free_hostlist(oldhlst); + return NULL; + } + hacnt += MSTEP; + } + hlst[hrcnt] = STRDUP(tk); /* stuff the name. */ + hlst[hrcnt+1] = NULL; /* set next to NULL for looping */ + if (hlst[hrcnt] == NULL) { + stonith_free_hostlist(hlst); + return NULL; + } + strdown(hlst[hrcnt]); + hrcnt++; + } + }else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n",tk,rcv); + break; + } + } + +HL_cleanup: + stonith_free_hostlist(hlst); /* give the mem back */ + return NULL; +} + +#define SND_SIZE 256 +int +vacm_reset_req(StonithPlugin *s, int request, const char *host) +{ + struct pluginDevice *sd; + char snd[SND_SIZE]; /* god forbid its bigger than this */ + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + switch(request) { +#ifdef ST_POWERON + case ST_POWERON: + snprintf(snd, SND_SIZE, "EMP:POWER_ON:%s", host); + break; +#endif /*ST_POWERON*/ +#ifdef ST_POWEROFF + case ST_POWEROFF: + snprintf(snd, SND_SIZE, "EMP:POWER_OFF:%s", host); + break; +#endif /*ST_POWEROFF*/ + case ST_GENERIC_RESET: + snprintf(snd, SND_SIZE, "EMP:POWER_CYCLE:%s", host); + break; + default: + return S_INVAL; + } + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_RESETFAIL; + } + if (!(tk = strtok(rcv,":"))) { /*EMP*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of teh below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; + } else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + } else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + return S_RESETFAIL; + } else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_RESETFAIL; +} + +/* list => "nexxus:username:password" */ +static const char * const * +vacm_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_NEXXUS, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +static int +vacm_set_config(StonithPlugin *s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_NEXXUS, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + char *rcv; + int rcvlen; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->nexxus = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + /* When to initialize the sd->h */ + + if (api_nexxus_connect(sd->nexxus, sd->user, sd->passwd, &sd->h)<0){ + return S_OOPS; + } + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_OOPS; + } + if (strcmp(rcv, "NEXXUS_READY")) { + rc = S_BADCONFIG; + }else{ + rc = S_OK; + } + free(rcv); + + return(rc); +} + +/* + * The "vacmconf:" is in the conffile so that one file could be used for + * multiple device configs. This module will only look at the first line + * that starts with this token. All other line are ignored. (and thus + * could contain configs for other modules.) + * + * I don't think any other stonith modules do this currently. + */ +const char * +vacm_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = dgettext(ST_TEXTDOMAIN, "VACM"); + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "A driver for the VA Linux Cluster Manager."; + break; + + case ST_DEVICEURL: /* VACM's web site */ + ret = "http://vacm.sourceforge.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = vacmXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + +void +vacm_destroy(StonithPlugin *s) +{ + struct pluginDevice *sd; + + VOIDERRIFWRONGDEV(s); + sd = (struct pluginDevice*)s; + + if( sd->h ) { + api_nexxus_disconnect(sd->h); + } + + sd->pluginid = NOTpluginid; + if (sd->nexxus != NULL) { + FREE(sd->nexxus); + sd->nexxus = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + if (sd->passwd != NULL) { + FREE(sd->passwd); + sd->passwd = NULL; + } + + FREE(sd); +} + +static StonithPlugin * +vacm_new(const char *subplugin) +{ + struct pluginDevice *sd; + + sd = MALLOC(sizeof(struct pluginDevice)); + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->h = NULL; + sd->pluginid = pluginid; + sd->nexxus = NULL; + sd->user = NULL; + sd->passwd = NULL; + sd->idinfo = DEVICE; + sd->sp.s_ops = &vacmOps; + return &(sd->sp); /* same as "sd" */ +} diff --git a/lib/plugins/stonith/wti_mpc.c b/lib/plugins/stonith/wti_mpc.c new file mode 100644 index 0000000..548f91c --- /dev/null +++ b/lib/plugins/stonith/wti_mpc.c @@ -0,0 +1,856 @@ +/* + * Stonith module for WTI MPC (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * Modified for WTI MPC by Denis Chapligin <chollya@satgate.net>, SatGate, 2009 + * + * 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> + +/* device ID */ +#define DEVICE "WTI MPC" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN wti_mpc +#define PIL_PLUGIN_S "wti_mpc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * wti_mpc_new(const char *); +static void wti_mpc_destroy(StonithPlugin *); +static const char * const * wti_mpc_get_confignames(StonithPlugin *); +static int wti_mpc_set_config(StonithPlugin *, StonithNVpair *); +static const char * wti_mpc_getinfo(StonithPlugin * s, int InfoType); +static int wti_mpc_status(StonithPlugin * ); +static int wti_mpc_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_mpc_hostlist(StonithPlugin *); + +static struct stonith_ops wti_mpcOps ={ + wti_mpc_new, /* Create new STONITH object */ + wti_mpc_destroy, /* Destroy STONITH object */ + wti_mpc_getinfo, /* Return STONITH info string */ + wti_mpc_get_confignames, /* Get configuration parameters */ + wti_mpc_set_config, /* Set configuration */ + wti_mpc_status, /* Return STONITH device status */ + wti_mpc_reset_req, /* Request a reset */ + wti_mpc_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; + DEBUGCALL; + + 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 + , &wti_mpcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 5 +#define OUTLET_OFF 6 +#define OUTLET_REBOOT 7 + +/* oids */ +#define OID_IDENT ".1.3.6.1.2.1.1.5.0" + +#define OID_GROUP_NAMES_V1 ".1.3.6.1.4.1.2634.3.1.3.1.2.%u" +#define OID_GROUP_STATE_V1 ".1.3.6.1.4.1.2634.3.1.3.1.3.%i" + +#define OID_GROUP_NAMES_V3 ".1.3.6.1.4.1.2634.3.100.300.1.2.%u" +#define OID_GROUP_STATE_V3 ".1.3.6.1.4.1.2634.3.100.300.1.3.%i" + +#define MAX_OUTLETS 128 + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" +#define ST_MIBVERSION "mib-version" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + int mib_version; /* mib version to use */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* constant strings */ +static const char *pluginid = "WTI-MPC-Stonith"; +static const char *NOTpluginID = "WTI MPC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_MIBVERSION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MIBVERSION \ + XML_PARM_SHORTDESC_END + +#define XML_MIBVERSION_LONGDESC \ + XML_MIBVERSION_LONGDESC_BEGIN("en") \ + "Version number of MPC MIB that we should use. Valid values are 1 (for 1.44 firmware) and 3 (for 1.62 firmware and later)" \ + XML_PARM_LONGDESC_END + +#define XML_MIBVERSION_PARM \ + XML_PARAMETER_BEGIN(ST_MIBVERSION, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_MIBVERSION_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void MPC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *MPC_open(char *hostname, int port +, char *community); +static void *MPC_read(struct snmp_session *sptr, const char *objname +, int type); +static int MPC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +MPC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +MPC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + MPC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +MPC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +MPC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +wti_mpc_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = MPC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +wti_mpc_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,j+1); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,j+1); + break; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: using %s as group names oid", __FUNCTION__, objname); + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +wti_mpc_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int outlet; + int found_outlet=-1; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,outlet); + break; + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + + /* Ok, stop iterating over host list */ + found_outlet=outlet; + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (found_outlet == -1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + break; + default: break; + } + + /* Turn them all off */ + + /* prepare objnames */ + + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V3,found_outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V1,found_outlet); + break; + } + + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!MPC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, found_outlet); + return (S_RESETFAIL); + } + + return (S_OK); +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +wti_mpc_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, ST_MIBVERSION, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +wti_mpc_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + char * i; + int mo; + char objname[MAX_STRING]; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {ST_MIBVERSION, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + sd->mib_version = atoi(namestocopy[3].s_value); + PluginImports->mfree(namestocopy[3].s_value); + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("wti_mpc"); + + /* now try to get a snmp session */ + if ((sd->sptr = MPC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of groups from the mpc */ + sd->num_outlets=0; + /* We scan goup names table starting from 1 to MAX_OUTLETS */ + /* and increase num_outlet counter on every group entry with name */ + /* first entry without name is the mark of the end of the group table */ + for (mo=1;mo<MAX_OUTLETS;mo++) { + switch (sd->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,mo); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,mo); + break; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: used for groupTable retrieval: %s." + , __FUNCTION__, objname); + } + + if ((i = MPC_read(sd->sptr, objname, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + if (strlen(i)) { + /* store the number of outlets */ + sd->num_outlets++; + } else { + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +wti_mpc_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "WTI MPC (via SNMP)\n" + "The WTI MPC can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +wti_mpc_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +wti_mpc_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->mib_version=1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &wti_mpcOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/wti_nps.c b/lib/plugins/stonith/wti_nps.c new file mode 100644 index 0000000..f0b81f7 --- /dev/null +++ b/lib/plugins/stonith/wti_nps.c @@ -0,0 +1,813 @@ +/* + * + * Copyright 2001 Mission Critical Linux, Inc. + * + * All Rights Reserved. + */ +/* + * Stonith module for WTI Network Power Switch Devices (NPS-xxx) + * Also supports the WTI Telnet Power Switch Devices (TPS-xxx) + * + * Copyright 2001 Mission Critical Linux, Inc. + * author: mike ledoux <mwl@mclinux.com> + * author: Todd Wheeling <wheeling@mclinux.com> + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Further hurt by Lon <lhh@redhat.com>, Red Hat, 2005 + * + * Supported WTI devices: + * NPS-115 + * NPS-230 + * IPS-15 + * IPS-800 + * IPS-800-CE + * NBB-1600 + * NBB-1600-CE + * TPS-2 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * 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 + * + */ + +/* Observations/Notes + * + * 1. The WTI Network Power Switch, unlike the BayTech network power switch, + * accpets only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the NPS will + * result in a connection refused/closed failure. In a cluster environment + * or other environment utilizing polling/monitoring of the NPS + * (from multiple nodes), this can clearly cause problems. Obviously the + * more nodes and the shorter the polling interval, the more frequently such + * errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the NPS became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include <lha_internal.h> +#define DEVICE "WTI Network Power Switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN wti_nps +#define PIL_PLUGIN_S "wti_nps" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_WTIPLUGINID 256 + +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * wti_nps_new(const char *); +static void wti_nps_destroy(StonithPlugin *); +static const char * const * wti_nps_get_confignames(StonithPlugin *); +static int wti_nps_set_config(StonithPlugin * , StonithNVpair * ); +static const char * wti_nps_get_info(StonithPlugin * s, int InfoType); +static int wti_nps_status(StonithPlugin * ); +static int wti_nps_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_nps_hostlist(StonithPlugin *); + +static struct stonith_ops wti_npsOps ={ + wti_nps_new, /* Create new STONITH object */ + wti_nps_destroy, /* Destroy STONITH object */ + wti_nps_get_info, /* Return STONITH info string */ + wti_nps_get_confignames,/* Return configration parameters */ + wti_nps_set_config, /* set configration */ + wti_nps_status, /* Return STONITH device status */ + wti_nps_reset_req, /* Request a reset */ + wti_nps_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; + +#include "stonith_expect_helpers.h" + +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 + , &wti_npsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have a NPS-110. This code has been tested with this switch. + * (Tested with NPS-230 and TPS-2 by lmb) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * passwd; +}; + +static const char * pluginid = "WTINPS-Stonith"; +static const char * NOTnpsid = "WTINPS device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *wti_npsXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + + +/* + * Different expect strings that we get from the WTI + * Network Power Switch + */ + +#define WTINPSSTR " Power Switch" +#define WTINBBSTR "Boot Bar" + +static struct Etoken password[] = { {"Password:", 0, 0}, {NULL,0,0}}; +static struct Etoken Prompt[] = { {"PS>", 0, 0} + , {"IPS>", 0, 0} + , {"BB>", 0, 0} + , {NULL,0,0}}; +static struct Etoken LoginOK[] = { {WTINPSSTR, 0, 0} + , {WTINBBSTR, 0, 0} + , {"Invalid password", 1, 0} + , {NULL,0,0} }; +static struct Etoken Separator[] = { {"-----+", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"rocessing - please wait", 0, 0} + , {"(Y/N):", 1, 0} + , {NULL,0,0}}; + +static int NPS_connect_device(struct pluginDevice * nps); +static int NPSLogin(struct pluginDevice * nps); +static int NPSNametoOutlet(struct pluginDevice*, const char * name, char **outlets); +static int NPSReset(struct pluginDevice*, char * outlets, const char * rebootid); +static int NPSLogout(struct pluginDevice * nps); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int NPS_onoff(struct pluginDevice*, const char * outlets, const char * unitid +, int request); +#endif + +/* Attempt to login up to 20 times... */ +static int +NPSRobustLogin(struct pluginDevice * nps) +{ + int rc = S_OOPS; + int j = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + for ( ; ; ) { + if (NPS_connect_device(nps) == S_OK) { + rc = NPSLogin(nps); + if (rc == S_OK) { + break; + } + } + if ((++j) == 20) { + break; + } + else { + sleep(1); + } + } + + return rc; +} + +/* Login to the WTI Network Power Switch (NPS) */ +static int +NPSLogin(struct pluginDevice * nps) +{ + char IDinfo[128]; + char * idptr = IDinfo; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Look for the unit type info */ + if (EXPECT_TOK(nps->rdfd, password, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", nps->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * Enter Password: + */ + + SEND(nps->wrfd, nps->passwd); + SEND(nps->wrfd, "\r"); + /* Expect "Network Power Switch vX.YY" */ + + switch (StonithLookFor(nps->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", nps->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", nps->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + return(S_OK); +} + +/* Log out of the WTI NPS */ + +static int +NPSLogout(struct pluginDevice* nps) +{ + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + /* + SEND(nps->wrfd, "/h\r"); + */ + /* Expect "PS>" */ + rc = StonithLookFor(nps->rdfd, Prompt, 5); + + /* "/x" is Logout, "/x,y" auto-confirms */ + SEND(nps->wrfd, "/x,y\r"); + + close(nps->wrfd); + close(nps->rdfd); + nps->wrfd = nps->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlets */ +static int +NPSReset(struct pluginDevice* nps, char * outlets, const char * rebootid) +{ + char unum[32]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send REBOOT command for given outlets */ + snprintf(unum, sizeof(unum), "/BOOT %s,y\r", outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(nps->rdfd, Processing, 5)) { + case 0: /* Got "Processing" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "PS>" */ + if (StonithLookFor(nps->rdfd, Prompt, 60) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", rebootid); + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +NPS_onoff(struct pluginDevice* nps, const char * outlets, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "/On" : "/Off"); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect prompt back */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %s,y\r", onoff, outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing"... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(nps->rdfd, Processing, 5) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + } + EXPECT(nps->rdfd, Prompt, 60); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to NPS outlet(s) %s turned %s", outlets, onoff); + + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +NPSNametoOutlet(struct pluginDevice* nps, const char * name, char **outlets) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + char buf[32]; + int left = 17; + int ret = -1; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if ((*outlets = (char *)MALLOC(left*sizeof(char))) == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(-1); + } + + strncpy(*outlets, "", left); + left = left - 1; /* ensure terminating '\0' */ + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----+" so we can skip over it... */ + EXPECT(nps->rdfd, Separator, 5); + + do { + NameMapping[0] = EOS; + SNARF(nps->rdfd, NameMapping, 5); + + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + *last = EOS; + --last; + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strncasecmp(name, sockname, 16) == 0) { + ret = sockno; + snprintf(buf, sizeof(buf), "%d ", sockno); + strncat(*outlets, buf, left); + left = left - strlen(buf); + } + } + } while (strlen(NameMapping) > 2 && left > 0); + + return(ret); +} + +static int +wti_nps_status(StonithPlugin *s) +{ + struct pluginDevice* nps; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(rc); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + return(NPSLogout(nps)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this NPS unit + */ + +static char ** +wti_nps_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nps; + unsigned int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + nps = (struct pluginDevice*) s; + if (NPSRobustLogin(nps) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(NULL); + } + + /* Expect "PS>" */ + NULLEXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(nps->rdfd, Separator, 5); + NULLEXPECT(nps->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(nps->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if (!strcmp(sockname,"(undefined)") || + !strcmp(sockname,"---")) { + /* lhh - skip undefined */ + continue; + } + if ((nm = STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memset(ret, 0, (numnames+1)*sizeof(char*)); + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)NPSLogout(nps); + + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + + return (NULL); +} + +/* + * Connect to the given NPS device. We should add serial support here + * eventually... + */ +static int +NPS_connect_device(struct pluginDevice * nps) +{ + int fd = OurImports->OpenStreamSocket(nps->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + nps->rdfd = nps->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +wti_nps_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + }else{ + char *outlets; + int noutlet; + + outlets = NULL; + noutlet = NPSNametoOutlet(nps, host, &outlets); + + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , nps->device, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + rc = NPS_onoff(nps, outlets, host, request); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; +#endif + case ST_GENERIC_RESET: + rc = NPSReset(nps, outlets, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + default: + rc = S_INVAL; + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + } + } + + lorc = NPSLogout(nps); + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +wti_nps_set_config(StonithPlugin * s, StonithNVpair *list) +{ + struct pluginDevice* nps; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nps->device = namestocopy[0].s_value; + nps->passwd = namestocopy[1].s_value; + return S_OK; +} + + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +wti_nps_get_confignames(StonithPlugin * p) +{ + static const char * names[] = { ST_IPADDR , ST_PASSWD , NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Get info about the stonith device + * + */ +static const char * +wti_nps_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nps; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nps = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: + ret = nps->idinfo; + break; + case ST_DEVICENAME: + ret = nps->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic (WTI) Network Power Switch Devices (NPS-xxx)\n" + "Also supports the WTI Telnet Power Switch Devices (TPS-xxx)\n" + "NOTE: The WTI Network Power Switch, accepts only " + "one (telnet) connection/session at a time."; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = wti_npsXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * WTI NPS Stonith destructor... + */ +static void +wti_nps_destroy(StonithPlugin *s) +{ + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + nps = (struct pluginDevice *)s; + + nps->pluginid = NOTnpsid; + if (nps->rdfd >= 0) { + close(nps->rdfd); + nps->rdfd = -1; + } + if (nps->wrfd >= 0) { + close(nps->wrfd); + nps->wrfd = -1; + } + if (nps->device != NULL) { + FREE(nps->device); + nps->device = NULL; + } + if (nps->passwd != NULL) { + FREE(nps->passwd); + nps->passwd = NULL; + } + FREE(nps); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +wti_nps_new(const char *subplugin) +{ + struct pluginDevice* nps = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (nps == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nps, 0, sizeof(*nps)); + nps->pluginid = pluginid; + nps->pid = -1; + nps->rdfd = -1; + nps->wrfd = -1; + nps->device = NULL; + nps->passwd = NULL; + nps->idinfo = DEVICE; + nps->sp.s_ops = &wti_npsOps; + + return &(nps->sp); +} + diff --git a/lib/stonith/Makefile.am b/lib/stonith/Makefile.am new file mode 100644 index 0000000..429e1d3 --- /dev/null +++ b/lib/stonith/Makefile.am @@ -0,0 +1,54 @@ +# +# Stonith: Shoot The Node In The Head +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## include files + +## binaries +sbin_PROGRAMS = stonith meatclient + +stonith_SOURCES = main.c + +stonith_LDADD = libstonith.la $(top_builddir)/lib/pils/libpils.la $(GLIBLIB) \ + $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/clplumbing/libplumbgpl.la +stonith_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ @LIBADD_INTL@ + +meatclient_SOURCES = meatclient.c +meatclient_LDADD = $(GLIBLIB) libstonith.la + +## libraries + +lib_LTLIBRARIES = libstonith.la + +libstonith_la_SOURCES = expect.c stonith.c st_ttylock.c +libstonith_la_LDFLAGS = -version-info 1:0:0 +libstonith_la_LIBADD = $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/replace/libreplace.la \ + $(GLIBLIB) + +helperdir = $(datadir)/$(PACKAGE_NAME) +helper_SCRIPTS = ha_log.sh + +EXTRA_DIST = $(helper_SCRIPTS) diff --git a/lib/stonith/README b/lib/stonith/README new file mode 100644 index 0000000..6b98ef9 --- /dev/null +++ b/lib/stonith/README @@ -0,0 +1,31 @@ +The STONITH module (a.k.a. STOMITH) provides an extensible interface +for remotely powering down a node in the cluster. The idea is quite simple: +When the software running on one machine wants to make sure another +machine in the cluster is not using a resource, pull the plug on the other +machine. It's simple and reliable, albiet admittedly brutal. + +Here's an example command line invocation used to power off a machine +named 'nodeb'. The parameters are dependent on the type of device you +are using for this capability. + +stonith -t rps10 -p "/dev/ttyS5 nodeb 0 " nodeb + +Currently supported devices: + + apcsmart: APCSmart (tested with 2 old 900XLI) + baytech: Baytech RPC5 + meatware: Alerts an operator to manually turn off a device. + nw_rpc100s: Micro Energetics Night/Ware RPC100S + rps10: Western Telematics RPS10 + vacm_stonith: VA Linux Cluster Manager (see README.vacm) + + +To see the parameter syntax for a module, run the 'stonith' command and omit the +-p parameter. For example: + +$ /usr/sbin/stonith -t rps10 test + +stonith: Invalid config file for rps10 device. +stonith: Config file syntax: <serial_device> <node> <outlet> [ <node> <outlet> [...] ] +All tokens are white-space delimited. +Blank lines and lines beginning with # are ignored diff --git a/lib/stonith/expect.c b/lib/stonith/expect.c new file mode 100644 index 0000000..bb1f818 --- /dev/null +++ b/lib/stonith/expect.c @@ -0,0 +1,539 @@ +/* + * 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 * const * hostlist) +{ + int hlleng = 1; + const char * const * 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; + 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 +}; diff --git a/lib/stonith/ha_log.sh b/lib/stonith/ha_log.sh new file mode 100755 index 0000000..73093f0 --- /dev/null +++ b/lib/stonith/ha_log.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# +# ha_log.sh for stonith external plugins +# (equivalent to ocf_log in ocf-shellfuncs in resource-agents) +# +# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée +# All Rights Reserved. +# +# +# 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 +# + +# Build version: @GLUE_BUILD_VERSION@ + +PROG=`basename $0` + +: ${HA_DATEFMT=+"%b %d %T"} +: ${HA_LOGD=yes} +: ${HA_LOGTAG=""} +: ${HA_LOGFACILITY=daemon} +: ${HA_LOGFILE=""} +: ${HA_DEBUGLOG=""} +: ${HA_debug="0"} + +hadate() { + date "+$HA_DATEFMT" +} + +level_pres() { + case "$1" in + crit) echo "CRIT";; + err|error) echo "ERROR";; + warn|warning) echo "WARN";; + notice) echo "notice";; + info) echo "info";; + debug) echo "debug";; + *) + ha_log err "$PROG: unrecognized loglevel: $1" + exit 1 + ;; + esac +} + +set_logtag() { + # add parent pid to the logtag + if [ "$HA_LOGTAG" ]; then + if [ -n "$CRM_meta_st_device_id" ]; then + HA_LOGTAG="$HA_LOGTAG($CRM_meta_st_device_id)[$PPID]" + else + HA_LOGTAG="$HA_LOGTAG[$PPID]" + fi + fi +} + +ha_log() { + loglevel=$1 + shift + prn_level=`level_pres $loglevel` + msg="$prn_level: $@" + + if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then + return 0 + fi + + set_logtag + + # if we're connected to a tty, then output to stderr + if tty >/dev/null; then + if [ "$HA_LOGTAG" ]; then + echo "$HA_LOGTAG: $msg" + else + echo "$msg" + fi >&2 + return 0 + fi + + [ "x$HA_LOGD" = "xyes" ] && + cat<<EOF | ha_logger -t "$HA_LOGTAG" && return 0 +$msg +EOF + + if [ -n "$HA_LOGFACILITY" -a "$HA_LOGFACILITY" != none ]; then + logger -t "$HA_LOGTAG" -p $HA_LOGFACILITY.$loglevel "$msg" + fi + dest=${HA_LOGFILE:-$HA_DEBUGLOG} + if [ -n "$dest" ]; then + msg="$prn_level: `hadate` $@" + echo "$HA_LOGTAG: $msg" >> $dest + fi +} + +if [ $# -lt 2 ]; then + ha_log err "$PROG: not enough arguments [$#]" + exit 1 +fi + +loglevel="$1" +shift 1 +msg="$*" + +ha_log "$loglevel" "$msg" diff --git a/lib/stonith/main.c b/lib/stonith/main.c new file mode 100644 index 0000000..44e099f --- /dev/null +++ b/lib/stonith/main.c @@ -0,0 +1,727 @@ +/* + * Stonith: simple test program for exercising the Stonith API code + * + * 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <syslog.h> +#include <stonith/stonith.h> +#include <pils/plugin.h> +#include <clplumbing/cl_log.h> +#include <glib.h> +#include <libxml/entities.h> + +#define OPTIONS "c:F:p:t:T:EsnSlLmvhVd" +#define EQUAL '=' + +extern char * optarg; +extern int optind, opterr, optopt; + +static int debug = 0; + +#define LOG_TERMINAL 0 +#define LOG_CLLOG 1 +static int log_destination = LOG_TERMINAL; + +static const char META_TEMPLATE[] = +"<?xml version=\"1.0\"?>\n" +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" +"<resource-agent name=\"%s\">\n" +"<version>1.0</version>\n" +"<longdesc lang=\"en\">\n" +"%s\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">%s</shortdesc>\n" +"%s\n" +"<actions>\n" +"<action name=\"start\" timeout=\"20\" />\n" +"<action name=\"stop\" timeout=\"15\" />\n" +"<action name=\"status\" timeout=\"20\" />\n" +"<action name=\"monitor\" timeout=\"20\" interval=\"3600\" />\n" +"<action name=\"meta-data\" timeout=\"15\" />\n" +"</actions>\n" +"<special tag=\"heartbeat\">\n" +"<version>2.0</version>\n" +"</special>\n" +"</resource-agent>\n"; + +void version(void); +void usage(const char * cmd, int exit_status, const char * devtype); +void confhelp(const char * cmd, FILE* stream, const char * devtype); +void print_stonith_meta(Stonith * stonith_obj, const char *rsc_type); +void print_types(void); +void print_confignames(Stonith *s); + +void log_buf(int severity, char *buf); +void log_msg(int severity, const char * fmt, ...)G_GNUC_PRINTF(2,3); +void trans_log(int priority, const char * fmt, ...)G_GNUC_PRINTF(2,3); + +static int pil_loglevel_to_syslog_severity[] = { + /* Indices: <none>=0, PIL_FATAL=1, PIL_CRIT=2, PIL_WARN=3, + PIL_INFO=4, PIL_DEBUG=5 + */ + LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_WARNING, LOG_INFO, LOG_DEBUG + }; + +/* + * Note that we don't use the cl_log logging code because the STONITH + * command is intended to be shipped without the clplumbing libraries. + * + * :-( + * + * The stonith command has so far always been shipped along with + * the clplumbing library, so we'll use cl_log + * If that ever changes, we'll use something else + */ + +void +version() +{ + printf("stonith: %s (%s)\n", GLUE_VERSION, GLUE_BUILD_VERSION); + exit(0); +} + +void +usage(const char * cmd, int exit_status, const char * devtype) +{ + FILE *stream; + + stream = exit_status ? stderr : stdout; + + /* non-NULL devtype indicates help for specific device, so no usage */ + if (devtype == NULL) { + fprintf(stream, "usage:\n"); + fprintf(stream, "\t %s [-svh] " + "-L\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-n\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-m\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-lS\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-T {reset|on|off} nodename\n" + , cmd); + + fprintf(stream, "\nwhere:\n"); + fprintf(stream, "\t-L\tlist supported stonith device types\n"); + fprintf(stream, "\t-l\tlist hosts controlled by this stonith device\n"); + fprintf(stream, "\t-S\treport stonith device status\n"); + fprintf(stream, "\t-s\tsilent\n"); + fprintf(stream, "\t-v\tverbose\n"); + fprintf(stream, "\t-n\toutput the config names of stonith-device-parameters\n"); + fprintf(stream, "\t-m\tdisplay meta-data of the stonith device type\n"); + fprintf(stream, "\t-h\tdisplay detailed help message with stonith device description(s)\n"); + } + + if (exit_status == 0) { + confhelp(cmd, stream, devtype); + } + + exit(exit_status); +} + +/* Thanks to Lorn Kay <lorn_kay@hotmail.com> for the confhelp code */ +void +confhelp(const char * cmd, FILE* stream, const char * devtype) +{ + char ** typelist; + char ** this; + Stonith * s; + int devfound = 0; + + + /* non-NULL devtype indicates help for specific device, so no header */ + if (devtype == NULL) { + fprintf(stream + , "\nSTONITH -t device types and" + " associated configuration details:\n"); + } + + typelist = stonith_types(); + + if (typelist == NULL) { + fprintf(stderr, + "Failed to retrieve list of STONITH modules!\n"); + return; + } + for(this=typelist; *this && !devfound; ++this) { + const char * SwitchType = *this; + const char * cres; + const char * const * pnames; + + + if ((s = stonith_new(SwitchType)) == NULL) { + fprintf(stderr, "Invalid STONITH type %s(!)\n" + , SwitchType); + continue; + } + + if (devtype) { + if (strcmp(devtype, SwitchType)) { + continue; + } else { + devfound = 1; + } + } + + fprintf(stream, "\n\nSTONITH Device: %s - ", SwitchType); + + if ((cres = stonith_get_info(s, ST_DEVICEDESCR)) != NULL){ + fprintf(stream, "%s\n" + , cres); + } + + if ((cres = stonith_get_info(s, ST_DEVICEURL)) != NULL){ + fprintf(stream + , "For more information see %s\n" + , cres); + } + if (NULL == (pnames = stonith_get_confignames(s))) { + continue; + } + fprintf(stream + , "List of valid parameter names for %s STONITH device:\n" + , SwitchType); + for (;*pnames; ++pnames) { + fprintf(stream + , "\t%s\n", *pnames); + } + +#ifdef ST_CONFI_INFO_SYNTAX + fprintf(stream, "\nConfig info [-p] syntax for %s:\n\t%s\n" + , SwitchType, stonith_get_info(s, ST_CONF_INFO_SYNTAX)); +#else + fprintf(stream, "For Config info [-p] syntax" + ", give each of the above parameters in order as" + "\nthe -p value.\n" + "Arguments are separated by white space."); +#endif +#ifdef ST_CONFI_FILE_SYNTAX + fprintf(stream, "\nConfig file [-F] syntax for %s:\n\t%s\n" + , SwitchType, stonith->get_info(s, ST_CONF_FILE_SYNTAX)); +#else + fprintf(stream + , "\nConfig file [-F] syntax is the same as -p" + ", except # at the start of a line" + "\ndenotes a comment\n"); +#endif + + stonith_delete(s); s = NULL; + } + /* Note that the type list can't/shouldn't be freed */ + if (devtype && !devfound) { + fprintf(stderr, "Invalid device type: '%s'\n", devtype); + } + +} + +void +print_stonith_meta(Stonith * stonith_obj, const char *rsc_type) +{ + const char * meta_param = NULL; + const char * meta_longdesc = NULL; + const char * meta_shortdesc = NULL; + char *xml_meta_longdesc = NULL; + char *xml_meta_shortdesc = NULL; + static const char * no_parameter_info = "<!-- no value -->"; + + meta_longdesc = stonith_get_info(stonith_obj, ST_DEVICEDESCR); + if (meta_longdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no long description"); + meta_longdesc = no_parameter_info; + } + xml_meta_longdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc); + + meta_shortdesc = stonith_get_info(stonith_obj, ST_DEVICEID); + if (meta_shortdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no short description"); + meta_shortdesc = no_parameter_info; + } + xml_meta_shortdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc); + + meta_param = stonith_get_info(stonith_obj, ST_CONF_XML); + if (meta_param == NULL) { + fprintf(stderr, "stonithRA plugin: no list of parameters"); + meta_param = no_parameter_info; + } + + printf(META_TEMPLATE, + rsc_type, xml_meta_longdesc, xml_meta_shortdesc, meta_param); + + xmlFree(xml_meta_longdesc); + xmlFree(xml_meta_shortdesc); +} + +#define MAXNVARG 50 + +void +print_types() +{ + char ** typelist; + + typelist = stonith_types(); + if (typelist == NULL) { + log_msg(LOG_ERR, "Could not list Stonith types."); + }else{ + char ** this; + + for(this=typelist; *this; ++this) { + printf("%s\n", *this); + } + } +} + +void +print_confignames(Stonith *s) +{ + const char * const * names; + int i; + + names = stonith_get_confignames(s); + + if (names != NULL) { + for (i=0; names[i]; ++i) { + printf("%s ", names[i]); + } + } + printf("\n"); +} + +void +log_buf(int severity, char *buf) +{ + if (severity == LOG_DEBUG && !debug) + return; + if (log_destination == LOG_TERMINAL) { + fprintf(stderr, "%s: %s\n", prio2str(severity),buf); + } else { + cl_log(severity, "%s", buf); + } +} + +void +log_msg(int severity, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +void +trans_log(int priority, const char * fmt, ...) +{ + int severity; + va_list ap; + char buf[MAXLINE]; + + severity = pil_loglevel_to_syslog_severity[ priority % sizeof + (pil_loglevel_to_syslog_severity) ]; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +int +main(int argc, char** argv) +{ + char * cmdname; + int rc; + Stonith * s; + const char * SwitchType = NULL; + const char * optfile = NULL; + const char * parameters = NULL; + int reset_type = ST_GENERIC_RESET; + int verbose = 0; + int status = 0; + int silent = 0; + int listhosts = 0; + int listtypes = 0; + int listparanames = 0; + int params_from_env = 0; + + int c; + int errors = 0; + int argcount; + StonithNVpair nvargs[MAXNVARG]; + int nvcount=0; + int j; + int count = 1; + int help = 0; + int metadata = 0; + + /* The bladehpi stonith plugin makes use of openhpi which is + * threaded. The mix of memory allocation without thread + * initialization followed by g_thread_init followed by + * deallocating that memory results in segfault. Hence the + * following G_SLICE setting; see + * http://library.gnome.org/devel/glib/stable/glib-Memory-Slices.html#g-slice-alloc + */ + + setenv("G_SLICE", "always-malloc", 1); + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + + case 'c': count = atoi(optarg); + if (count < 1) { + fprintf(stderr + , "bad count [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'd': debug++; + break; + + case 'F': optfile = optarg; + break; + + case 'E': params_from_env = 1; + break; + + case 'h': help++; + break; + + case 'm': metadata++; + break; + + case 'l': ++listhosts; + break; + + case 'L': ++listtypes; + break; + + case 'p': parameters = optarg; + break; + + case 's': ++silent; + break; + + case 'S': ++status; + break; + + case 't': SwitchType = optarg; + break; + + case 'T': if (strcmp(optarg, "on")== 0) { + reset_type = ST_POWERON; + }else if (strcmp(optarg, "off")== 0) { + reset_type = ST_POWEROFF; + }else if (strcmp(optarg, "reset")== 0) { + reset_type = ST_GENERIC_RESET; + }else{ + fprintf(stderr + , "bad reset type [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'n': ++listparanames; + break; + + case 'v': ++verbose; + break; + + case 'V': version(); + break; + + default: ++errors; + break; + } + } + + /* if we're invoked by stonithd, log through cl_log */ + if (!isatty(fileno(stdin))) { + log_destination = LOG_CLLOG; + cl_log_set_entity("stonith"); + cl_log_enable_stderr(debug?TRUE:FALSE); + cl_log_set_facility(HA_LOG_FACILITY); + + /* Use logd if it's enabled by heartbeat */ + cl_inherit_logging_environment(0); + } + + if (help && !errors) { + usage(cmdname, 0, SwitchType); + } + if (debug) { + PILpisysSetDebugLevel(debug); + setenv("HA_debug","2",0); + } + if ((optfile && parameters) || (optfile && params_from_env) + || (params_from_env && parameters)) { + fprintf(stderr + , "Please use just one of -F, -p, and -E options\n"); + usage(cmdname, 1, NULL); + } + + /* + * Process name=value arguments on command line... + */ + for (;optind < argc; ++optind) { + char * eqpos; + if ((eqpos=strchr(argv[optind], EQUAL)) == NULL) { + break; + } + if (parameters || optfile || params_from_env) { + fprintf(stderr + , "Cannot mix name=value and -p, -F, or -E " + "style arguments\n"); + usage(cmdname, 1, NULL); + } + if (nvcount >= MAXNVARG) { + fprintf(stderr + , "Too many name=value style arguments\n"); + exit(1); + } + nvargs[nvcount].s_name = argv[optind]; + *eqpos = EOS; + nvargs[nvcount].s_value = eqpos+1; + nvcount++; + } + nvargs[nvcount].s_name = NULL; + nvargs[nvcount].s_value = NULL; + + argcount = argc - optind; + + if (!(argcount == 1 || (argcount < 1 + && (status||listhosts||listtypes||listparanames||metadata)))) { + ++errors; + } + + if (errors) { + usage(cmdname, 1, NULL); + } + + if (listtypes) { + print_types(); + exit(0); + } + + if (SwitchType == NULL) { + log_msg(LOG_ERR,"Must specify device type (-t option)"); + usage(cmdname, 1, NULL); + } + s = stonith_new(SwitchType); + if (s == NULL) { + log_msg(LOG_ERR,"Invalid device type: '%s'", SwitchType); + exit(S_OOPS); + } + if (debug) { + stonith_set_debug(s, debug); + } + stonith_set_log(s, (PILLogFun)trans_log); + + if (!listparanames && !metadata && optfile == NULL && + parameters == NULL && !params_from_env && nvcount == 0) { + const char * const * names; + int needs_parms = 1; + + if (s != NULL && (names = stonith_get_confignames(s)) != NULL && names[0] == NULL) { + needs_parms = 0; + } + + if (needs_parms) { + fprintf(stderr + , "Must specify either -p option, -F option, -E option, or " + "name=value style arguments\n"); + if (s != NULL) { + stonith_delete(s); + } + usage(cmdname, 1, NULL); + } + } + + if (listparanames) { + print_confignames(s); + stonith_delete(s); + s=NULL; + exit(0); + } + + if (metadata) { + print_stonith_meta(s,SwitchType); + stonith_delete(s); + s=NULL; + exit(0); + } + + /* Old STONITH version 1 stuff... */ + if (optfile) { + /* Configure the Stonith object from a file */ + if ((rc=stonith_set_config_file(s, optfile)) != S_OK) { + log_msg(LOG_ERR + , "Invalid config file for %s device." + , SwitchType); +#if 0 + log_msg(LOG_INFO, "Config file syntax: %s" + , s->s_ops->getinfo(s, ST_CONF_FILE_SYNTAX)); +#endif + stonith_delete(s); s=NULL; + exit(S_BADCONFIG); + } + }else if (params_from_env) { + /* Configure Stonith object from the environment */ + StonithNVpair * pairs; + if ((pairs = stonith_env_to_NVpair(s)) == NULL) { + fprintf(stderr + , "Invalid config info for %s device.\n" + , SwitchType); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else if (parameters) { + /* Configure Stonith object from the -p argument */ + StonithNVpair * pairs; + if ((pairs = stonith1_compat_string_to_NVpair + ( s, parameters)) == NULL) { + fprintf(stderr + , "Invalid STONITH -p parameter [%s]\n" + , parameters); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else{ + /* + * Configure STONITH device using cmdline arguments... + */ + if ((rc = stonith_set_config(s, nvargs)) != S_OK) { + const char * const * names; + int j; + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + + names = stonith_get_confignames(s); + + if (names != NULL) { + fprintf(stderr + , "Valid config names are:\n"); + + for (j=0; names[j]; ++j) { + fprintf(stderr + , "\t%s\n", names[j]); + } + } + stonith_delete(s); s=NULL; + exit(rc); + } + } + + + for (j=0; j < count; ++j) { + rc = S_OK; + + if (status) { + rc = stonith_get_status(s); + + if (!silent) { + if (rc == S_OK) { + log_msg((log_destination == LOG_TERMINAL) ? + LOG_INFO : LOG_DEBUG, + "%s device OK.", SwitchType); + }else{ + /* Uh-Oh */ + log_msg(LOG_ERR, "%s device not accessible." + , SwitchType); + } + } + } + + if (listhosts) { + char ** hostlist; + + hostlist = stonith_get_hostlist(s); + if (hostlist == NULL) { + log_msg(LOG_ERR, "Could not list hosts for %s." + , SwitchType); + rc = -1; + }else{ + char ** this; + + for(this=hostlist; *this; ++this) { + printf("%s\n", *this); + } + stonith_free_hostlist(hostlist); + } + } + + if (optind < argc) { + char *nodename; + nodename = g_strdup(argv[optind]); + strdown(nodename); + rc = stonith_req_reset(s, reset_type, nodename); + g_free(nodename); + } + } + stonith_delete(s); s = NULL; + return(rc); +} diff --git a/lib/stonith/meatclient.c b/lib/stonith/meatclient.c new file mode 100644 index 0000000..e95dc0e --- /dev/null +++ b/lib/stonith/meatclient.c @@ -0,0 +1,152 @@ +/* + * Stonith client for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder <gbinder@sysfive.com> + * + * This program is a rewrite of the "do_meatware" program by + * David C. Teigland <teigland@sistina.com> originally appeared + * in the GFS stomith meatware agent. + * + * 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stonith/stonith.h> +#include <glib.h> + +#define OPTIONS "c:w" + +void usage(const char * cmd); + +void +usage(const char * cmd) +{ + fprintf(stderr, "usage: %s -c node [-w]\n", cmd); + exit(S_INVAL); +} + +extern char * optarg; +extern int optind, opterr, optopt; +int +main(int argc, char** argv) +{ + char * cmdname; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatware.c as well */ + char * opthost = NULL; + int clearhost = 0; + + int c, argcount, waitmode = 0; + int errors = 0; + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + case 'c': opthost = optarg; + ++clearhost; + break; + case 'w': ++waitmode; + break; + default: ++errors; + break; + } + } + argcount = argc - optind; + if (!(argcount == 0) || !opthost) { + errors++; + } + + if (errors) { + usage(cmdname); + } + + strdown(opthost); + + if (clearhost) { + + int rc, fd; + char resp[3]; + + char line[256]; + char meatpipe[256]; + + gboolean waited=FALSE; + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, opthost); + + while(1) { + fd = open(meatpipe, O_WRONLY | O_NONBLOCK); + if (fd >= 0) + break; + if (!waitmode || (errno != ENOENT && errno != ENXIO)) { + if (waited) printf("\n"); + snprintf(line, sizeof(line) + , "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_BADHOST); + } + printf("."); fflush(stdout); waited=TRUE; + sleep(1); + } + if (waited) printf("\n"); + + printf("\nWARNING!\n\n" + "If node \"%s\" has not been manually power-cycled or " + "disconnected from all shared resources and networks, " + "data on shared disks may become corrupted and " + "migrated services might not work as expected.\n" + "Please verify that the name or address above " + "corresponds to the node you just rebooted.\n\n" + "PROCEED? [yN] ", opthost); + + rc = scanf("%s", resp); + + if (rc == 0 || rc == EOF || tolower(resp[0] != 'y')) { + printf("Meatware_client: operation canceled.\n"); + exit(S_INVAL); + } + + sprintf(line, "meatware reply %s", opthost); + + rc = write(fd, line, 256); + + if (rc < 0) { + sprintf(line, "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_OOPS); + } + + printf("Meatware_client: reset confirmed.\n"); + } + + exit(S_OK); +} diff --git a/lib/stonith/st_ttylock.c b/lib/stonith/st_ttylock.c new file mode 100644 index 0000000..adc918d --- /dev/null +++ b/lib/stonith/st_ttylock.c @@ -0,0 +1,225 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <clplumbing/cl_signal.h> +#include <stonith/st_ttylock.h> + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on HA_VARLOCKDIR (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +#define DEVDIR "/dev/" +#define DEVLEN (sizeof(DEVDIR)-1) + +static void raw_device (const char *dev, char *dest_name, size_t size); +static int DoLock(const char * prefix, const char *lockname); +static int DoUnlock(const char * prefix, const char *lockname); + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + +static void +raw_device (const char *serial_device, char *dest_name, size_t size) +{ + char* dp = dest_name; + const char* sp = serial_device+DEVLEN; + const char* dpend = dp + size - 1; + + while (*sp != '\0' && dp < dpend) { + if (isalnum((unsigned int)*sp)) + *dp++ = *sp; + sp++; + } + *dp = EOS; +} + +int +st_ttylock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoLock("LCK..", rawname)); +} + +/* + * Unlock a tty (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +st_ttyunlock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoUnlock("LCK..", rawname)); +} + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 + +static int +DoLock(const char * prefix, const char *lockname) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s" + , HA_VARLOCKDIR, prefix, lockname); + + snprintf(tf_name, sizeof(tf_name), "%s/tmp%lu-%s" + , HA_VARLOCKDIR, mypid, lockname); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && ((long)getpid() != pid) + && ((CL_KILL((pid_t)pid, 0) >= 0) + || errno != ESRCH)) { + /* tty is locked by existing (not + * necessarily running) process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + return -3; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * prefix, const char *lockname) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s", HA_VARLOCKDIR + , prefix, lockname); + return unlink(lf_name); +} diff --git a/lib/stonith/stonith.c b/lib/stonith/stonith.c new file mode 100644 index 0000000..4ced8c7 --- /dev/null +++ b/lib/stonith/stonith.c @@ -0,0 +1,636 @@ +/* + * Stonith API infrastructure. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <syslog.h> +#include <sys/wait.h> +#include <sys/param.h> +#include <dlfcn.h> +#include <dirent.h> +#include <glib.h> +#define ENABLE_PIL_DEFS_PRIVATE +#include <pils/plugin.h> +#include <pils/generic.h> +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + + +#define MALLOC StonithPIsys->imports->alloc +#ifdef MALLOCT +# undef MALLOCT +#endif +#define MALLOCT(t) (t*)(MALLOC(sizeof(t))) +#define REALLOC StonithPIsys->imports->mrealloc +#define STRDUP StonithPIsys->imports->mstrdup +#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;} + +#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args) + +#define EXTPINAME_S "external" +#define RHCSPINAME_S "rhcs" + +PILPluginUniv* StonithPIsys = NULL; +static GHashTable* Splugins = NULL; +static int init_pluginsys(void); +extern StonithImports stonithimports; + +static PILGenericIfMgmtRqst Reqs[] = +{ + {STONITH_TYPE_S, &Splugins, &stonithimports, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} +}; + +void PILpisysSetDebugLevel(int); +/* Initialize the plugin system... */ +static int +init_pluginsys(void) { + + if (StonithPIsys) { + return TRUE; + } + + + /* PILpisysSetDebugLevel(10); */ + StonithPIsys = NewPILPluginUniv(STONITH_MODULES); + + if (StonithPIsys) { + int rc = PILLoadPlugin(StonithPIsys, PI_IFMANAGER, "generic", Reqs); + if (rc != PIL_OK) { + fprintf(stderr, "generic plugin load failed: %d\n", rc); + DelPILPluginUniv(StonithPIsys); + StonithPIsys = NULL; + } + /*PILSetDebugLevel(StonithPIsys, PI_IFMANAGER, "generic", 10);*/ + }else{ + fprintf(stderr, "pi univ creation failed\n"); + } + return StonithPIsys != NULL; +} + +/* + * Create a new Stonith object of the requested type. + */ + +Stonith * +stonith_new(const char * type) +{ + StonithPlugin * sp = NULL; + struct stonith_ops* ops = NULL; + char * key; + char * subplugin; + char * typecopy; + + + if (!init_pluginsys()) { + return NULL; + } + + if ((typecopy = STRDUP(type)) == NULL) { + return NULL; + } + + if (((subplugin = strchr(typecopy, '/')) != NULL) && + (strncmp(EXTPINAME_S, typecopy, strlen(EXTPINAME_S)) == 0 || + strncmp(RHCSPINAME_S, typecopy, strlen(RHCSPINAME_S)) == 0)) { + *subplugin++ = 0; /* make two strings */ + } + + /* Look and see if it's already loaded... */ + + if (g_hash_table_lookup_extended(Splugins, typecopy + , (gpointer)&key, (gpointer)&ops)) { + /* Yes! Increment reference count */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, typecopy, 1); + + }else{ /* No. Try and load it... */ + if (PILLoadPlugin(StonithPIsys, STONITH_TYPE_S, typecopy, NULL) + != PIL_OK) { + FREE(typecopy); + return NULL; + } + + /* Look up the plugin in the Splugins table */ + if (!g_hash_table_lookup_extended(Splugins, typecopy + , (void*)&key, (void*)&ops)) { + /* OOPS! didn't find it(!?!)... */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S + , typecopy, -1); + FREE(typecopy); + return NULL; + } + } + + if (ops != NULL) { + sp = ops->new((const char *)(subplugin)); + if (sp != NULL) { + sp->s.stype = STRDUP(typecopy); + } + } + + FREE(typecopy); + return sp ? (&sp->s) : NULL; +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +/* + * Return list of STONITH types valid in stonith_new() + */ + +static char ** +get_plugin_list(const char *pltype) +{ + char ** typelist = NULL; + const char * const *extPI; + const char * const *p; + int numextPI, i; + Stonith * ext; + + /* let the external plugin return a list */ + if ((ext = stonith_new(pltype)) == NULL) { + LOG(PIL_CRIT, "Cannot create new external " + "plugin object"); + return NULL; + } + if ((extPI = stonith_get_confignames(ext)) == NULL) { + /* don't complain if rhcs plugins are not installed */ + if (strcmp(pltype, "rhcs")) + LOG(PIL_INFO, "Cannot get %s plugin subplugins", pltype); + stonith_delete(ext); + return NULL; + } + + /* count the external plugins */ + for (numextPI = 0, p = extPI; *p; p++, numextPI++); + + typelist = (char **) + MALLOC((numextPI+1)*sizeof(char *)); + if (typelist == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + return NULL; + } + + memset(typelist, 0, (numextPI + 1)*sizeof(char *)); + + /* copy external plugins */ + for (i = 0; i < numextPI; i++) { + int len = strlen(pltype) + + strlen(extPI[i]) + 2; + typelist[i] = MALLOC(len); + if (typelist[i] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + goto err; + } + snprintf(typelist[i], len, "%s/%s" + , pltype, extPI[i]); + } + + stonith_delete(ext); + + /* sort the list of plugin names */ + qsort(typelist, numextPI, sizeof(char *), qsort_string_cmp); + + return typelist; +err: + stonith_free_hostlist(typelist); + return NULL; +} + +char ** +stonith_types(void) +{ + int i, j, cur=0, rl_size, sub_pl = 0; + static char ** rl = NULL; + char ** new_list, **sub_list = NULL; + + if (!init_pluginsys()) { + return NULL; + } + + new_list = PILListPlugins(StonithPIsys, STONITH_TYPE_S, NULL); + if (new_list == NULL) { + return NULL; + } + for (i=0; new_list[i]; ++i) + ; /* count */ + rl_size = i+1; + + rl = (char**)MALLOC(rl_size * sizeof(char *)); + if (rl == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit; + } + + for (i=0; new_list[i]; ++i) { + /* look for 'external' and 'rhcs' plugins */ + if (strcmp(new_list[i], EXTPINAME_S) == 0) { + sub_list = get_plugin_list(EXTPINAME_S); + sub_pl = 1; + } else if (strcmp(new_list[i], RHCSPINAME_S) == 0) { + sub_list = get_plugin_list(RHCSPINAME_S); + sub_pl = 1; + } + if (sub_pl) { + if (sub_list) { + for (j=0; sub_list[j]; ++j) + ; /* count */ + rl_size += j; + rl = (char**)REALLOC(rl, rl_size*sizeof(char *)); + for (j=0; sub_list[j]; ++j) { + rl[cur++] = sub_list[j]; + } + FREE(sub_list); + sub_list = NULL; + } + sub_pl = 0; + } else { + rl[cur] = STRDUP(new_list[i]); + if (rl[cur] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit_mem; + } + cur++; + } + } + + rl[cur] = NULL; + goto types_exit; + +types_exit_mem: + stonith_free_hostlist(rl); + rl = NULL; +types_exit: + PILFreePluginList(new_list); + return rl; +} + +/* Destroy the STONITH object... */ + +void +stonith_delete(Stonith *s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + char * st = sp->s.stype; + sp->s_ops->destroy(sp); + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, st, -1); + /* destroy should not free it */ + FREE(st); + } +} + +const char * const * +stonith_get_confignames(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_confignames(sp); + } + return NULL; +} + +const char* +stonith_get_info(Stonith* s, int infotype) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_info(sp, infotype); + } + return NULL; + +} + +void +stonith_set_debug (Stonith* s, int debuglevel) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (StonithPIsys == NULL) { + return; + } + PILSetDebugLevel(StonithPIsys, STONITH_TYPE_S, sp->s.stype, debuglevel); +} + +void +stonith_set_log(Stonith* s, PILLogFun logfun) +{ + if (StonithPIsys == NULL) { + return; + } + PilPluginUnivSetLog(StonithPIsys, logfun); +} + +int +stonith_set_config(Stonith* s, StonithNVpair* list) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + int rc = sp->s_ops->set_config(sp, list); + if (rc == S_OK) { + sp->isconfigured = TRUE; + } + return rc; + } + return S_INVAL; +} + +/* + * FIXME: We really ought to support files with name=value type syntax + * on each line... + * + */ +int +stonith_set_config_file(Stonith* s, const char * configname) +{ + FILE * cfgfile; + + char line[1024]; + + if ((cfgfile = fopen(configname, "r")) == NULL) { + LOG(PIL_CRIT, "Cannot open %s", configname); + return(S_BADCONFIG); + } + while (fgets(line, sizeof(line), cfgfile) != NULL){ + int len; + + if (*line == '#' || *line == '\n' || *line == EOS) { + continue; + } + + /*remove the new line in the end*/ + len = strnlen(line, sizeof(line)-1); + if (line[len-1] == '\n'){ + line[len-1] = '\0'; + }else{ + line[len] = '\0'; + } + + fclose(cfgfile); + return stonith_set_config_info(s, line); + } + fclose(cfgfile); + return S_BADCONFIG; +} + +int +stonith_set_config_info(Stonith* s, const char * info) +{ + StonithNVpair* cinfo; + int rc; + cinfo = stonith1_compat_string_to_NVpair(s, info); + if (cinfo == NULL) { + return S_BADCONFIG; + } + rc = stonith_set_config(s, cinfo); + free_NVpair(cinfo); cinfo = NULL; + return rc; +} + +char** +stonith_get_hostlist(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_hostlist(sp); + } + return NULL; +} + +void +stonith_free_hostlist(char** hostlist) +{ + char ** here; + + for (here=hostlist; *here; ++here) { + FREE(*here); + } + FREE(hostlist); +} + +int +stonith_get_status(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_status(sp); + } + return S_INVAL; +} + +void +strdown(char *str) +{ + while( *str ) { + if( isupper(*str) ) + *str = tolower(*str); + str++; + } +} + +int +stonith_req_reset(Stonith* s, int operation, const char* node) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + char* nodecopy = STRDUP(node); + int rc; + if (nodecopy == NULL) { + return S_OOPS; + } + strdown(nodecopy); + + rc = sp->s_ops->req_reset(sp, operation, nodecopy); + FREE(nodecopy); + return rc; + } + return S_INVAL; +} +/* Stonith 1 compatibility: Convert a string to an NVpair set */ +StonithNVpair* +stonith1_compat_string_to_NVpair(Stonith* s, const char * str) +{ + /* We make some assumptions that the order of parameters in the + * result from stonith_get_confignames() matches that which + * was required from a Stonith1 module. + * Everything after the last delimiter is passed along as part of + * the final argument - white space and all... + */ + const char * const * config_names; + int n_names; + int j; + const char * delims = " \t\n\r\f"; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + size_t len; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + ret[j].s_value = NULL; + str += strspn(str, delims); + if (*str == EOS) { + goto freeandexit; + } + if (j == (n_names -1)) { + len = strlen(str); + }else{ + len = strcspn(str, delims); + } + if ((ret[j].s_value = MALLOC((len+1)*sizeof(char))) == NULL) { + goto freeandexit; + } + memcpy(ret[j].s_value, str, len); + ret[j].s_value[len] = EOS; + str += len; + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +StonithNVpair* +stonith_env_to_NVpair(Stonith* s) +{ + /* Read the config names values from the environment */ + const char * const * config_names; + int n_names; + int j; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + char *env_value; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + env_value = getenv(config_names[j]); + if (env_value) { + if ((ret[j].s_value = STRDUP(env_value)) == NULL) { + goto freeandexit; + } + } else { + ret[j].s_value = NULL; + } + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +static int NVcur = -1; +static int NVmax = -1; +static gboolean NVerr = FALSE; + +static void +stonith_walk_ghash(gpointer key, gpointer value, gpointer user_data) +{ + StonithNVpair* u = user_data; + + if (NVcur <= NVmax && !NVerr) { + u[NVcur].s_name = STRDUP(key); + u[NVcur].s_value = STRDUP(value); + if (u[NVcur].s_name == NULL || u[NVcur].s_value == NULL) { + /* Memory allocation error */ + NVerr = TRUE; + return; + } + ++NVcur; + }else{ + NVerr = TRUE; + } +} + + +StonithNVpair* +stonith_ghash_to_NVpair(GHashTable* stringtable) +{ + int hsize = g_hash_table_size(stringtable); + StonithNVpair* ret; + + if ((ret = (StonithNVpair*)MALLOC(sizeof(StonithNVpair)*(hsize+1))) == NULL) { + return NULL; + } + NVmax = hsize; + NVcur = 0; + ret[hsize].s_name = NULL; + ret[hsize].s_value = NULL; + g_hash_table_foreach(stringtable, stonith_walk_ghash, ret); + NVmax = NVcur = -1; + if (NVerr) { + free_NVpair(ret); + ret = NULL; + } + return ret; +} + +void +free_NVpair(StonithNVpair* nv) +{ + StonithNVpair* this; + + if (nv == NULL) { + return; + } + for (this=nv; this->s_name; ++this) { + FREE(this->s_name); + if (this->s_value) { + FREE(this->s_value); + } + } + FREE(nv); +} |