diff options
Diffstat (limited to 'lib/clplumbing')
39 files changed, 21215 insertions, 0 deletions
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 |