diff options
Diffstat (limited to 'src/libpcp')
78 files changed, 44265 insertions, 0 deletions
diff --git a/src/libpcp/GNUlocaldefs.32 b/src/libpcp/GNUlocaldefs.32 new file mode 100644 index 0000000..38bc43e --- /dev/null +++ b/src/libpcp/GNUlocaldefs.32 @@ -0,0 +1,4 @@ +LCFLAGS += -m32 -march=i386 +LLDFLAGS += -m32 -march=i386 +PCP_LIB_DIR=$(PCP_LIB32_DIR) +LIBPCP_ABIDIR=32 diff --git a/src/libpcp/GNUmakefile b/src/libpcp/GNUmakefile new file mode 100644 index 0000000..bb890a1 --- /dev/null +++ b/src/libpcp/GNUmakefile @@ -0,0 +1,31 @@ +# +# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# + +TOPDIR = ../.. + +include $(TOPDIR)/src/include/builddefs + +LSRCFILES = GNUlocaldefs.32 + +SUBDIRS = src $(PCP_ALTLIBS) + +default install : $(SUBDIRS) + $(SUBDIRS_MAKERULE) + +include $(BUILDRULES) + +default_pcp : default + +install_pcp : install + diff --git a/src/libpcp/src/AF.c b/src/libpcp/src/AF.c new file mode 100644 index 0000000..b2cb34b --- /dev/null +++ b/src/libpcp/src/AF.c @@ -0,0 +1,511 @@ +/* + * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2009 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* + * general purpose asynchronous event management + */ + +#include "pmapi.h" +#include "impl.h" + +#define MIN_ITIMER_USEC 100 + +typedef struct _qelt { + struct _qelt *q_next; + int q_afid; + struct timeval q_when; + struct timeval q_delta; + void *q_data; + void (*q_func)(int afid, void *data); +} qelt; + +static qelt *root; +static int afid = 0x8000; +static int block; +static void onalarm(int); + +/* + * Platform dependent routines follow, Windows is very different + * to POSIX platforms in terms of signals and timers. Note - we + * attempted to use CreateTimerQueue API on Windows, but it does + * not behave in the way we'd like unfortunately (QA slow_af.c - + * shows quite different results & its un-debuggable cos its all + * below the Win32 API). + */ +#ifdef IS_MINGW +VOID CALLBACK ontimer(LPVOID arg, DWORD lo, DWORD hi) +{ + onalarm(14); /* 14 == POSIX SIGALRM */ +} + +static HANDLE afblock; /* mutex protecting callback */ +static HANDLE aftimer; /* equivalent to ITIMER_REAL */ +static int afsetup; /* one-time-setup: done flag */ + +static void AFsetup(void) +{ + PM_LOCK(__pmLock_libpcp); + if (afsetup) { + PM_UNLOCK(__pmLock_libpcp); + return; + } + afsetup = 1; + afblock = CreateMutex(NULL, FALSE, NULL); + aftimer = CreateWaitableTimer(NULL, TRUE, NULL); + PM_UNLOCK(__pmLock_libpcp); +} +static void AFhold(void) +{ + AFsetup(); + WaitForSingleObject(afblock, INFINITE); +} +static void AFrelse(void) +{ + if (afsetup) + ReleaseMutex(afblock); +} +static void AFrearm(void) +{ + /* do nothing, callback is always "armed" (except when not setup) */ +} + +static void AFsetitimer(struct timeval *interval) +{ + LARGE_INTEGER duetime; + long long inc; + + AFsetup(); + + inc = interval->tv_sec * 10000000ULL; /* sec -> 100 nsecs */ + inc += (interval->tv_usec * 10ULL); /* used -> 100 nsecs */ + if (inc > 0) /* negative is relative, positive absolute */ + inc = -inc; /* we will always want this to be relative */ + duetime.QuadPart = inc; + SetWaitableTimer(aftimer, &duetime, 0, ontimer, NULL, FALSE); +} + +#else /* POSIX */ +static void AFsetitimer(struct timeval *interval) +{ + struct itimerval val; + + val.it_value = *interval; + val.it_interval.tv_sec = val.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &val, NULL); +} + +#if !defined(HAVE_SIGHOLD) +static int +sighold(int sig) +{ + sigset_t s; + + sigemptyset(&s); + sigaddset(&s, sig); + sigprocmask(SIG_BLOCK, &s, NULL); + + return 0; +} +#else +/* + * for Linux the prototype is hidden, even though the function is in + * libc + */ +extern int sighold(int); +#endif + +#if !defined(HAVE_SIGRELSE) +static int +sigrelse(int sig) +{ + sigset_t s; + + sigemptyset(&s); + sigaddset(&s, sig); + sigprocmask(SIG_UNBLOCK, &s, NULL); + return 0; +} +#else +/* + * for Linux the prototype is hidden, even though the function is in + * libc + */ +extern int sigrelse(int); +#endif + +static void AFhold(void) { sighold(SIGALRM); } +static void AFrelse(void) { sigrelse(SIGALRM); } +static void AFrearm(void) { signal(SIGALRM, onalarm); } + +#endif /* POSIX */ + + +/* + * Platform independent code follows + */ + +#ifdef PCP_DEBUG +static void +printdelta(FILE *f, struct timeval *tp) +{ + struct tm *tmp; + time_t tt = (time_t)tp->tv_sec; + + tmp = gmtime(&tt); + fprintf(stderr, "%02d:%02d:%02d.%06ld", tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (long)tp->tv_usec); +} +#endif +/* + * a := a + b for struct timevals + */ +static void +tadd(struct timeval *a, struct timeval *b) +{ + a->tv_usec += b->tv_usec; + if (a->tv_usec > 1000000) { + a->tv_usec -= 1000000; + a->tv_sec++; + } + a->tv_sec += b->tv_sec; +} + +/* + * a := a - b for struct timevals + */ +static void +tsub_real(struct timeval *a, struct timeval *b) +{ + a->tv_usec -= b->tv_usec; + if (a->tv_usec < 0) { + a->tv_usec += 1000000; + a->tv_sec--; + } + a->tv_sec -= b->tv_sec; +} + +/* + * a := a - b for struct timevals, but result is never less than zero + */ +static void +tsub(struct timeval *a, struct timeval *b) +{ + tsub_real(a, b); + if (a->tv_sec < 0) { + /* clip negative values at zero */ + a->tv_sec = 0; + a->tv_usec = 0; + } +} + +/* + * a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b + */ +static int +tcmp(struct timeval *a, struct timeval *b) +{ + int res; + res = (int)(a->tv_sec - b->tv_sec); + if (res == 0) + res = (int)(a->tv_usec - b->tv_usec); + return res; +} + +static void +enqueue(qelt *qp) +{ + qelt *tqp; + qelt *priorp; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + struct timeval now; + + __pmtimevalNow(&now); + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFenqueue " PRINTF_P_PFX "%p(%d, " PRINTF_P_PFX "%p) for ", + qp->q_func, qp->q_afid, qp->q_data); + __pmPrintStamp(stderr, &qp->q_when); + fputc('\n', stderr); + } +#endif + + for (tqp = root, priorp = NULL; + tqp != NULL && tcmp(&qp->q_when, &tqp->q_when) >= 0; + tqp = tqp->q_next) + priorp = tqp; + + if (priorp == NULL) { + qp->q_next = root; + root = qp; + } + else { + qp->q_next = priorp->q_next; + priorp->q_next = qp; + } +} + +static void +onalarm(int dummy) +{ + struct timeval now; + struct timeval tmp; + struct timeval interval; + qelt *qp; + + if (!block) + AFhold(); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmtimevalNow(&now); + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFonalarm(%d)\n", dummy); + } +#endif + if (root != NULL) { + /* something to do ... */ + while (root != NULL) { + /* compute difference between scheduled time and now */ + __pmtimevalNow(&now); + tmp = root->q_when; + tsub(&tmp, &now); + if (tmp.tv_sec == 0 && tmp.tv_usec <= 10000) { + /* + * within one 10msec tick, the time has passed for this one, + * execute the callback and reschedule + */ + + qp = root; + root = root->q_next; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFcallback " PRINTF_P_PFX "%p(%d, " PRINTF_P_PFX "%p)\n", + qp->q_func, qp->q_afid, qp->q_data); + } +#endif + qp->q_func(qp->q_afid, qp->q_data); + + if (qp->q_delta.tv_sec == 0 && qp->q_delta.tv_usec == 0) { + /* + * if delta is zero, this is a single-shot event, + * so do not reschedule it + */ + free(qp); + } + else { + /* + * avoid falling too far behind + * if the scheduled time is more than q_delta in the + * past we will never catch up ... better to skip some + * events to catch up ... + * + * <------------ next q_when range -----------------> + * + * cannot catchup | may catchup | on schedule + * | | + * --------------------|---------------|------------> time + * | | + * | +-- now + * +-- now - q_delta + */ + __pmtimevalNow(&now); + for ( ; ; ) { + tadd(&qp->q_when, &qp->q_delta); + tmp = qp->q_when; + tsub_real(&tmp, &now); + tadd(&tmp, &qp->q_delta); + if (tmp.tv_sec >= 0) + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFcallback event %d too slow, skip callback for ", qp->q_afid); + __pmPrintStamp(stderr, &qp->q_when); + fputc('\n', stderr); + } +#endif + } + enqueue(qp); + } + } + else + /* + * head of the queue (and hence all others) are too far in + * the future ... done for this time + */ + break; + } + + if (root == NULL) { + pmprintf("Warning: AF event queue is empty, nothing more will be scheduled\n"); + pmflush(); + } + else { + /* set itimer for head of queue */ + interval = root->q_when; + __pmtimevalNow(&now); + tsub(&interval, &now); + if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC) + /* use minimal delay (platform dependent) */ + interval.tv_usec = MIN_ITIMER_USEC; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFsetitimer for delta "); + printdelta(stderr, &interval); + fputc('\n', stderr); + } +#endif + AFsetitimer(&interval); + } + } + if (!block) { + AFrearm(); + AFrelse(); + } +} + +int +__pmAFregister(const struct timeval *delta, void *data, void (*func)(int, void *)) +{ + qelt *qp; + struct timeval now; + struct timeval interval; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_AF)) + return PM_ERR_THREAD; + + if (!block) + AFhold(); + if (afid == 0x8000 && !block) /* first time */ + AFrearm(); + if ((qp = (qelt *)malloc(sizeof(qelt))) == NULL) { + return -oserror(); + } + qp->q_afid = ++afid; + qp->q_data = data; + qp->q_delta = *delta; + qp->q_func = func; + __pmtimevalNow(&qp->q_when); + tadd(&qp->q_when, &qp->q_delta); + + enqueue(qp); + if (root == qp) { + /* we ended up at the head of the list, set itimer */ + interval = qp->q_when; + __pmtimevalNow(&now); + tsub(&interval, &now); + + if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC) + /* use minimal delay (platform dependent) */ + interval.tv_usec = MIN_ITIMER_USEC; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFsetitimer for delta "); + printdelta(stderr, &interval); + fputc('\n', stderr); + } +#endif + AFsetitimer(&interval); + } + + if (!block) + AFrelse(); + return qp->q_afid; +} + +int +__pmAFunregister(int afid) +{ + qelt *qp; + qelt *priorp; + struct timeval now; + struct timeval interval; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_AF)) + return PM_ERR_THREAD; + + if (!block) + AFhold(); + for (qp = root, priorp = NULL; qp != NULL && qp->q_afid != afid; qp = qp->q_next) + priorp = qp; + + if (qp == NULL) { + if (!block) + AFrelse(); + return -1; + } + + if (priorp == NULL) { + root = qp->q_next; + if (root != NULL) { + /* + * we removed the head of the queue, set itimer for the + * new head of queue + */ + interval = root->q_when; + __pmtimevalNow(&now); + tsub(&interval, &now); + if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC) + /* use minimal delay (platform dependent) */ + interval.tv_usec = MIN_ITIMER_USEC; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_AF) { + __pmPrintStamp(stderr, &now); + fprintf(stderr, " AFsetitimer for delta "); + printdelta(stderr, &interval); + fputc('\n', stderr); + } +#endif + AFsetitimer(&interval); + } + } + else + priorp->q_next = qp->q_next; + + free(qp); + + if (!block) + AFrelse(); + return 0; +} + +void +__pmAFblock(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_AF)) + return; + block = 1; + AFhold(); +} + +void +__pmAFunblock(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_AF)) + return; + block = 0; + AFrearm(); + AFrelse(); +} + +int +__pmAFisempty(void) +{ + return (root==NULL); +} diff --git a/src/libpcp/src/GNUlocaldefs.coverage b/src/libpcp/src/GNUlocaldefs.coverage new file mode 100644 index 0000000..9df0bef --- /dev/null +++ b/src/libpcp/src/GNUlocaldefs.coverage @@ -0,0 +1,11 @@ +# example QA build for libpcp ... to enable +# $ make clean +# $ ln GNUlocaldefs.coverage GNUlocaldefs +# $ make +# $ sudo make install +# this one turns on coverage hooks for gcov and ggcov and builds +# libpcp with fault injection enabled +# +CFLAGS += -fprofile-arcs -ftest-coverage -DPM_FAULT_INJECTION +LDLIBS += -lgcov -lpcp_pmda +LDIRT = *.gcov *.gcda *.gcno diff --git a/src/libpcp/src/GNUlocaldefs.debug b/src/libpcp/src/GNUlocaldefs.debug new file mode 100644 index 0000000..04004ce --- /dev/null +++ b/src/libpcp/src/GNUlocaldefs.debug @@ -0,0 +1,8 @@ +# example QA build for libpcp ... to enable +# $ make clean +# $ ln GNUlocaldefs.debug GNUlocaldefs +# $ make +# $ sudo make install +# this one turns optimization off and debug info on +# +CFLAGS_OPT = -O0 -g diff --git a/src/libpcp/src/GNUmakefile b/src/libpcp/src/GNUmakefile new file mode 100644 index 0000000..4142b03 --- /dev/null +++ b/src/libpcp/src/GNUmakefile @@ -0,0 +1,160 @@ +# +# Copyright (c) 2012-2014 Red Hat. +# Copyright (c) 2008 Aconex. All Rights Reserved. +# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# + +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs +-include ./GNUlocaldefs + +CFILES = connect.c context.c desc.c err.c fetch.c freeresult.c \ + help.c instance.c p_desc.c p_error.c p_fetch.c p_instance.c \ + p_profile.c p_result.c p_text.c p_pmns.c p_creds.c p_auth.c \ + pdu.c pdubuf.c pmns.c profile.c store.c units.c util.c ipc.c \ + sortinst.c logmeta.c logportmap.c logutil.c tz.c interp.c \ + checksum.c rtime.c tv.c spec.c fetchlocal.c optfetch.c AF.c \ + stuffvalue.c endian.c config.c auxconnect.c auxserver.c discovery.c \ + p_lcontrol.c p_lrequest.c p_lstatus.c logconnect.c logcontrol.c \ + connectlocal.c derive.c derive_fetch.c events.c lock.c hash.c \ + fault.c access.c getopt.c probe.c +HFILES = derive.h internal.h avahi.h probe.h +YFILES = getdate.y +VERSION_SCRIPT = exports + +LSRCFILES = check-statics $(VERSION_SCRIPT) + +ifeq "$(ENABLE_SECURE)" "true" +LLDLIBS += $(LIB_FOR_SSL) $(LIB_FOR_NSS) $(LIB_FOR_NSPR) $(LIB_FOR_SASL) +LCFLAGS += $(NSSCFLAGS) $(NSPRCFLAGS) $(SASLCFLAGS) +CFILES += secureserver.c secureconnect.c +else +LSRCFILES += secureserver.c secureconnect.c +endif + +ifeq "$(ENABLE_AVAHI)" "true" +LLDLIBS += $(LIB_FOR_AVAHI) +LCFLAGS += $(AVAHICFLAGS) +CFILES += avahi.c +else +LSRCFILES += avahi.c +endif + +ifneq "$(TARGET_OS)" "mingw" +CFILES += accounts.c +LSRCFILES += win32.c +else +CFILES += win32.c +LSRCFILES += accounts.c +LLDLIBS += -lpsapi -lws2_32 +endif + +ifeq "$(TARGET_OS)" "solaris" +# enables standards compliant thread-safe interfaces (accounts.c) +LCFLAGS += -D_POSIX_PTHREAD_SEMANTICS +endif + +ifeq "$(LIB_FOR_BASENAME)" "-lpcp" +# don't need to be linked to myself in this case! +LIB_FOR_BASENAME = +endif + +LLDLIBS += $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS) $(LIB_FOR_RT) + +LCFLAGS += -DLIBPCP_INTERNAL '-DEXEC_SUFFIX="$(EXECSUFFIX)"' \ + '-DDSO_SUFFIX="$(DSOSUFFIX)"' + +DSOVERSION = 3 +STATICLIBTARGET = libpcp.a +LIBTARGET = libpcp.$(DSOSUFFIX).$(DSOVERSION) +SYMTARGET = libpcp.$(DSOSUFFIX) libpcp.$(DSOSUFFIX).2 + +ifeq "$(PACKAGE_DISTRIBUTION)" "debian" +SYMTARGET = libpcp.$(DSOSUFFIX) +endif +ifeq "$(TARGET_OS)" "darwin" +LIBTARGET = libpcp.$(DSOVERSION).$(DSOSUFFIX) +SYMTARGET = libpcp.$(DSOSUFFIX) +endif +ifeq "$(TARGET_OS)" "mingw" +STATICLIBTARGET = +LIBTARGET = libpcp.$(DSOSUFFIX) +SYMTARGET = +endif +ifeq "$(ENABLE_SHARED)" "no" +LIBTARGET = +SYMTARGET = +endif + +LDIRT += $(SYMTARGET) $(YFILES:%.y=%.tab.?) getdate.h check.done + +base default : $(LIBTARGET) check.done $(SYMTARGET) $(STATICLIBTARGET) + +ifneq "$(SYMTARGET)" "" +$(SYMTARGET): + $(LN_S) -f $(LIBTARGET) $@ +endif + +include $(BUILDRULES) + +*.o: internal.h +rtime.o: getdate.h +derive.o derive_fetch.o: derive.h +util.o: $(TOPDIR)/src/include/pcp/pmdbg.h + +$(OBJECTS): $(TOPDIR)/src/include/pcp/pmapi.h \ + $(TOPDIR)/src/include/pcp/impl.h \ + $(TOPDIR)/src/include/pcp/platform_defs.h + +.NOTPARALLEL: +getdate.h getdate.tab.c: getdate.y + $(YACC) -d -b `basename $< .y` $< && cp `basename $@ .h`.tab.h $@ + +ifeq "$(TARGET_OS)" "mingw" +kernel_pmda_dso = windows +else +kernel_pmda_dso = $(TARGET_OS) +endif + +install : default +ifneq ($(LIBTARGET),) + $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET) +endif +ifneq ($(SYMTARGET),) + for tt in $(SYMTARGET); do \ + $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \ + done +endif +ifneq ($(STATICLIBTARGET),) + $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET) +endif + +default_pcp : default + +install_pcp : install + +$(TOPDIR)/src/pmns/stdpmid: + cd $(@D); $(MAKE) $(@F) + +# The library is thread-safe ... check-statics will force a build failure +# if there has been any change to the static variables and their disposition +# ... refer to check-statics to add exceptions and annotations for new +# cases +# +check.done: $(OBJECTS) + ./check-statics + touch check.done + +ifneq ($(LIBTARGET),) +$(LIBTARGET): $(VERSION_SCRIPT) +endif diff --git a/src/libpcp/src/access.c b/src/libpcp/src/access.c new file mode 100644 index 0000000..2bc99f0 --- /dev/null +++ b/src/libpcp/src/access.c @@ -0,0 +1,1875 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <limits.h> +#include <assert.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* Host access control list */ + +typedef struct { + char *hostspec; /* Host specification */ + __pmSockAddr *hostid; /* Partial host-id to match */ + __pmSockAddr *hostmask; /* Mask for wildcarding */ + int level; /* Level of wildcarding */ + unsigned int specOps; /* Mask of specified operations */ + unsigned int denyOps; /* Mask of disallowed operations */ + int maxcons; /* Max connections permitted (0 => no limit) */ + int curcons; /* Current # connections from matching clients */ +} hostinfo; + +static hostinfo *hostlist; +static int nhosts; +static int szhostlist; + +/* User access control list */ + +typedef struct { + char *username; /* User specification */ + __pmUserID userid; /* User identifier to match */ + unsigned int ngroups; /* Count of groups to which the user belongs */ + __pmGroupID *groupids; /* Names of groups to which the user belongs */ + unsigned int specOps; /* Mask of specified operations */ + unsigned int denyOps; /* Mask of disallowed operations */ + int maxcons; /* Max connections permitted (0 => no limit) */ + int curcons; /* Current # connections from matching clients */ +} userinfo; + +static userinfo *userlist; +static int nusers; +static int szuserlist; + +/* Group access control list */ + +typedef struct { + char *groupname; /* Group specification */ + __pmGroupID groupid; /* Group identifier to match */ + unsigned int nusers; /* Count of users in this group */ + __pmUserID *userids; /* Names of users in this group */ + unsigned int specOps; /* Mask of specified operations */ + unsigned int denyOps; /* Mask of disallowed operations */ + int maxcons; /* Max connections permitted (0 => no limit) */ + int curcons; /* Current # connections from matching clients */ +} groupinfo; + +static groupinfo *grouplist; +static int ngroups; +static int szgrouplist; + +/* Mask of the operations defined by the user of the routines */ +static unsigned int all_ops; /* mask of all operations specifiable */ + +/* This allows the set of valid operations to be specified. + * Operations must be powers of 2. + */ +int +__pmAccAddOp(unsigned int op) +{ + unsigned int i, mask; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + + /* op must not be zero or clash with existing ops */ + if (op == 0 || (op & all_ops)) + return -EINVAL; + + /* Find the lowest bit in op that is set (WORD_BIT from limits.h is the + * number of bits in an unsigned int) + */ + for (i = 0; i < WORD_BIT; i++) + if (op & (mask = 1 << i)) + break; + + /* only one bit may be set in op */ + if (op & ~mask) + return -EINVAL; + + all_ops |= mask; + return 0; +} + +/* Get the host id for this host. The host id is used when translating + * references to localhost into the host's real IP address during parsing of + * the access control section of the config file. It may also used when + * checking for incoming connections from localhost. + */ + +static int gotmyhostid; +static __pmHostEnt *myhostid; +static char myhostname[MAXHOSTNAMELEN+1]; + +/* + * Always called with __pmLock_libpcp already held, so accessing + * gotmyhostid, myhostname, myhostid and gethostname() call are all + * thread-safe. + */ +static int +getmyhostid(void) +{ + if (gethostname(myhostname, MAXHOSTNAMELEN) < 0) { + __pmNotifyErr(LOG_ERR, "gethostname failure\n"); + return -1; + } + myhostname[MAXHOSTNAMELEN-1] = '\0'; + + if ((myhostid = __pmGetAddrInfo(myhostname)) == NULL) { + if ((myhostid = __pmGetAddrInfo("localhost")) == NULL) { + __pmNotifyErr(LOG_ERR, + "__pmGetAddrInfo failure for both %s and localhost\n", + myhostname); + return -1; + } + } + gotmyhostid = 1; + return 0; +} + +/* Used for saving the current state of the access lists */ + +enum { HOSTS_SAVED = 0x1, USERS_SAVED = 0x2, GROUPS_SAVED = 0x4 }; +static int saved; +static hostinfo *oldhostlist; +static int oldnhosts; +static int oldszhostlist; +static userinfo *olduserlist; +static int oldnusers; +static int oldszuserlist; +static groupinfo *oldgrouplist; +static int oldngroups; +static int oldszgrouplist; + +/* Save the current access control lists. + * Returns 0 for success or a negative error code on error. + */ +int +__pmAccSaveLists(void) +{ + int sts, code = 0; + + if ((sts = __pmAccSaveHosts()) < 0) + code = sts; + if ((sts = __pmAccSaveUsers()) < 0) + code = sts; + if ((sts = __pmAccSaveGroups()) < 0) + code = sts; + return code; +} + +int +__pmAccSaveHosts(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (saved & HOSTS_SAVED) + return PM_ERR_TOOBIG; + + saved |= HOSTS_SAVED; + oldhostlist = hostlist; + oldnhosts = nhosts; + oldszhostlist = szhostlist; + hostlist = NULL; + nhosts = 0; + szhostlist = 0; + return 0; +} + +int +__pmAccSaveUsers(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (saved & USERS_SAVED) + return PM_ERR_TOOBIG; + + saved |= USERS_SAVED; + olduserlist = userlist; + oldnusers = nusers; + oldszuserlist = szuserlist; + userlist = NULL; + nusers = 0; + szuserlist = 0; + return 0; +} + +int +__pmAccSaveGroups(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (saved & GROUPS_SAVED) + return PM_ERR_TOOBIG; + + saved |= GROUPS_SAVED; + oldgrouplist = grouplist; + oldngroups = ngroups; + oldszgrouplist = szgrouplist; + grouplist = NULL; + ngroups = 0; + szgrouplist = 0; + return 0; +} + +/* Free the current access lists. These are done automatically by + * __pmAccRestoreLists so there is no need for them to be globally visible. + * A caller of these routines should never need to dispose of the access lists + * once they has been built. + */ +static void +accfreehosts(void) +{ + int i; + + if (szhostlist) { + for (i = 0; i < nhosts; i++) + if (hostlist[i].hostspec != NULL) + free(hostlist[i].hostspec); + free(hostlist); + } + hostlist = NULL; + nhosts = 0; + szhostlist = 0; +} + +static void +accfreeusers(void) +{ + int i; + + if (szuserlist) { + for (i = 1; i < nusers; i++) { + free(userlist[i].username); + if (userlist[i].ngroups) + free(userlist[i].groupids); + } + free(userlist); + } + userlist = NULL; + nusers = 0; + szuserlist = 0; +} + +static void +accfreegroups(void) +{ + int i; + + if (szgrouplist) { + for (i = 1; i < ngroups; i++) { + free(grouplist[i].groupname); + if (grouplist[i].nusers) + free(grouplist[i].userids); + } + free(grouplist); + } + grouplist = NULL; + ngroups = 0; + szgrouplist = 0; +} + +/* Restore the previously saved access lists. Any current list is freed. + * Returns 0 for success or a negative error code on error. + */ +int +__pmAccRestoreLists(void) +{ + int sts, code = 0; + + if ((sts = __pmAccRestoreHosts()) < 0) + code = sts; + if ((sts = __pmAccRestoreUsers()) < 0 && !code) + code = sts; + if ((sts = __pmAccRestoreGroups()) < 0 && !code) + code = sts; + return code; +} + +int +__pmAccRestoreHosts(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (!(saved & HOSTS_SAVED)) + return PM_ERR_TOOSMALL; + + accfreehosts(); + saved &= ~HOSTS_SAVED; + hostlist = oldhostlist; + nhosts = oldnhosts; + szhostlist = oldszhostlist; + return 0; +} + +int +__pmAccRestoreUsers(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (!(saved & USERS_SAVED)) + return PM_ERR_TOOSMALL; + + accfreeusers(); + saved &= ~USERS_SAVED; + userlist = olduserlist; + nusers = oldnusers; + szuserlist = oldszuserlist; + return 0; +} + +int +__pmAccRestoreGroups(void) +{ + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (!(saved & GROUPS_SAVED)) + return PM_ERR_TOOSMALL; + + accfreegroups(); + saved &= ~GROUPS_SAVED; + grouplist = oldgrouplist; + ngroups = oldngroups; + szgrouplist = oldszgrouplist; + return 0; +} + +/* Free the previously saved access lists. These should be called when the saved + * access lists are no longer required (typically because the new ones supercede + * the old, have been verified as valid and correct, etc). + */ +void +__pmAccFreeSavedLists(void) +{ + __pmAccFreeSavedHosts(); + __pmAccFreeSavedUsers(); + __pmAccFreeSavedGroups(); +} + +void +__pmAccFreeSavedHosts(void) +{ + int i; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + if (!(saved & HOSTS_SAVED)) + return; + + if (oldszhostlist) { + for (i = 0; i < oldnhosts; i++) + if (oldhostlist[i].hostspec != NULL) + free(oldhostlist[i].hostspec); + free(oldhostlist); + } + saved &= ~HOSTS_SAVED; +} + +void +__pmAccFreeSavedUsers(void) +{ + int i; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + if (!(saved & USERS_SAVED)) + return; + + if (oldszuserlist) { + for (i = 1; i < oldnusers; i++) { + free(olduserlist[i].username); + if (olduserlist[i].ngroups) + free(olduserlist[i].groupids); + } + free(olduserlist); + } + saved &= ~USERS_SAVED; +} + +void +__pmAccFreeSavedGroups(void) +{ + int i; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + if (!(saved & GROUPS_SAVED)) + return; + + if (oldszgrouplist) { + for (i = 1; i < oldngroups; i++) { + free(oldgrouplist[i].groupname); + if (oldgrouplist[i].nusers) + free(oldgrouplist[i].userids); + } + free(oldgrouplist); + } + saved &= ~GROUPS_SAVED; +} + +/* Build up strings representing the ip address and the mask. + * Compute the wildcard level as we go. + */ +static int +parseInetWildCard(const char *name, char *ip, char *mask) +{ + int level; + int ipIx, maskIx; + int i, n; + const char *p; + + /* Accept "*" or ".*" as the inet full wild card spec. */ + level = 4; + ipIx = maskIx = 0; + p = name; + if (*p == '.') { + ++p; + if (*p != '*') { + __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name); + return -EINVAL; + } + } + for (/**/; *p && *p != '*' ; p++) { + n = (int)strtol(p, (char **)&p, 10); + if ((*p != '.' && *p != '*') || n < 0 || n > 255) { + __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name); + return -EINVAL; + } + if (ipIx != 0) { + ipIx += sprintf(ip + ipIx, "."); + maskIx += sprintf(mask + maskIx, "."); + } + ipIx += sprintf(ip + ipIx, "%d", n); + maskIx += sprintf(mask + maskIx, "255"); + --level; + /* Check the wildcard level, 0 is exact match, 4 is most general */ + if (level < 1) { + __pmNotifyErr(LOG_ERR, "Too many dots in host pattern \"%s\"\n", name); + return -EINVAL; + } + } + /* Add zeroed components for the wildcarded levels. */ + for (i = 0; i < level; ++i) { + if (ipIx != 0) { + ipIx += sprintf(ip + ipIx, "."); + maskIx += sprintf(mask + maskIx, "."); + } + ipIx += sprintf(ip + ipIx, "0"); + maskIx += sprintf(mask + maskIx, "0"); + } + return level; +} + +static int +parseIPv6WildCard(const char *name, char *ip, char *mask) +{ + int level; + int ipIx, maskIx; + int emptyRegion; + int n; + const char *p; + + /* Accept ":*" as the IPv6 full wild card spec. Otherwise, + if the string starts with ':', then the second character must also be a ':' + which would form a region of zeroes of unspecified length. */ + level = 8; + emptyRegion = 0; + ipIx = maskIx = 0; + p = name; + if (*p == ':') { + ++p; + if (*p != '*') { + if (*p != ':') { + __pmNotifyErr(LOG_ERR, "Bad IPv6 address wildcard, %s\n", name); + return -EINVAL; + } + ipIx = sprintf(ip, ":"); + maskIx = sprintf(mask, ":"); + /* The second colon will be detected in the loop below. */ + } + } + + for (/**/; *p && *p != '*' ; p++) { + /* Check for an empty region. There can only be one. */ + if (*p == ':') { + if (emptyRegion) { + __pmNotifyErr(LOG_ERR, "Too many empty regions in host pattern \"%s\"\n", name); + return -EINVAL; + } + emptyRegion = 1; + ipIx += sprintf(ip + ipIx, ":"); + maskIx += sprintf(mask + maskIx, ":"); + } + else { + n = (int)strtol(p, (char **)&p, 16); + if ((*p != ':' && *p != '*') || n < 0 || n > 0xffff) { + __pmNotifyErr(LOG_ERR, "Bad IPv6 address wildcard, %s\n", name); + return -EINVAL; + } + if (ipIx != 0) { + ipIx += sprintf(ip + ipIx, ":"); + maskIx += sprintf(mask + maskIx, ":"); + } + ipIx += sprintf(ip + ipIx, "%x", n); + maskIx += sprintf(mask + maskIx, "ffff"); + } + --level; + /* Check the wildcard level, 0 is exact match, 8 is most general */ + if (level < 1) { + __pmNotifyErr(LOG_ERR, "Too many colons in host pattern \"%s\"\n", name); + return -EINVAL; + } + } + /* Add zeroed components for the wildcarded levels. + If the entire address is wildcarded then return the zero address. */ + if (level == 8 || (level == 7 && emptyRegion)) { + /* ":*" or "::*" */ + strcpy(ip, "::"); + strcpy(mask, "::"); + level = 8; + } + else if (emptyRegion) { + /* If there was an empty region, then we assume that the wildcard represents the final + segment of the spec only. */ + sprintf(ip + ipIx, ":0"); + sprintf(mask + maskIx, ":0"); + } + else { + /* no empty region, so use one to finish off the address and the mask */ + sprintf(ip + ipIx, "::"); + sprintf(mask + maskIx, "::"); + } + return level; +} + +static int +parseWildCard(const char *name, char *ip, char *mask) +{ + /* We need only handle inet and IPv6 wildcards here. Unix + * wildcards are handled separately. + * + * Names containing ':' are IPv6. The IPv6 full wildcard spec is ":*". + */ + if (strchr(name, ':') != NULL) + return parseIPv6WildCard(name, ip, mask); + + /* Names containing '.' are inet. The inet full wildcard spec ".*". */ + if (strchr(name, '.') != NULL) + return parseInetWildCard(name, ip, mask); + + __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name); + return -EINVAL; +} + +/* Information representing an access specification. */ +struct accessSpec { + char *name; + __pmSockAddr *hostid; + __pmSockAddr *hostmask; + int level; +}; + +static int +setAccessSpecAddresses(struct accessSpec *spec, const char *addr, const char *mask) +{ + /* Now create socket addresses for the address and mask. */ + spec->hostid = __pmStringToSockAddr(addr); + if (spec->hostid == NULL) { + __pmNotifyErr(LOG_ERR, "__pmStringToSockAddr failure\n"); + return -ENOMEM; + } + spec->hostmask = __pmStringToSockAddr(mask); + if (spec->hostmask == NULL) { + __pmNotifyErr(LOG_ERR, "__pmStringToSockAddr failure\n"); + __pmSockAddrFree(spec->hostid); + return -ENOMEM; + } + return 0; /* ok */ +} + +#if defined(HAVE_STRUCT_SOCKADDR_UN) +/* For the Unix spec: + * - On input: + * - We're expecting 'name' to be empty or an optional series of '/' followed by + * and optional '*'. + * - On output, within the 'spec' structure: + * - The path of the 'hostid' will be '/'. + * - The 'hostmask' will be a copy of the 'hostid'. + * - The 'level' will be 1 + * This sets up the spec to match the path of any unix domain socket. + */ +static int +getUnixSpec(const char *name, struct accessSpec *spec) +{ + const char *path; + size_t addrSize; + char rootPath[2]; + int sts; + + /* Accept any number of '/', as is done by parseProtocolSpec(). */ + for (path = name; *path == __pmPathSeparator(); ++path) + ; + + /* Accept a final '*'. */ + addrSize = strlen(path); + if (addrSize >= 1 && path[addrSize - 1] == '*') + --addrSize; + + /* If there is anything remaining, then it is a path, which we will ignore, with a + * warning. + */ + if (addrSize) + __pmNotifyErr(LOG_WARNING, "Ignoring the path in host pattern \"%s\"\n", name); + + /* Set the address and mask. */ + rootPath[0] = __pmPathSeparator(); + rootPath[1] = '\0'; + sts = setAccessSpecAddresses(spec, rootPath, rootPath); + if (sts < 0) + return sts; + + /* Complete the rest of the spec. + * Do this last since a valid name indicates a valid spec. + */ + spec->name = strdup("unix:"); + if (spec->name == NULL) + __pmNoMem("Unix host pattern name buffer", sizeof("unix:"), PM_FATAL_ERR); + spec->level = 1; + + return 0; /* ok */ +} +#endif /* defined(HAVE_STRUCT_SOCKADDR_UN) */ + +/* Construct the proper spec for the given wildcard. */ +static int +getWildCardSpec(const char *name, struct accessSpec *spec) +{ + char addr[INET6_ADDRSTRLEN]; + char mask[INET6_ADDRSTRLEN]; + int sts; + + /* Build up strings representing the ip address and the mask. Compute the wildcard + level as we go. */ + spec->level = parseWildCard(name, addr, mask); + if (spec->level < 0) + return spec->level; + + /* Set the address and mask. */ + if ((sts = setAccessSpecAddresses(spec, addr, mask)) < 0) + return sts; + + /* Do this last since a valid name indicates a valid spec. */ + spec->name = strdup(name); + return sts; /* ok */ +} + +/* Determine all of the access specs which result from the given name. */ +static struct accessSpec * +getHostAccessSpecs(const char *name, int *sts) +{ + struct accessSpec *specs; + size_t specSize; + size_t specIx; + size_t ix; + size_t need; + __pmSockAddr *myAddr; + __pmHostEnt *servInfo; + void *enumIx; + int family; + int isWildCard; + const char *realname; + const char *p; + + /* If the general wildcard ("*") is specified, then generate individual + * wildcards for inet, IPv6 (if supported) and unix domain sockets + * (if supported). "localhost" is covered by the inet and IPv6 wildcards. + */ + if (strcmp(name, "*") == 0) { + const char *ipv6 = __pmGetAPIConfig("ipv6"); + + /* Use calloc so that the final entries are zeroed, if not used. */ + specs = calloc(4, sizeof(*specs)); + if (specs == NULL) + __pmNoMem("Access Spec List", 4 * sizeof(*specs), PM_FATAL_ERR); + + /* The inet general wildcard. */ + specIx = 0; + getWildCardSpec(".*", &specs[specIx]); /* Guaranteed to succeed. */ + ++specIx; + + /* The IPv6 general wildcard. */ + if (ipv6 != NULL && strcmp(ipv6, "true") == 0) { + getWildCardSpec(":*", &specs[specIx]); /* Guaranteed to succeed. */ + ++specIx; + } + +#if defined(HAVE_STRUCT_SOCKADDR_UN) + /* The unix domain socket general wildcard. */ + getUnixSpec("*", &specs[specIx]); /* Guaranteed to succeed. */ +#endif + + return specs; + } + + /* If it is any other wildcard, make sure the '*' is at the end. */ + if ((p = strchr(name, '*')) != NULL) { + if (p[1] != '\0') { + __pmNotifyErr(LOG_ERR, + "Wildcard in host pattern \"%s\" is not at the end\n", + name); + *sts = -EINVAL; + return NULL; + } + isWildCard = 1; + } + else + isWildCard = 0; + + /* Initialize the specs array controls for general use. */ + specs = NULL; + specSize = 0; + specIx = 0; + + /* If a name of the form "local:[xxx]" is specified, then expand it to be + * "unix:[xxx]" + "localhost" in order to match the meaning of "local:[xxx]" + * for pmcd clients. + * If the spec is already "unix:[xxx] then leave it at that. + * Note that the above includes wildcards. + */ + if (strncmp(name, "local:", 6) == 0 || strncmp(name, "unix:", 5) == 0) { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + /* Use calloc so that the final entry is zeroed, if not used. */ + specSize = 2; + specs = calloc(specSize, sizeof(*specs)); + if (specs == NULL) + __pmNoMem("Access Spec List", specSize * sizeof(*specs), PM_FATAL_ERR); + + /* Process the equivalent unix domain socket spec. */ + if ((*sts = getUnixSpec(strchr(name, ':') + 1, &specs[specIx])) >= 0) { + /* If the spec was "unix:" then we're done. */ + if (name[0] == 'u') + return specs; + ++specIx; + } +#else + __pmNotifyErr(LOG_WARNING, "Host pattern \"%s\" is not supported. Using \"localhost\"\n", + name); +#endif + + /* Fall through to handle "localhost". */ + name = "localhost"; + } + else if (isWildCard) { + /* If any other wildcard is specified, then our list will contain that single item. + * Use calloc so that the final entry is zeroed. + */ + specs = calloc(2, sizeof(*specs)); + if (specs == NULL) + __pmNoMem("Access Spec List", 2 * sizeof(*specs), PM_FATAL_ERR); + *sts = getWildCardSpec(name, &specs[0]); + return specs; + } + + /* Assume we have a host name or address. Resolve it and contruct a list containing all of the + resolved addresses. If the name is "localhost", then resolve using the actual host name. */ + if (strcasecmp(name, "localhost") == 0) { + if (!gotmyhostid) { + if (getmyhostid() < 0) { + __pmNotifyErr(LOG_ERR, "Can't get host name/IP address, giving up\n"); + *sts = -EHOSTDOWN; + if (specs) + free(specs); + return NULL; /* should never happen! */ + } + } + realname = myhostname; + } + else + realname = name; + + *sts = -EHOSTUNREACH; + if ((servInfo = __pmGetAddrInfo(realname)) != NULL) { + /* Collect all of the resolved addresses. Check for the end of the list within the + loop since we need to add an empty entry and the code to grow the list is within the + loop. */ + enumIx = NULL; + for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx); + /**/; + myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) { + if (specIx == specSize) { + specSize = specSize == 0 ? 4 : specSize * 2; + need = specSize * sizeof(*specs); + specs = realloc(specs, need); + if (specs == NULL) { + __pmNoMem("Access Spec List", need, PM_FATAL_ERR); + } + } + /* No more addresses? */ + if (myAddr == NULL) { + specs[specIx].name = NULL; + break; + } + /* Don't add any duplicate entries. It causes false permission clashes. */ + for (ix = 0; ix < specIx; ++ix) { + if (__pmSockAddrCompare(myAddr, specs[ix].hostid) == 0) + break; + } + if (ix < specIx){ + __pmSockAddrFree(myAddr); + continue; + } + /* Add the new address and its corresponding mask. AF_UNIX socket addresses + * will not appear here. + */ + family = __pmSockAddrGetFamily(myAddr); + if (family == AF_INET) { + specs[specIx].hostmask = __pmStringToSockAddr("255.255.255.255"); + specs[specIx].level = 0; + } + else if (family == AF_INET6) { + specs[specIx].hostmask = __pmStringToSockAddr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + specs[specIx].level = 0; + } + else { + __pmNotifyErr(LOG_ERR, "Unsupported socket address family: %d\n", family); + __pmSockAddrFree(myAddr); + continue; + } + specs[specIx].hostid = myAddr; + specs[specIx].name = strdup(name); + *sts = 0; + ++specIx; + } + __pmHostEntFree(servInfo); + } + else { + __pmNotifyErr(LOG_ERR, "__pmGetAddrInfo(%s), %s\n", + realname, hoststrerror()); + } + + /* Return NULL if nothing was discovered. *sts is already set. */ + if (specIx == 0 && specs != NULL) { + free(specs); + specs = NULL; + } + return specs; +} + +/* Routine to add a group to the group access list with a specified set of + * permissions and a maximum connection limit. + * specOps is a mask. Only bits corresponding to operations specified by + * __pmAccAddOp have significance. A 1 bit indicates that the + * corresponding bit in the denyOps mask is to be used. A zero bit in + * specOps means the corresponding bit in denyOps should be ignored. + * denyOps is a mask where a 1 bit indicates that permission to perform the + * corresponding operation should be denied. + * maxcons is a maximum connection limit for individial groups. Zero means + * unspecified, which will allow unlimited connections or a subsequent + * __pmAccAddUser call with the same group to override maxcons. + * + * Returns a negated system error code on failure. + */ + +int +__pmAccAddGroup(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons) +{ + size_t need; + unsigned int nusers; + int i = 0, sts, wildcard, found = 0; + char errmsg[256]; + char *groupname; + __pmUserID *userids; + __pmGroupID groupid; + groupinfo *gp; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (specOps & ~all_ops) + return -EINVAL; + if (maxcons < 0) + return -EINVAL; + + wildcard = (strcmp(name, "*") == 0); + if (!wildcard) { + if ((sts = __pmGroupnameToID(name, &groupid)) < 0) { + __pmNotifyErr(LOG_ERR, "Failed to lookup group \"%s\": %s\n", + name, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + return -EINVAL; + } + + /* Search for a match to this group in the groups access table */ + for (i = 1; i < ngroups; i++) { + if (__pmEqualGroupIDs(groupid, grouplist[i].groupid)) { + found = 1; + break; + } + } + } + + /* Check and augment existing group access list entry for this groupid + * if a match was found, otherwise insert a new entry in list. + */ + if (found) { + /* If the specified operations overlap, they must agree */ + gp = &grouplist[i]; + if ((gp->maxcons && maxcons && gp->maxcons != maxcons) || + ((gp->specOps & specOps) && + ((gp->specOps & gp->denyOps) ^ (specOps & denyOps)))) { + __pmNotifyErr(LOG_ERR, + "Permission clash for group %s with earlier statement\n", + name); + return -EINVAL; + } + gp->specOps |= specOps; + gp->denyOps |= (specOps & denyOps); + if (maxcons) + gp->maxcons = maxcons; + } else { + /* Make the group access list larger if required */ + if (ngroups == szgrouplist) { + szgrouplist += 8; + need = szgrouplist * sizeof(groupinfo); + grouplist = (groupinfo *)realloc(grouplist, need); + if (grouplist == NULL) + __pmNoMem("AddGroup enlarge", need, PM_FATAL_ERR); + } + /* insert a permanent initial entry for '*' group wildcard */ + if (ngroups == 0) { + gp = &grouplist[0]; + memset(gp, 0, sizeof(*gp)); + gp->groupname = "*"; + gp->denyOps = gp->specOps = all_ops; + if (!wildcard) { /* if so, we're adding two entries */ + i = ++ngroups; + } + } + if (wildcard) { + i = 0; /* always the first entry, setup constants */ + gp = &grouplist[i]; /* for use when overwriting below */ + groupname = gp->groupname; + groupid = gp->groupid; + userids = gp->userids; + nusers = gp->nusers; + } else if ((sts = __pmGroupsUserIDs(name, &userids, &nusers)) < 0) { + __pmNotifyErr(LOG_ERR, + "Failed to lookup users in group \"%s\": %s\n", + name, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + return sts; + } else if ((groupname = strdup(name)) == NULL) { + __pmNoMem("AddGroup name", strlen(name)+1, PM_FATAL_ERR); + } + gp = &grouplist[i]; + gp->groupname = groupname; + gp->groupid = groupid; + gp->userids = userids; + gp->nusers = nusers; + gp->specOps = specOps; + gp->denyOps = specOps & denyOps; + gp->maxcons = maxcons; + gp->curcons = 0; + ngroups++; + } + + return 0; +} + +/* Routine to add a user to the user access list with a specified set of + * permissions and a maximum connection limit. + * specOps is a mask. Only bits corresponding to operations specified by + * __pmAccAddOp have significance. A 1 bit indicates that the + * corresponding bit in the denyOps mask is to be used. A zero bit in + * specOps means the corresponding bit in denyOps should be ignored. + * denyOps is a mask where a 1 bit indicates that permission to perform the + * corresponding operation should be denied. + * maxcons is a maximum connection limit for individial users. Zero means + * unspecified, which will allow unlimited connections or a subsequent + * __pmAccAddUser call with the same user to override maxcons. + * + * Returns a negated system error code on failure. + */ + +int +__pmAccAddUser(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons) +{ + size_t need; + unsigned int ngroups; + int i = 0, sts, wildcard, found = 0; + char errmsg[256]; + char *username; + __pmUserID userid; + __pmGroupID *groupids; + userinfo *up; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (specOps & ~all_ops) + return -EINVAL; + if (maxcons < 0) + return -EINVAL; + + wildcard = (strcmp(name, "*") == 0); + if (!wildcard) { + if ((sts = __pmUsernameToID(name, &userid)) < 0) { + __pmNotifyErr(LOG_ERR, "Failed to lookup user \"%s\": %s\n", + name, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + return -EINVAL; + } + + /* Search for a match to this user in the existing table of users. */ + for (i = 1; i < nusers; i++) { + if (__pmEqualUserIDs(userid, userlist[i].userid)) { + found = 1; + break; + } + } + } + + /* Check and augment existing user access list entry for this userid if a + * match was found otherwise insert a new entry in list. + */ + if (found) { + /* If the specified operations overlap, they must agree */ + up = &userlist[i]; + if ((up->maxcons && maxcons && up->maxcons != maxcons) || + ((up->specOps & specOps) && + ((up->specOps & up->denyOps) ^ (specOps & denyOps)))) { + __pmNotifyErr(LOG_ERR, + "Permission clash for user %s with earlier statement\n", + name); + return -EINVAL; + } + up->specOps |= specOps; + up->denyOps |= (specOps & denyOps); + if (maxcons) + up->maxcons = maxcons; + } else { + /* Make the user access list larger if required */ + if (nusers == szuserlist) { + szuserlist += 8; + need = szuserlist * sizeof(userinfo); + userlist = (userinfo *)realloc(userlist, need); + if (userlist == NULL) { + __pmNoMem("AddUser enlarge", need, PM_FATAL_ERR); + } + } + /* insert a permanent initial entry for '*' user wildcard */ + if (nusers == 0) { + up = &userlist[0]; + memset(up, 0, sizeof(*up)); + up->username = "*"; + up->denyOps = up->specOps = all_ops; + if (!wildcard) /* if so, we're adding two entries */ + i = ++nusers; + } + if (wildcard) { + i = 0; /* always the first entry, setup constants */ + up = &userlist[i]; /* for use when overwriting below */ + username = up->username; + userid = up->userid; + ngroups = up->ngroups; + groupids = up->groupids; + } else if ((sts = __pmUsersGroupIDs(name, &groupids, &ngroups)) < 0) { + __pmNotifyErr(LOG_ERR, + "Failed to lookup groups for user \"%s\": %s\n", + name, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + return sts; + } else if ((username = strdup(name)) == NULL) { + __pmNoMem("AddUser name", strlen(name)+1, PM_FATAL_ERR); + } + up = &userlist[i]; + up->username = username; + up->userid = userid; + up->groupids = groupids; + up->ngroups = ngroups; + up->specOps = specOps; + up->denyOps = specOps & denyOps; + up->maxcons = maxcons; + up->curcons = 0; + nusers++; + } + + return 0; +} + +/* Routine to add a host to the host access list with a specified set of + * permissions and a maximum connection limit. + * specOps is a mask. Only bits corresponding to operations specified by + * __pmAccAddOp have significance. A 1 bit indicates that the + * corresponding bit in the denyOps mask is to be used. A zero bit in + * specOps means the corresponding bit in denyOps should be ignored. + * denyOps is a mask where a 1 bit indicates that permission to perform the + * corresponding operation should be denied. + * maxcons is a maximum connection limit for clients on hosts matching the host + * id. Zero means unspecified, which will allow unlimited connections or + * a subsequent __pmAccAddHost call with the same host to override maxcons. + * + * Returns a negated system error code on failure. + */ + +int +__pmAccAddHost(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons) +{ + size_t need; + int i, sts = 0; + struct accessSpec *specs; + struct accessSpec *spec; + hostinfo *hp; + int found; + char *prevHost; + char *prevName; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + if (specOps & ~all_ops) + return -EINVAL; + if (maxcons < 0) + return -EINVAL; + + /* The specified name may result in more than one access specification. */ + specs = getHostAccessSpecs(name, &sts); + if (specs == NULL) + return sts; + + /* Search for a match to each spec in the existing table of hosts. We will either use + or free the host id, mask and name of each spec as we go. */ + prevHost = NULL; + prevName = NULL; + found = 0; + for (spec = specs; spec->name != NULL; ++spec) { + sts = 0; + for (i = 0; i < nhosts; i++) { + if (hostlist[i].level > spec->level) + break; + /* hostid AND level must match. Wildcarded IP addresses have zero in + * the unspecified components. Distinguish between 155.23.6.0 and + * 155.23.6.* or 155.23.0.0 and 155.23.* by wildcard level. IP + * addresses shouldn't have zero in last position but to deal with + * them just in case. + * This test also works for Unix Domain addresses and wildcards. + */ + if (__pmSockAddrCompare(spec->hostid, hostlist[i].hostid) == 0 && + spec->level == hostlist[i].level) { + sts = 1; + break; + } + } + + /* Check and augment existing host access list entry for this host id if a + * match was found (sts == 1) otherwise insert a new entry in list. + */ + if (sts == 1) { + __pmSockAddrFree(spec->hostid); + __pmSockAddrFree(spec->hostmask); + + /* If the specified operations overlap, they must agree */ + hp = &hostlist[i]; + if ((hp->maxcons && maxcons && hp->maxcons != maxcons) || + ((hp->specOps & specOps) && + ((hp->specOps & hp->denyOps) ^ (specOps & denyOps)))) { + /* Suppress duplicate messages. These can occur when a host resolves to more + than one address. */ + if (prevName == NULL || + strcmp(prevName, spec->name) != 0 || strcmp(prevHost, hp->hostspec) != 0) { + __pmNotifyErr(LOG_ERR, + "Permission clash for %s with earlier statement for %s\n", + spec->name, hp->hostspec); + if (prevName != NULL) { + free(prevName); + free(prevHost); + } + prevName = strdup(spec->name); + prevHost = strdup(hp->hostspec); + } + free(spec->name); + continue; + } + free(spec->name); + hp->specOps |= specOps; + hp->denyOps |= (specOps & denyOps); + if (maxcons) + hp->maxcons = maxcons; + } + else { + /* Make the host access list larger if required */ + if (nhosts == szhostlist) { + szhostlist += 8; + need = szhostlist * sizeof(hostinfo); + hostlist = (hostinfo *)realloc(hostlist, need); + if (hostlist == NULL) { + __pmNoMem("AddHost enlarge", need, PM_FATAL_ERR); + } + } + + /* Move any subsequent hosts down to make room for the new entry*/ + hp = &hostlist[i]; + if (i < nhosts) + memmove(&hostlist[i+1], &hostlist[i], + (nhosts - i) * sizeof(hostinfo)); + hp->hostspec = spec->name; + hp->hostid = spec->hostid; + hp->hostmask = spec->hostmask; + hp->level = spec->level; + hp->specOps = specOps; + hp->denyOps = specOps & denyOps; + hp->maxcons = maxcons; + hp->curcons = 0; + nhosts++; + } + /* Count the found hosts. */ + ++found; + } /* loop over addresses */ + + if (prevName != NULL) { + free(prevName); + free(prevHost); + } + free(specs); + return found != 0 ? 0 : -EINVAL; +} + +static __pmSockAddr ** +getClientIds(const __pmSockAddr *hostid, int *sts) +{ + __pmSockAddr **clientIds; + __pmSockAddr *myAddr; + size_t clientIx; + size_t clientSize; + size_t need; + void *enumIx; + + *sts = 0; + + /* If the address is not for "localhost", then return a list containing only + the given address. */ + if (! __pmSockAddrIsLoopBack(hostid)) { + clientIds = calloc(2, sizeof(*clientIds)); + if (clientIds == NULL) + __pmNoMem("Client Ids", 2 * sizeof(*clientIds), PM_FATAL_ERR); + clientIds[0] = __pmSockAddrDup(hostid); + return clientIds; + } + + /* Map "localhost" to the real IP addresses. Host access statements for + * localhost are mapped to the "real" IP addresses so that wildcarding works + * consistently. First get the real host address; + */ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (!gotmyhostid) + getmyhostid(); + + *sts = PM_ERR_PERMISSION; + if (gotmyhostid <= 0) { + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + PM_UNLOCK(__pmLock_libpcp); + + /* Now construct a list containing each address. Check for the end of the list within the + loop since we need to add an empty entry and the code to grow the list is within the + loop. */ + clientIds = NULL; + clientIx = 0; + clientSize = 0; + enumIx = NULL; + for (myAddr = __pmHostEntGetSockAddr(myhostid, &enumIx); + /**/; + myAddr = __pmHostEntGetSockAddr(myhostid, &enumIx)) { + if (clientIx == clientSize) { + clientSize = clientSize == 0 ? 4 : clientSize * 2; + need = clientSize * sizeof(*clientIds); + clientIds = realloc(clientIds, need); + if (clientIds == NULL) { + PM_UNLOCK(__pmLock_libpcp); + __pmNoMem("Client Ids", need, PM_FATAL_ERR); + } + } + /* No more addresses? */ + if (myAddr == NULL) { + clientIds[clientIx] = NULL; + break; + } + /* Add the new address and its corrsponding mask. */ + clientIds[clientIx] = myAddr; + ++clientIx; + *sts = 0; + } + + /* If no addresses were discovered, then return NULL. *sts is already set. */ + if (clientIx == 0) { + free(clientIds); + clientIds = NULL; + } + return clientIds; +} + +static void +freeClientIds(__pmSockAddr **clientIds) +{ + int i; + for (i = 0; clientIds[i] != NULL; ++i) + free(clientIds[i]); + free(clientIds); +} + +/* Called after accepting new client's connection to check that another + * connection from its host is permitted and to find which operations the + * client is permitted to perform. + * hostid is the address of the host that the client is running on + * denyOpsResult is a pointer to return the capability vector + */ +int +__pmAccAddClient(__pmSockAddr *hostid, unsigned int *denyOpsResult) +{ + int i; + int sts; + hostinfo *hp; + hostinfo *lastmatch = NULL; + int clientIx; + __pmSockAddr **clientIds; + __pmSockAddr *clientId; + __pmSockAddr *matchId; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + + *denyOpsResult = 0; /* deny nothing == allow all */ + if (nhosts == 0) /* No access controls => allow all */ + return 0; + + /* There could be more than one address associated with this host.*/ + clientIds = getClientIds(hostid, &sts); + if (clientIds == NULL) + return sts; + + /* Accumulate permissions for each client address. */ + for (clientIx = 0; clientIds[clientIx] != NULL; ++clientIx) { + clientId = clientIds[clientIx]; + for (i = nhosts - 1; i >= 0; i--) { + hp = &hostlist[i]; + /* At a minumum, the addresses must be from the same family. */ + if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) { + matchId = __pmSockAddrDup(clientId); + __pmSockAddrMask(matchId, hp->hostmask); + if (__pmSockAddrCompare(matchId, hp->hostid) == 0) { + /* Clobber specified ops then set. Leave unspecified ops alone. */ + *denyOpsResult &= ~hp->specOps; + *denyOpsResult |= hp->denyOps; + lastmatch = hp; + } + __pmSockAddrFree(matchId); + } + } + /* no matching entry in hostlist => allow all */ + + /* If no operations are allowed, disallow connection */ + if (*denyOpsResult == all_ops) { + freeClientIds(clientIds); + return PM_ERR_PERMISSION; + } + + /* Check for connection limit */ + if (lastmatch != NULL && lastmatch->maxcons && + lastmatch->curcons >= lastmatch->maxcons) { + + *denyOpsResult = all_ops; + freeClientIds(clientIds); + return PM_ERR_CONNLIMIT; + } + + /* Increment the count of current connections for ALL host specs in the + * host access list that match the client's IP address. A client may + * contribute to several connection counts because of wildcarding. + */ + for (i = 0; i < nhosts; i++) { + hp = &hostlist[i]; + /* At a minumum, the addresses must be from the same family. */ + if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) { + matchId = __pmSockAddrDup(clientId); + __pmSockAddrMask(matchId, hp->hostmask); + if (__pmSockAddrCompare(matchId, hp->hostid) == 0) { + if (hp->maxcons) + hp->curcons++; + } + __pmSockAddrFree(matchId); + } + } + } + + freeClientIds(clientIds); + return 0; +} + +void +__pmAccDelClient(__pmSockAddr *hostid) +{ + int i; + int sts; + hostinfo *hp; + int clientIx; + __pmSockAddr **clientIds; + __pmSockAddr *clientId; + __pmSockAddr *matchId; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + + /* There could be more than one address associated with this host.*/ + clientIds = getClientIds(hostid, &sts); + if (clientIds == NULL) + return; + + /* Decrement the count of current connections for ALL host specs in the + * host access list that match the client's IP addresses. A client may + * contribute to several connection counts because of wildcarding. + */ + for (clientIx = 0; clientIds[clientIx] != NULL; ++clientIx) { + clientId = clientIds[clientIx]; + for (i = 0; i < nhosts; i++) { + hp = &hostlist[i]; + /* At a minumum, the addresses must be from the same family. */ + if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) { + matchId = __pmSockAddrDup(clientId); + __pmSockAddrMask(matchId, hp->hostmask); + if (__pmSockAddrCompare(matchId, hp->hostid) == 0) { + if (hp->maxcons) + hp->curcons--; + } + __pmSockAddrFree(matchId); + } + } + } + freeClientIds(clientIds); +} + +static int +findGidInUsersGroups(const userinfo *up, __pmGroupID gid) +{ + int i; + + for (i = 0; i < up->ngroups; i++) + if (__pmEqualGroupIDs(up->groupids[i], gid)) + return 1; + return 0; +} + +static int +accessCheckUsers(__pmUserID uid, __pmGroupID gid, unsigned int *denyOpsResult) +{ + userinfo *up; + int matched = 0; + int i; + + for (i = 1; i < nusers; i++) { + up = &userlist[i]; + if ((__pmValidUserID(uid) && __pmEqualUserIDs(up->userid, uid)) + || (__pmValidGroupID(gid) && findGidInUsersGroups(up, gid))) { + if (up->maxcons && up->curcons >= up->maxcons) { + *denyOpsResult = all_ops; + return PM_ERR_CONNLIMIT; + } + *denyOpsResult |= up->denyOps; + matched = 1; + } + } + + if (nusers && !matched) { + up = &userlist[0]; /* global wildcard */ + if (up->maxcons && up->curcons >= up->maxcons) { + *denyOpsResult = all_ops; + return PM_ERR_CONNLIMIT; + } + *denyOpsResult |= up->denyOps; + } + + return 0; +} + +static int +findUidInGroupsUsers(const groupinfo *gp, __pmUserID uid) +{ + int i; + + for (i = 0; i < gp->nusers; i++) + if (__pmEqualUserIDs(gp->userids[i], uid)) + return 1; + return 0; +} + +static int +accessCheckGroups(__pmUserID uid, __pmGroupID gid, unsigned int *denyOpsResult) +{ + groupinfo *gp; + int matched = 0; + int i; + + for (i = 1; i < ngroups; i++) { + gp = &grouplist[i]; + if ((__pmValidGroupID(gid) && __pmEqualGroupIDs(gp->groupid, gid)) + || (__pmValidUserID(uid) && findUidInGroupsUsers(gp, uid))) { + if (gp->maxcons && gp->curcons >= gp->maxcons) { + *denyOpsResult = all_ops; + return PM_ERR_CONNLIMIT; + } + *denyOpsResult |= gp->denyOps; + matched = 1; + } + } + + if (ngroups && !matched) { + gp = &grouplist[0]; /* global wildcard */ + if (gp->maxcons && gp->curcons >= gp->maxcons) { + *denyOpsResult = all_ops; + return PM_ERR_CONNLIMIT; + } + *denyOpsResult |= gp->denyOps; + } + + return 0; +} + +static void updateGroupAccountConnections(__pmGroupID, int, int); + +static void +updateUserAccountConnections(__pmUserID uid, int descend, int direction) +{ + int i, j; + userinfo *up; + + for (i = 1; i < nusers; i++) { + up = &userlist[i]; + if (!__pmEqualUserIDs(up->userid, uid)) + continue; + if (up->maxcons) + up->curcons += direction; /* might be negative */ + assert(up->curcons >= 0); + if (!descend) + continue; + for (j = 0; j < up->ngroups; j++) + updateGroupAccountConnections(up->groupids[j], 0, direction); + } +} + +static void +updateGroupAccountConnections(__pmGroupID gid, int descend, int direction) +{ + int i, j; + groupinfo *gp; + + for (i = 1; i < ngroups; i++) { + gp = &grouplist[i]; + if (!__pmEqualGroupIDs(gp->groupid, gid)) + continue; + if (gp->maxcons) + gp->curcons += direction; /* might be negative */ + assert(gp->curcons >= 0); + if (!descend) + continue; + for (j = 0; j < gp->nusers; j++) + updateUserAccountConnections(gp->userids[j], 0, direction); + } +} + +/* Called after authenticating a new connection to check that another + * connection from this account is permitted and to find which operations + * the account is permitted to perform. + * uid and gid identify the account, if not authenticated these will be + * negative. denyOpsResult is a pointer to return the capability vector + * and note that it is both input (host access) and output (merged host + * and account access). So, do not blindly zero or overwrite existing. + */ +int +__pmAccAddAccount(const char *userid, const char *groupid, unsigned int *denyOpsResult) +{ + int sts; + __pmUserID uid; + __pmGroupID gid; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return PM_ERR_THREAD; + + if (nusers == 0 && ngroups == 0) /* No access controls => allow all */ + return (userid || groupid); /* Inform caller of credentials */ + + /* Access controls present, but no authentication information - denied */ + if (!userid || !groupid) { + *denyOpsResult = all_ops; + return PM_ERR_PERMISSION; + } + + __pmUserIDFromString(userid, &uid); + __pmGroupIDFromString(groupid, &gid); + + /* Access controls present, but invalid user/group information - denied */ + if (!__pmValidUserID(uid) && !__pmValidGroupID(gid)) { + *denyOpsResult = all_ops; + return PM_ERR_PERMISSION; + } + + if ((sts = accessCheckUsers(uid, gid, denyOpsResult)) < 0) + return sts; + if ((sts = accessCheckGroups(uid, gid, denyOpsResult)) < 0) + return sts; + + /* If no operations are allowed, disallow connection */ + if (*denyOpsResult == all_ops) + return PM_ERR_PERMISSION; + + /* Increment the count of current connections for this user and group + * in the user and groups access lists. Must walk the supplementary + * ID lists as well as the primary ID ACLs. + */ + updateUserAccountConnections(uid, 1, +1); + updateGroupAccountConnections(gid, 1, +1); + + /* Return code indicates access controls OK and have credentials */ + return 1; +} + +void +__pmAccDelAccount(const char *userid, const char *groupid) +{ + __pmUserID uid; + __pmGroupID gid; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + + __pmUserIDFromString(userid, &uid); + __pmGroupIDFromString(groupid, &gid); + + /* Decrement the count of current connections for this user and group + * in the user and groups access lists. Must walk the supplementary + * ID lists as well as the primary ID ACLs. + */ + updateUserAccountConnections(uid, 1, -1); + updateGroupAccountConnections(gid, 1, -1); +} + +static void +getAccMinMaxBits(int *minbitp, int *maxbitp) +{ + unsigned int mask = all_ops; + int i, minbit = -1; + + for (i = 0; mask; i++) { + if (mask % 2) + if (minbit < 0) + minbit = i; + mask = mask >> 1; + } + + *minbitp = minbit; + *maxbitp = i - 1; +} + +#define NAME_WIDTH 39 /* sufficient for a full IPv6 address */ +#define ID_WIDTH 7 /* sufficient for large group/user ID */ + +void +__pmAccDumpHosts(FILE *stream) +{ + int h, i; + int minbit, maxbit; + char *addrid, *addrmask; + unsigned int mask; + hostinfo *hp; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + + if (nhosts == 0) { + fprintf(stream, "Host access list empty: host-based access control turned off\n"); + return; + } + + getAccMinMaxBits(&minbit, &maxbit); + fprintf(stream, "Host access list:\n"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fprintf(stream, "%02d ", i); + fprintf(stream, "Cur/MaxCons %-*s %-*s lvl host-name\n", + NAME_WIDTH, "host-spec", NAME_WIDTH, "host-mask"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fputs("== ", stream); + fprintf(stream, "=========== "); + for (i = 0; i < 2; i++) { + for (h = 0; h < NAME_WIDTH; h++) + fprintf(stream, "="); + fprintf(stream, " "); + } + fprintf(stream, "=== ==============\n"); + + for (h = 0; h < nhosts; h++) { + hp = &hostlist[h]; + + for (i = minbit; i <= maxbit; i++) { + mask = 1 << i; + if (all_ops & mask) { + if (hp->specOps & mask) + fputs((hp->denyOps & mask) ? " n " : " y ", stream); + else + fputs(" ", stream); + } + } + addrid = __pmSockAddrToString(hp->hostid); + addrmask = __pmSockAddrToString(hp->hostmask); + fprintf(stream, "%5d %5d %-*s %-*s %3d %s\n", hp->curcons, hp->maxcons, + NAME_WIDTH, addrid, NAME_WIDTH, addrmask, + hp->level, hp->hostspec); + free(addrmask); + free(addrid); + } +} + +void +__pmAccDumpUsers(FILE *stream) +{ + int u, i; + int minbit, maxbit; + char buf[128]; + unsigned int mask; + userinfo *up; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + + if (nusers == 0) { + fprintf(stream, "User access list empty: user-based access control turned off\n"); + return; + } + + getAccMinMaxBits(&minbit, &maxbit); + fprintf(stream, "User access list:\n"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fprintf(stream, "%02d ", i); + fprintf(stream, "Cur/MaxCons %*s %-*s %s\n", + ID_WIDTH, "uid", NAME_WIDTH-ID_WIDTH-1, "user-name", "group-list"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fputs("== ", stream); + fprintf(stream, "=========== "); + for (i = 0; i < ID_WIDTH; i++) /* user-id */ + fprintf(stream, "="); + fprintf(stream, " "); + for (i = 0; i < NAME_WIDTH-ID_WIDTH-1; i++) /* user-name */ + fprintf(stream, "="); + fprintf(stream, " "); + for (i = 0; i < NAME_WIDTH + 19; i++) /* group-list */ + fprintf(stream, "="); + fprintf(stream, "\n"); + + for (u = nusers-1; u >= 0; u--) { + up = &userlist[u]; + + for (i = minbit; i <= maxbit; i++) { + mask = 1 << i; + if (all_ops & mask) { + if (up->specOps & mask) + fputs((up->denyOps & mask) ? " n " : " y ", stream); + else + fputs(" ", stream); + } + } + fprintf(stream, "%5d %5d %*s %-*s", up->curcons, up->maxcons, + ID_WIDTH, u == 0 ? "*" : + __pmUserIDToString(up->userid, buf, sizeof(buf)), + NAME_WIDTH-ID_WIDTH-1, up->username); + for (i = 0; i < up->ngroups; i++) + fprintf(stream, "%c%u(%s)", i ? ',' : ' ', up->groupids[i], + __pmGroupnameFromID(up->groupids[i], buf, sizeof(buf))); + fprintf(stream, "\n"); + } +} + +void +__pmAccDumpGroups(FILE *stream) +{ + int g, i; + int minbit, maxbit; + char buf[128]; + unsigned int mask; + groupinfo *gp; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL)) + return; + + if (ngroups == 0) { + fprintf(stream, "Group access list empty: group-based access control turned off\n"); + return; + } + + getAccMinMaxBits(&minbit, &maxbit); + fprintf(stream, "Group access list:\n"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fprintf(stream, "%02d ", i); + fprintf(stream, "Cur/MaxCons %*s %-*s %s\n", + ID_WIDTH, "gid", NAME_WIDTH-ID_WIDTH-1, "group-name", "user-list"); + + for (i = minbit; i <= maxbit; i++) + if (all_ops & (1 << i)) + fputs("== ", stream); + fprintf(stream, "=========== "); + for (i = 0; i < ID_WIDTH; i++) /* group-id */ + fprintf(stream, "="); + fprintf(stream, " "); + for (i = 0; i < NAME_WIDTH-ID_WIDTH-1; i++) /* group-name */ + fprintf(stream, "="); + fprintf(stream, " "); + for (i = 0; i < NAME_WIDTH + 19; i++) /* user-list */ + fprintf(stream, "="); + fprintf(stream, "\n"); + + for (g = ngroups-1; g >= 0; g--) { + gp = &grouplist[g]; + + for (i = minbit; i <= maxbit; i++) { + mask = 1 << i; + if (all_ops & mask) { + if (gp->specOps & mask) + fputs((gp->denyOps & mask) ? " n " : " y ", stream); + else + fputs(" ", stream); + } + } + snprintf(buf, sizeof(buf), g ? "%6d" : " *", gp->groupid); + fprintf(stream, "%5d %5d %*s %-*s", gp->curcons, gp->maxcons, + ID_WIDTH, g == 0 ? "*" : + __pmGroupIDToString(gp->groupid, buf, sizeof(buf)), + NAME_WIDTH-ID_WIDTH-1, gp->groupname); + for (i = 0; i < gp->nusers; i++) + fprintf(stream, "%c%u(%s)", i ? ',' : ' ', gp->userids[i], + __pmUsernameFromID(gp->userids[i], buf, sizeof(buf))); + fprintf(stream, "\n"); + } +} + +void +__pmAccDumpLists(FILE *stream) +{ + putc('\n', stream); + __pmAccDumpHosts(stream); + __pmAccDumpUsers(stream); + __pmAccDumpGroups(stream); + putc('\n', stream); +} diff --git a/src/libpcp/src/accounts.c b/src/libpcp/src/accounts.c new file mode 100644 index 0000000..99436a5 --- /dev/null +++ b/src/libpcp/src/accounts.c @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * + * 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. + */ + +#include <limits.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#if defined(HAVE_PWD_H) +#include <pwd.h> +#endif +#if defined(HAVE_GRP_H) +#include <grp.h> +#endif + +int +__pmEqualUserIDs(__pmUserID uid1, __pmUserID uid2) +{ + return uid1 == uid2; +} + +int +__pmEqualGroupIDs(__pmGroupID gid1, __pmGroupID gid2) +{ + return gid1 == gid2; +} + +int +__pmValidUserID(__pmUserID uid) +{ + return uid >= 0; +} + +int __pmValidGroupID(__pmGroupID gid) +{ + return gid >= 0; +} + +void +__pmUserIDFromString(const char *userid, __pmUserID *uid) +{ + *uid = atoi(userid); +} + +void +__pmGroupIDFromString(const char *groupid, __pmGroupID *gid) +{ + *gid = atoi(groupid); +} + +char * +__pmUserIDToString(__pmUserID uid, char *buf, size_t size) +{ + snprintf(buf, size, "%u", (unsigned int)uid); + buf[size-1] = '\0'; + return buf; +} + +char * +__pmGroupIDToString(__pmGroupID gid, char *buf, size_t size) +{ + snprintf(buf, size, "%u", (unsigned int)gid); + buf[size-1] = '\0'; + return buf; +} + +#if defined(HAVE_GETGRGID_R) +char * +__pmGroupnameFromID(gid_t gid, char *buf, size_t size) +{ + char namebuf[1024]; + struct group grp, *result; + + getgrgid_r(gid, &grp, namebuf, sizeof(namebuf), &result); + snprintf(buf, size, "%s", result ? result->gr_name : "unknown"); + buf[size-1] = '\0'; + return buf; +} +#elif defined(HAVE_GETGRGID) +char * +__pmGroupnameFromID(gid_t gid, char *buf, size_t size) +{ + struct group *result; + + result = getgrgid(gid); + snprintf(buf, size, "%s", result ? result->gr_name : "unknown"); + buf[size-1] = '\0'; + return buf; +} +#else +!bozo! +#endif + +#if defined(HAVE_GETPWUID_R) +char * +__pmUsernameFromID(uid_t uid, char *buf, size_t size) +{ + char namebuf[1024]; + struct passwd pwd, *result; + + getpwuid_r(uid, &pwd, namebuf, sizeof(namebuf), &result); + snprintf(buf, size, "%s", result ? result->pw_name : "unknown"); + buf[size-1] = '\0'; + return buf; +} +#elif defined(HAVE_GETPWUID) +char * +__pmUsernameFromID(uid_t uid, char *buf, size_t size) +{ + struct passwd *result; + + result = getpwuid(uid); + snprintf(buf, size, "%s", result ? result->pw_name : "unknown"); + buf[size-1] = '\0'; + return buf; +} +#else +!bozo! +#endif + +#if defined(HAVE_GETPWNAM_R) +int +__pmUsernameToID(const char *name, uid_t *uid) +{ + char namebuf[1024]; + struct passwd pwd, *result = NULL; + + getpwnam_r(name, &pwd, namebuf, sizeof(namebuf), &result); + if (!result) + return -ENOENT; + *uid = result->pw_uid; + return 0; +} +#elif defined(HAVE_GETPWNAM) +int +__pmUsernameToID(const char *name, uid_t *uid) +{ + struct passwd *result; + + result = getpwnam(name); + if (!result) + return -ENOENT; + *uid = result->pw_uid; + return 0; +} +#else +!bozo! +#endif + +#if defined(HAVE_GETGRNAM_R) +int +__pmGroupnameToID(const char *name, gid_t *gid) +{ + char namebuf[512]; + struct group grp, *result = NULL; + + getgrnam_r(name, &grp, namebuf, sizeof(namebuf), &result); + if (result == NULL) + return -ENOENT; + *gid = result->gr_gid; + return 0; +} +#elif defined(HAVE_GETGRNAM) +int +__pmGroupnameToID(const char *name, gid_t *gid) +{ + struct group *result; + + result = getgrnam(name); + if (result == NULL) + return -ENOENT; + *gid = result->gr_gid; + return 0; +} +#else +!bozo! +#endif + +#if defined(HAVE_GETPWUID_R) +char * +__pmHomedirFromID(uid_t uid, char *buf, size_t size) +{ + char namebuf[1024]; + struct passwd pwd, *result; + char *env; + + /* + * Use $HOME, if it is set, otherwise get the information from + * getpwuid_r() + */ + env = getenv("HOME"); + if (env != NULL) + snprintf(buf, size, "%s", env); + else { + getpwuid_r(uid, &pwd, namebuf, sizeof(namebuf), &result); + if (result == NULL) + return NULL; + snprintf(buf, size, "%s", result->pw_dir); + } + buf[size-1] = '\0'; + return buf; +} +#elif defined(HAVE_GETPWUID) +char * +__pmHomedirFromID(uid_t uid, char *buf, size_t size) +{ + struct passwd *result; + + /* + * Use $HOME, if it is set, otherwise get the information from + * getpwuid() + */ + env = getenv("HOME"); + if (env != NULL) + snprintf(buf, size, "%s", env); + else { + result = getpwuid(uid); + if (result == NULL) + return NULL; + snprintf(buf, size, "%s", result->pw_dir); + } + buf[size-1] = '\0'; + return buf; +} +#else +!bozo! +#endif + +/* + * Add a group ID into a group list, if it is not there already. + * The current group ID list and size are passed in, updated if + * changed, and passed back out. + */ +static int +__pmAddGroupID(gid_t gid, gid_t **gidlist, unsigned int *count) +{ + gid_t *gids = *gidlist; + size_t need; + unsigned int i, total = *count; + + for (i = 0; i < total; i++) + if (gids[i] == gid) + return 0; /* already in the list, we're done */ + + need = (total + 1) * sizeof(gid_t); + if ((gids = (gid_t *)realloc(gids, need)) == NULL) + return -ENOMEM; + gids[total++] = gid; + *gidlist = gids; + *count = total; + return 0; +} + +#if defined(HAVE_GETPWNAM_R) && defined(HAVE_GETGRENT_R) +int +__pmUsersGroupIDs(const char *username, gid_t **groupids, unsigned int *ngroups) +{ + int i, sts; + unsigned int count = 0; + char grbuf[1024]; + gid_t *gidlist = NULL; + struct passwd pwd, *result = NULL; + struct group gr, *grp; + + getpwnam_r(username, &pwd, grbuf, sizeof(grbuf), &result); + if (!result) + return -ENOENT; + + /* add the primary group in right away, before supplementary groups */ + if ((sts = __pmAddGroupID(result->pw_gid, &gidlist, &count)) < 0) + return sts; + + /* search for groups in which the given user is a member */ + setgrent(); + while (1) { + grp = NULL; +#ifdef IS_SOLARIS + if ((grp = getgrent_r(&gr, grbuf, sizeof(grbuf))) == NULL) + break; +#else + if (getgrent_r(&gr, grbuf, sizeof(grbuf), &grp) != 0 || grp == NULL) + break; +#endif + for (i = 0; grp->gr_mem[i]; i++) { + if (strcmp(username, grp->gr_mem[i]) != 0) + continue; + if ((sts = __pmAddGroupID(grp->gr_gid, &gidlist, &count)) < 0) { + endgrent(); + return sts; + } + break; + } + } + endgrent(); + + *groupids = gidlist; + *ngroups = count; + return 0; +} +#elif defined(HAVE_GETPWNAM) && defined(HAVE_GETGRENT) +int +__pmUsersGroupIDs(const char *username, gid_t **groupids, unsigned int *ngroups) +{ + int i, sts; + unsigned int count = 0; + gid_t *gidlist = NULL; + struct passwd *result; + struct group *grp; + + result = getpwnam(username); + if (!result) + return -ENOENT; + + /* add the primary group in right away, before supplementary groups */ + if ((sts = __pmAddGroupID(result->pw_gid, &gidlist, &count)) < 0) + return sts; + + /* search for groups in which the given user is a member */ + setgrent(); + while (1) { + grp = NULL; + if ((grp = getgrent()) == NULL) + break; + for (i = 0; grp->gr_mem[i]; i++) { + if (strcmp(username, grp->gr_mem[i]) != 0) + continue; + if ((sts = __pmAddGroupID(grp->gr_gid, &gidlist, &count)) < 0) { + endgrent(); + return sts; + } + break; + } + } + endgrent(); + + *groupids = gidlist; + *ngroups = count; + return 0; +} +#else +!bozo! +#endif + +/* + * Add a user ID into a user list, if it is not there already. + * The current user ID list and size are passed in, updated if + * changed, and passed back out. + */ +static int +__pmAddUserID(uid_t uid, uid_t **uidlist, unsigned int *count) +{ + uid_t *uids = *uidlist; + size_t need; + unsigned int i, total = *count; + + for (i = 0; i < total; i++) + if (uids[i] == uid) + return 0; /* already in the list, we're done */ + + need = (total + 1) * sizeof(uid_t); + if ((uids = (uid_t *)realloc(uids, need)) == NULL) + return -ENOMEM; + uids[total++] = uid; + *uidlist = uids; + *count = total; + return 0; +} + +#if defined(HAVE_GETGRNAM_R) && defined(HAVE_GETPWENT_R) +int +__pmGroupsUserIDs(const char *groupname, uid_t **userids, unsigned int *nusers) +{ + int sts; + uid_t *uidlist = NULL; + gid_t groupid; + char grbuf[1024]; + char buf[512]; + char **names = NULL; + struct group gr, *grp = NULL; + struct passwd pw, *pwp; + unsigned int i, count = 0; + + /* for a given group name, find gid and user names */ + getgrnam_r(groupname, &gr, grbuf, sizeof(grbuf), &grp); + if (grp == NULL) + return -EINVAL; + groupid = grp->gr_gid; + names = grp->gr_mem; /* supplementaries */ + + /* for a given list of usernames, lookup the user IDs */ + setpwent(); + while (1) { +#ifdef IS_SOLARIS + if ((pwp = getpwent_r(&pw, buf, sizeof(buf))) == NULL) + break; +#else + pwp = NULL; + if (getpwent_r(&pw, buf, sizeof(buf), &pwp) != 0 || pwp == NULL) + break; +#endif + /* check to see if this user has given group as primary */ + if (pwp->pw_gid == groupid && + (sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) { + endpwent(); + return sts; + } + /* check to see if this user is listed in groups file */ + for (i = 0; names[i]; i++) { + if (strcmp(pwp->pw_name, names[i]) == 0) { + if ((sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) { + endpwent(); + return sts; + } + break; + } + } + } + endpwent(); + + *userids = uidlist; + *nusers = count; + return 0; +} +#elif defined(HAVE_GETGRNAM) && defined(HAVE_GETPWENT) +int +__pmGroupsUserIDs(const char *name, uid_t **userids, unsigned int *nusers) +{ + int sts; + uid_t *uidlist = NULL; + gid_t groupid; + char **names = NULL; + struct group *grp = NULL; + struct passwd *pwp; + unsigned int i, count = 0; + + /* for a given group name, find gid and user names */ + if ((grp = getgrnam(name)) == NULL) + return -EINVAL; + groupid = grp->gr_gid; + names = grp->gr_mem; + + setpwent(); + while (1) { + if ((pwp = getpwent()) == NULL) + break; + /* check to see if this user has given group as primary */ + if (pwp->pw_gid == groupid && + (sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) { + endpwent(); + return sts; + } + for (i = 0; names[i]; i++) { + if (strcmp(pwp->pw_name, names[i]) == 0) { + if ((sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) { + endpwent(); + return sts; + } + break; + } + } + } + endpwent(); + + *userids = uidlist; + *nusers = count; + return 0; +} +#else +!bozo! +#endif + +#if defined(HAVE_GETPWNAM_R) +int +__pmGetUserIdentity(const char *username, uid_t *uid, gid_t *gid, int mode) +{ + int sts; + char buf[4096]; + struct passwd pwd, *pw; + + sts = getpwnam_r(username, &pwd, buf, sizeof(buf), &pw); + if (pw == NULL) { + __pmNotifyErr(LOG_CRIT, + "cannot find the %s user to switch to\n", username); + if (mode == PM_FATAL_ERR) + exit(1); + return -ENOENT; + } else if (sts != 0) { + __pmNotifyErr(LOG_CRIT, "getpwnam_r(%s) failed: %s\n", + username, pmErrStr_r(sts, buf, sizeof(buf))); + if (mode == PM_FATAL_ERR) + exit(1); + return -ENOENT; + } + *uid = pwd.pw_uid; + *gid = pwd.pw_gid; + return 0; +} +#elif defined(HAVE_GETPWNAM) +int +__pmGetUserIdentity(const char *username, uid_t *uid, gid_t *gid, int mode) +{ + int sts; + char errmsg[128]; + struct passwd *pw; + + setoserror(0); + if ((pw = getpwnam(username)) == 0) { + __pmNotifyErr(LOG_CRIT, + "cannot find the %s user to switch to\n", username); + if (mode == PM_FATAL_ERR) + exit(1); + return -ENOENT; + } else if (oserror() != 0) { + __pmNotifyErr(LOG_CRIT, "getpwnam(%s) failed: %s\n", + username, pmErrStr_r(oserror(), errmsg, sizeof(errmsg))); + if (mode == PM_FATAL_ERR) + exit(1); + return -ENOENT; + } + *uid = pw->pw_uid; + *gid = pw->pw_gid; + return 0; +} +#else +!bozo! +#endif + +int +__pmSetProcessIdentity(const char *username) +{ + gid_t gid; + uid_t uid; + char msg[256]; + + __pmGetUserIdentity(username, &uid, &gid, PM_FATAL_ERR); + + if (setgid(gid) < 0) { + __pmNotifyErr(LOG_CRIT, + "setgid to gid of %s user (gid=%d): %s", + username, gid, osstrerror_r(msg, sizeof(msg))); + exit(1); + } + + /* + * We must allow initgroups to fail with EPERM, as this + * is the behaviour when the parent process has already + * dropped privileges (e.g. pmcd receives SIGHUP). + */ + if (initgroups(username, gid) < 0 && oserror() != EPERM) { + __pmNotifyErr(LOG_CRIT, + "initgroups with gid of %s user (gid=%d): %s", + username, gid, osstrerror_r(msg, sizeof(msg))); + exit(1); + } + + if (setuid(uid) < 0) { + __pmNotifyErr(LOG_CRIT, + "setuid to uid of %s user (uid=%d): %s", + username, uid, osstrerror_r(msg, sizeof(msg))); + exit(1); + } + + return 0; +} + +int +__pmGetUsername(char **username) +{ + char *user = pmGetConfig("PCP_USER"); + if (user && user[0] != '\0') { + *username = user; + return 1; + } + *username = "pcp"; + return 0; +} diff --git a/src/libpcp/src/auxconnect.c b/src/libpcp/src/auxconnect.c new file mode 100644 index 0000000..bf00ae3 --- /dev/null +++ b/src/libpcp/src/auxconnect.c @@ -0,0 +1,1389 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 2000,2004,2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include <net/if.h> +#define SOCKET_INTERNAL +#include "internal.h" + +/* default connect timeout is 5 seconds */ +static struct timeval canwait = { 5, 000000 }; + +__pmHostEnt * +__pmHostEntAlloc(void) +{ + return calloc(1, sizeof(__pmHostEnt)); +} + +void +__pmHostEntFree(__pmHostEnt *hostent) +{ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DESPERATE) + fprintf(stderr, "%s:__pmHostEntFree(hostent=%p) name=%p (%s) addresses=%p\n", __FILE__, hostent, hostent->name, hostent->name, hostent-> addresses); +#endif + if (hostent->name != NULL) + free(hostent->name); + if (hostent->addresses != NULL) + freeaddrinfo(hostent->addresses); + free(hostent); +} + +__pmSockAddr * +__pmSockAddrAlloc(void) +{ + return calloc(1, sizeof(__pmSockAddr)); +} + +__pmSockAddr * +__pmSockAddrDup(const __pmSockAddr *sockaddr) +{ + __pmSockAddr *new = malloc(sizeof(__pmSockAddr)); + if (new) + *new = *sockaddr; + return new; +} + +size_t +__pmSockAddrSize(void) +{ + return sizeof(__pmSockAddr); +} + +/* Initialize a socket address. The integral address must be INADDR_ANY or + INADDR_LOOPBACK in host byte order. */ +void +__pmSockAddrInit(__pmSockAddr *addr, int family, int address, int port) +{ + memset(addr, 0, sizeof(*addr)); + if (family == AF_INET) { + addr->sockaddr.inet.sin_family = family; + addr->sockaddr.inet.sin_addr.s_addr = htonl(address); + addr->sockaddr.inet.sin_port = htons(port); + } + else if (family == AF_INET6) { + addr->sockaddr.ipv6.sin6_family = family; + addr->sockaddr.ipv6.sin6_port = htons(port); + if (address == INADDR_LOOPBACK) + addr->sockaddr.ipv6.sin6_addr.s6_addr[15] = 1; + } + else + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrInit: Invalid address family: %d\n", __FILE__, addr->sockaddr.raw.sa_family); +} + +void +__pmSockAddrSetFamily(__pmSockAddr *addr, int family) +{ + addr->sockaddr.raw.sa_family = family; +} + +int +__pmSockAddrGetFamily(const __pmSockAddr *addr) +{ + return addr->sockaddr.raw.sa_family; +} + +void +__pmSockAddrSetPort(__pmSockAddr *addr, int port) +{ + if (addr->sockaddr.raw.sa_family == AF_INET) + addr->sockaddr.inet.sin_port = htons(port); + else if (addr->sockaddr.raw.sa_family == AF_INET6) + addr->sockaddr.ipv6.sin6_port = htons(port); + else + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrSetPort: Invalid address family: %d\n", + __FILE__, addr->sockaddr.raw.sa_family); +} + +int +__pmSockAddrGetPort(const __pmSockAddr *addr) +{ + if (addr->sockaddr.raw.sa_family == AF_INET) + return ntohs(addr->sockaddr.inet.sin_port); + if (addr->sockaddr.raw.sa_family == AF_INET6) + return ntohs(addr->sockaddr.ipv6.sin6_port); + __pmNotifyErr(LOG_ERR, + "__pmSockAddrGetPort: Invalid address family: %d\n", + addr->sockaddr.raw.sa_family); + return 0; /* not set */ +} + +int +__pmSockAddrIsLoopBack(const __pmSockAddr *addr) +{ + int rc; + int family; + __pmSockAddr *loopBackAddr; + + family = __pmSockAddrGetFamily(addr); + loopBackAddr = __pmLoopBackAddress(family); + if (loopBackAddr == NULL) + return 0; + rc = __pmSockAddrCompare(addr, loopBackAddr); + __pmSockAddrFree(loopBackAddr); + return rc == 0; +} + +void +__pmSockAddrSetScope(__pmSockAddr *addr, int scope) +{ + if (addr->sockaddr.raw.sa_family == AF_INET6) + addr->sockaddr.ipv6.sin6_scope_id = scope; +} + +void +__pmSockAddrSetPath(__pmSockAddr *addr, const char *path) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (addr->sockaddr.raw.sa_family == AF_UNIX) + strncpy(addr->sockaddr.local.sun_path, path, sizeof(addr->sockaddr.local.sun_path)); + else + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrSetPath: Invalid address family: %d\n", + __FILE__, addr->sockaddr.raw.sa_family); +#else + __pmNotifyErr(LOG_ERR, "%s:__pmSockAddrSetPath: AF_UNIX is not supported\n", __FILE__); +#endif +} + +__pmSockAddr * +__pmSockAddrMask(__pmSockAddr *addr, const __pmSockAddr *mask) +{ + int i; + if (addr->sockaddr.raw.sa_family != mask->sockaddr.raw.sa_family) { + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrMask: Address family of the address (%d) must match that of the mask (%d)\n", + __FILE__, addr->sockaddr.raw.sa_family, mask->sockaddr.raw.sa_family); + } + else if (addr->sockaddr.raw.sa_family == AF_INET) + addr->sockaddr.inet.sin_addr.s_addr &= mask->sockaddr.inet.sin_addr.s_addr; + else if (addr->sockaddr.raw.sa_family == AF_INET6) { + /* IPv6: Mask it byte by byte */ + unsigned char *addrBytes = addr->sockaddr.ipv6.sin6_addr.s6_addr; + const unsigned char *maskBytes = mask->sockaddr.ipv6.sin6_addr.s6_addr; + for (i = 0; i < sizeof(addr->sockaddr.ipv6.sin6_addr.s6_addr); ++i) + addrBytes[i] &= maskBytes[i]; + } +#if defined(HAVE_STRUCT_SOCKADDR_UN) + else if (addr->sockaddr.raw.sa_family == AF_UNIX) { + /* Simply truncate the path in the address to the length of the mask. */ + i = strlen(mask->sockaddr.local.sun_path); + addr->sockaddr.local.sun_path[i] = '\0'; + } +#endif + else /* not applicable to other address families. */ + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrMask: Invalid address family: %d\n", __FILE__, addr->sockaddr.raw.sa_family); + + return addr; +} + +int +__pmSockAddrCompare(const __pmSockAddr *addr1, const __pmSockAddr *addr2) +{ + if (addr1->sockaddr.raw.sa_family != addr2->sockaddr.raw.sa_family) + return addr1->sockaddr.raw.sa_family - addr2->sockaddr.raw.sa_family; + + if (addr1->sockaddr.raw.sa_family == AF_INET) + return addr1->sockaddr.inet.sin_addr.s_addr - addr2->sockaddr.inet.sin_addr.s_addr; + + if (addr1->sockaddr.raw.sa_family == AF_INET6) { + /* IPv6: Compare it byte by byte */ + return memcmp(&addr1->sockaddr.ipv6.sin6_addr.s6_addr, &addr2->sockaddr.ipv6.sin6_addr.s6_addr, + sizeof(addr1->sockaddr.ipv6.sin6_addr.s6_addr)); + } + +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (addr1->sockaddr.raw.sa_family == AF_UNIX) { + /* Unix Domain: Compare the paths */ + return strncmp(addr1->sockaddr.local.sun_path, addr2->sockaddr.local.sun_path, + sizeof(addr1->sockaddr.local.sun_path)); + } +#endif + + /* Unknown address family. */ + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrCompare: Invalid address family: %d\n", __FILE__, addr1->sockaddr.raw.sa_family); + return 1; /* not equal */ +} + +int +__pmSockAddrIsInet(const __pmSockAddr *addr) +{ + return addr->sockaddr.raw.sa_family == AF_INET; +} + +int +__pmSockAddrIsIPv6(const __pmSockAddr *addr) +{ + return addr->sockaddr.raw.sa_family == AF_INET6; +} + +int +__pmSockAddrIsUnix(const __pmSockAddr *addr) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + return addr->sockaddr.raw.sa_family == AF_UNIX; +#else + return 0; +#endif +} + +__pmSockAddr * +__pmStringToSockAddr(const char *cp) +{ + __pmSockAddr *addr = __pmSockAddrAlloc(); + if (addr) { + if (cp == NULL || strcmp(cp, "INADDR_ANY") == 0) { + addr->sockaddr.inet.sin_addr.s_addr = INADDR_ANY; + /* Set the address family to 0, meaning "not set". */ + addr->sockaddr.raw.sa_family = 0; + } + else { + int sts; + /* Determine the address family. */ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (*cp == __pmPathSeparator()) { + if (strlen(cp) >= sizeof(addr->sockaddr.local.sun_path)) + sts = -1; /* too long */ + else { + addr->sockaddr.raw.sa_family = AF_UNIX; + strcpy(addr->sockaddr.local.sun_path, cp); + sts = 1; + } + } + else +#endif + if (strchr(cp, ':') != NULL) { + char *cp1; + char *scope; + /* + * inet_pton(3) does not support the "%<interface>" extension for specifying the + * scope of a link-local address. If one is present, then strip it out and + * set the scope_id manually. + */ + if ((scope = strchr(cp, '%')) != NULL) { + size_t size = scope - cp; + ++scope; /* get past the '%' */ + if ((cp1 = malloc(size + 1)) == NULL) + sts = -1; + else { + strncpy(cp1, cp, size); + cp1[size] = '\0'; + } + cp = cp1; + } + if (cp != NULL) { + addr->sockaddr.raw.sa_family = AF_INET6; + sts = inet_pton(AF_INET6, cp, &addr->sockaddr.ipv6.sin6_addr); + if (scope != NULL) { + free(cp1); + /* Manually set the scope_id */ + if ((addr->sockaddr.ipv6.sin6_scope_id = if_nametoindex(scope)) == 0) + sts = -1; + } + } + } + else { + addr->sockaddr.raw.sa_family = AF_INET; + sts = inet_pton(AF_INET, cp, &addr->sockaddr.inet.sin_addr); + } + if (sts <= 0) { + __pmSockAddrFree(addr); + addr = NULL; + } + } + } + return addr; +} + +/* + * Convert an address to a string. + * The caller must free the buffer. + */ +char * +__pmSockAddrToString(const __pmSockAddr *addr) +{ + char str[INET6_ADDRSTRLEN]; + int family; + const char *sts; + + sts = NULL; + family = addr->sockaddr.raw.sa_family; + if (family == AF_INET) + sts = inet_ntop(family, &addr->sockaddr.inet.sin_addr, str, sizeof(str)); + else if (family == AF_INET6) + sts = inet_ntop(family, &addr->sockaddr.ipv6.sin6_addr, str, sizeof(str)); +#if defined(HAVE_STRUCT_SOCKADDR_UN) + else if (family == AF_UNIX) + return strdup(addr->sockaddr.local.sun_path); +#endif + if (sts == NULL) + return NULL; + return strdup(str); +} + +__pmSockAddr * +__pmSockAddrFirstSubnetAddr(const __pmSockAddr *netAddr, int maskBits) +{ + __pmSockAddr *addr; + + /* Make a copy of the net address for iteration purposes. */ + addr = __pmSockAddrDup(netAddr); + if (addr) { + /* + * Construct the first address in the subnet based on the given number + * of mask bits. + */ + if (addr->sockaddr.raw.sa_family == AF_INET) { + /* An inet address. The ip address is in network byte order. */ + unsigned ip = ntohl(addr->sockaddr.inet.sin_addr.s_addr); + ip = __pmFirstInetSubnetAddr (ip, maskBits); + addr->sockaddr.inet.sin_addr.s_addr = htonl(ip); + } + else if (addr->sockaddr.raw.sa_family == AF_INET6) { + __pmFirstIpv6SubnetAddr(addr->sockaddr.ipv6.sin6_addr.s6_addr, maskBits); + } + else { + /* not applicable to other address families, e.g. AF_LOCAL. */ + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrFirstSubnetAddr: Unsupported address family: %d\n", + __FILE__, addr->sockaddr.raw.sa_family); + __pmSockAddrFree(addr); + return NULL; + } + } + + return addr; +} + +__pmSockAddr * +__pmSockAddrNextSubnetAddr(__pmSockAddr *addr, int maskBits) +{ + if (addr) { + /* + * Construct the next address in the subnet based on the given the + * previous address and the given number of mask bits. + */ + if (addr->sockaddr.raw.sa_family == AF_INET) { + /* An inet address. The ip address is in network byte order. */ + unsigned ip = ntohl(addr->sockaddr.inet.sin_addr.s_addr); + unsigned newIp = __pmNextInetSubnetAddr(ip, maskBits); + + /* Is this the final address? */ + if (newIp == ip) { + __pmSockAddrFree(addr); + return NULL; + } + addr->sockaddr.inet.sin_addr.s_addr = htonl(newIp); + } + else if (addr->sockaddr.raw.sa_family == AF_INET6) { + unsigned char *newAddr = + __pmNextIpv6SubnetAddr(addr->sockaddr.ipv6.sin6_addr.s6_addr, maskBits); + + if (newAddr == NULL) { + /* This is the final address. */ + __pmSockAddrFree(addr); + return NULL; + } + } + else { + /* not applicable to other address families, e.g. AF_LOCAL. */ + __pmNotifyErr(LOG_ERR, + "%s:__pmSockAddrFirstSubnetAddr: Unsupported address family: %d\n", + __FILE__, addr->sockaddr.raw.sa_family); + __pmSockAddrFree(addr); + return NULL; + } + } + + return addr; +} + +void +__pmSockAddrFree(__pmSockAddr *sockaddr) +{ + free(sockaddr); +} + +__pmSockAddr * +__pmLoopBackAddress(int family) +{ + __pmSockAddr* addr; + +#if defined(HAVE_STRUCT_SOCKADDR_UN) + /* There is no loopback address for a unix domain socket. */ + if (family == AF_UNIX) + return NULL; +#endif + + addr = __pmSockAddrAlloc(); + if (addr != NULL) + __pmSockAddrInit(addr, family, INADDR_LOOPBACK, 0); + return addr; +} + +int +__pmInitSocket(int fd, int family) +{ + int sts; + int nodelay = 1; + struct linger nolinger = {1, 0}; + char errmsg[PM_MAXERRMSGLEN]; + + if ((sts = __pmSetSocketIPC(fd)) < 0) { + __pmCloseSocket(fd); + return sts; + } + + /* Don't linger on close */ + if (__pmSetSockOpt(fd, SOL_SOCKET, SO_LINGER, (char *)&nolinger, + (__pmSockLen)sizeof(nolinger)) < 0) { + __pmNotifyErr(LOG_WARNING, + "%s:__pmCreateSocket(%d): __pmSetSockOpt SO_LINGER: %s\n", + __FILE__, fd, netstrerror_r(errmsg, sizeof(errmsg))); + } + +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (family == AF_UNIX) + return fd; +#endif + + /* Avoid 200 ms delay. This option is not supported for unix domain sockets. */ + if (__pmSetSockOpt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, + (__pmSockLen)sizeof(nodelay)) < 0) { + __pmNotifyErr(LOG_WARNING, + "%s:__pmCreateSocket(%d): __pmSetSockOpt TCP_NODELAY: %s\n", + __FILE__, fd, netstrerror_r(errmsg, sizeof(errmsg))); + } + + return fd; +} + +int +__pmCreateSocket(void) +{ + int sts, fd; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -neterror(); + if ((sts = __pmInitSocket(fd, AF_INET)) < 0) + return sts; + return fd; +} + +int +__pmCreateIPv6Socket(void) +{ + int sts, fd, on; + __pmSockLen onlen = sizeof(on); + + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) + return -neterror(); + + /* + * Disable IPv4-mapped connections + * Must explicitly check whether that worked, for ipv6.enabled=false + * kernels. Setting then testing is the most reliable way we've found. + */ + on = 1; + __pmSetSockOpt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + on = 0; + sts = __pmGetSockOpt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, &onlen); + if (sts < 0 || on != 1) { + __pmNotifyErr(LOG_ERR, "%s:__pmCreateIPv6Socket: IPV6 is not supported\n", __FILE__); + close(fd); + return -EOPNOTSUPP; + } + + if ((sts = __pmInitSocket(fd, AF_INET6)) < 0) + return sts; + return fd; +} + +int +__pmCreateUnixSocket(void) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + int sts, fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return -neterror(); + + if ((sts = __pmInitSocket(fd, AF_UNIX)) < 0) + return sts; + + return fd; +#else + __pmNotifyErr(LOG_ERR, "%s:__pmCreateUnixSocket: AF_UNIX is not supported\n", __FILE__); + return -EOPNOTSUPP; +#endif +} + +int +__pmGetFileStatusFlags(int fd) +{ + return fcntl(fd, F_GETFL); +} + +int +__pmSetFileStatusFlags(int fd, int flags) +{ + return fcntl(fd, F_SETFL, flags); +} + +int +__pmGetFileDescriptorFlags(int fd) +{ + return fcntl(fd, F_GETFD); +} + +int +__pmSetFileDescriptorFlags(int fd, int flags) +{ + return fcntl(fd, F_SETFD, flags); +} + +int +__pmListen(int fd, int backlog) +{ + return listen(fd, backlog); +} + +int +__pmAccept(int fd, void *addr, __pmSockLen *addrlen) +{ + __pmSockAddr *sockAddr = (__pmSockAddr *)addr; + fd = accept(fd, &sockAddr->sockaddr.raw, addrlen); + __pmCheckAcceptedAddress(sockAddr); + return fd; +} + +int +__pmBind(int fd, void *addr, __pmSockLen addrlen) +{ + __pmSockAddr *sock = (__pmSockAddr *)addr; + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_CONTEXT) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "%s:__pmBind(fd=%d, family=%d, port=%d, addr=%s)\n", + __FILE__, fd, __pmSockAddrGetFamily(sock), __pmSockAddrGetPort(sock), + __pmSockAddrToString(sock)); + } +#endif + if (sock->sockaddr.raw.sa_family == AF_INET) + return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.inet)); + if (sock->sockaddr.raw.sa_family == AF_INET6) + return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.ipv6)); +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (sock->sockaddr.raw.sa_family == AF_UNIX) + return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.local)); +#endif + __pmNotifyErr(LOG_ERR, + "%s:__pmBind: Invalid address family: %d\n", __FILE__, sock->sockaddr.raw.sa_family); + errno = EAFNOSUPPORT; + return -1; /* failure */ +} + +int +__pmConnect(int fd, void *addr, __pmSockLen addrlen) +{ + __pmSockAddr *sock = (__pmSockAddr *)addr; + if (sock->sockaddr.raw.sa_family == AF_INET) + return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.inet)); + if (sock->sockaddr.raw.sa_family == AF_INET6) + return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.ipv6)); +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (sock->sockaddr.raw.sa_family == AF_UNIX) + return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.local)); +#endif + __pmNotifyErr(LOG_ERR, + "%s:__pmConnect: Invalid address family: %d\n", __FILE__, sock->sockaddr.raw.sa_family); + errno = EAFNOSUPPORT; + return -1; /* failure */ +} + +int +__pmConnectWithFNDELAY(int fd, void *addr, __pmSockLen addrlen) +{ + return __pmConnect(fd, addr, addrlen); +} + +int +__pmConnectTo(int fd, const __pmSockAddr *addr, int port) +{ + int sts, fdFlags = __pmGetFileStatusFlags(fd); + __pmSockAddr myAddr; + + myAddr = *addr; + if (port >= 0) + __pmSockAddrSetPort(&myAddr, port); + + if (__pmSetFileStatusFlags(fd, fdFlags | FNDELAY) < 0) { + char errmsg[PM_MAXERRMSGLEN]; + + __pmNotifyErr(LOG_ERR, "%s:__pmConnectTo: cannot set FNDELAY - " + "fcntl(%d,F_SETFL,0x%x) failed: %s\n", + __FILE__, fd, fdFlags|FNDELAY , osstrerror_r(errmsg, sizeof(errmsg))); + } + + if (__pmConnectWithFNDELAY(fd, &myAddr, sizeof(myAddr)) < 0) { + sts = neterror(); + if (sts != EINPROGRESS) { + __pmCloseSocket(fd); + return -sts; + } + } + + return fdFlags; +} + +int +__pmConnectCheckError(int fd) +{ + int so_err = 0; + __pmSockLen olen = sizeof(int); + char errmsg[PM_MAXERRMSGLEN]; + + if (__pmGetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void *)&so_err, &olen) < 0) { + so_err = neterror(); + __pmNotifyErr(LOG_ERR, + "%s:__pmConnectCheckError: __pmGetSockOpt(SO_ERROR) failed: %s\n", + __FILE__, netstrerror_r(errmsg, sizeof(errmsg))); + } + return so_err; +} + +int +__pmConnectRestoreFlags(int fd, int fdFlags) +{ + int sts; + char errmsg[PM_MAXERRMSGLEN]; + + if (__pmSetFileStatusFlags(fd, fdFlags) < 0) { + __pmNotifyErr(LOG_WARNING, "%s:__pmConnectRestoreFlags: cannot restore " + "flags fcntl(%d,F_SETFL,0x%x) failed: %s\n", + __FILE__, fd, fdFlags, osstrerror_r(errmsg, sizeof(errmsg))); + } + + if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0) + sts = __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC); + else + sts = fdFlags; + + if (sts == -1) { + __pmNotifyErr(LOG_WARNING, "%s:__pmConnectRestoreFlags: " + "fcntl(%d) get/set flags failed: %s\n", + __FILE__, fd, osstrerror_r(errmsg, sizeof(errmsg))); + __pmCloseSocket(fd); + return sts; + } + + return fd; +} + +const struct timeval * +__pmConnectTimeout(void) +{ + static int first_time = 1; + + /* + * get optional stuff from environment ... + * PMCD_CONNECT_TIMEOUT + * PMCD_PORT + */ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (first_time) { + char *env_str; + first_time = 0; + + if ((env_str = getenv("PMCD_CONNECT_TIMEOUT")) != NULL) { + char *end_ptr; + double timeout = strtod(env_str, &end_ptr); + if (*end_ptr != '\0' || timeout < 0.0) + __pmNotifyErr(LOG_WARNING, "%s:__pmAuxConnectPMCDPort: " + "ignored bad PMCD_CONNECT_TIMEOUT = '%s'\n", + __FILE__, env_str); + else { + canwait.tv_sec = (time_t)timeout; + canwait.tv_usec = (int)((timeout - + (double)canwait.tv_sec) * 1000000); + } + } + } + PM_UNLOCK(__pmLock_libpcp); + return (&canwait); +} + +int +__pmFD(int fd) +{ + return fd; +} + +void +__pmFD_CLR(int fd, __pmFdSet *set) +{ + FD_CLR(fd, set); +} + +int +__pmFD_ISSET(int fd, __pmFdSet *set) +{ + return FD_ISSET(fd, set); +} + +void +__pmFD_SET(int fd, __pmFdSet *set) +{ + FD_SET(fd, set); +} + +void +__pmFD_ZERO(__pmFdSet *set) +{ + FD_ZERO(set); +} + +void +__pmFD_COPY(__pmFdSet *s1, const __pmFdSet *s2) +{ + memcpy(s1, s2, sizeof(*s1)); +} + +int +__pmSelectRead(int nfds, __pmFdSet *readfds, struct timeval *timeout) +{ + return select(nfds, readfds, NULL, NULL, timeout); +} + +int +__pmSelectWrite(int nfds, __pmFdSet *writefds, struct timeval *timeout) +{ + return select(nfds, NULL, writefds, NULL, timeout); +} + +/* + * This interface is old and mouldy (exposed via impl.h many years ago) + * and very much deprecated. It was replaced by __pmAuxConnectPMCDPort. + * + * The implementation here is retained for any (out-of-tree) application + * that might have called this interface directly ... the implementation + * is correct when $PMCD_PORT is unset, or set to a single numeric port + * number, i.e. the old semantics + */ +int +__pmAuxConnectPMCD(const char *hostname) +{ + static int *pmcd_ports = NULL; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (pmcd_ports == NULL) + __pmPMCDAddPorts(&pmcd_ports, 0); + PM_UNLOCK(__pmLock_libpcp); + + /* __pmPMCDAddPorts discovers at least one valid port, if it returns. */ + return __pmAuxConnectPMCDPort(hostname, pmcd_ports[0]); +} + +int +__pmAuxConnectPMCDPort(const char *hostname, int pmcd_port) +{ + __pmSockAddr *myAddr; + __pmHostEnt *servInfo; + int fd = -1; /* Fd for socket connection to pmcd */ + int sts; + int fdFlags = 0; + void *enumIx; + + if ((servInfo = __pmGetAddrInfo(hostname)) == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "%s:__pmAuxConnectPMCDPort(%s, %d) : hosterror=%d, ``%s''\n", + __FILE__, hostname, pmcd_port, hosterror(), hoststrerror()); + } +#endif + return -EHOSTUNREACH; + } + + __pmConnectTimeout(); + + enumIx = NULL; + for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx); + myAddr != NULL; + myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) { + /* Create a socket */ + if (__pmSockAddrIsInet(myAddr)) + fd = __pmCreateSocket(); + else if (__pmSockAddrIsIPv6(myAddr)) + fd = __pmCreateIPv6Socket(); + else { + __pmNotifyErr(LOG_ERR, + "%s:__pmAuxConnectPMCDPort(%s, %d) : invalid address family %d\n", + __FILE__, hostname, pmcd_port, __pmSockAddrGetFamily(myAddr)); + fd = -EINVAL; + } + if (fd < 0) { + __pmSockAddrFree(myAddr); + continue; /* Try the next address */ + } + + /* Attempt to connect */ + fdFlags = __pmConnectTo(fd, myAddr, pmcd_port); + __pmSockAddrFree(myAddr); + if (fdFlags < 0) { + /* + * Mark failure in case we fall out the end of the loop + * and try next address + */ + fd = -ECONNREFUSED; + continue; + } + + /* FNDELAY and we're in progress - wait on select */ + struct timeval stv = canwait; + struct timeval *pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL; + __pmFdSet wfds; + int rc; + + __pmFD_ZERO(&wfds); + __pmFD_SET(fd, &wfds); + sts = 0; + if ((rc = __pmSelectWrite(fd+1, &wfds, pstv)) == 1) { + sts = __pmConnectCheckError(fd); + } + else if (rc == 0) { + sts = ETIMEDOUT; + } + else { + sts = (rc < 0) ? neterror() : EINVAL; + } + + /* Successful connection? */ + if (sts == 0) + break; + + /* Unsuccessful connection. */ + __pmCloseSocket(fd); + fd = -sts; + } + + __pmHostEntFree(servInfo); + if (fd < 0) + return fd; + + /* + * If we're here, it means we have a valid connection; restore the + * flags and make sure this file descriptor is closed if exec() is + * called + */ + return __pmConnectRestoreFlags(fd, fdFlags); +} + +/* + * Return the path to the default PMCD local unix domain socket. + * Returns a pointer to a static buffer which can be used directly. + * Return the path regardless of whether unix domain sockets are + * supported by our build. Other functions can then print reasonable + * messages if an attempt is made to use one. + */ +const char * +__pmPMCDLocalSocketDefault(void) +{ + static char pmcd_socket[MAXPATHLEN]; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (pmcd_socket[0] == '\0') { + char *envstr; + if ((envstr = getenv("PMCD_SOCKET")) != NULL) + snprintf(pmcd_socket, sizeof(pmcd_socket), "%s", envstr); + else + snprintf(pmcd_socket, sizeof(pmcd_socket), "%s%c" "pmcd.socket", + pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator()); + } + PM_UNLOCK(__pmLock_libpcp); + + return pmcd_socket; +} + +int +__pmAuxConnectPMCDUnixSocket(const char *sock_path) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + __pmSockAddr *myAddr; + int fd = -1; /* Fd for socket connection to pmcd */ + int sts; + int fdFlags = 0; + struct timeval stv; + struct timeval *pstv; + __pmFdSet wfds; + int rc; + + __pmConnectTimeout(); + + /* Initialize the socket address. */ + myAddr = __pmSockAddrAlloc(); + if (myAddr == NULL) { + __pmNotifyErr(LOG_ERR, "%s:__pmAuxConnectPMCDUnixSocket(%s): out of memory\n", __FILE__, sock_path); + return -1; + } + __pmSockAddrSetFamily(myAddr, AF_UNIX); + __pmSockAddrSetPath(myAddr, sock_path); + + /* Create a socket */ + fd = __pmCreateUnixSocket(); + if (fd < 0) { + char errmsg[PM_MAXERRMSGLEN]; + + __pmNotifyErr(LOG_ERR, + "%s:__pmAuxConnectPMCDUnixSocket(%s): unable to create socket: %s\n", + __FILE__, sock_path, osstrerror_r(errmsg, sizeof(errmsg))); + __pmSockAddrFree(myAddr); + return fd; + } + + /* Attempt to connect */ + fdFlags = __pmConnectTo(fd, myAddr, -1); + __pmSockAddrFree(myAddr); + if (fdFlags < 0) + return -ECONNREFUSED; + + /* FNDELAY and we're in progress - wait on select */ + stv = canwait; + pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL; + __pmFD_ZERO(&wfds); + __pmFD_SET(fd, &wfds); + sts = 0; + if ((rc = __pmSelectWrite(fd+1, &wfds, pstv)) == 1) { + sts = __pmConnectCheckError(fd); + } + else if (rc == 0) { + sts = ETIMEDOUT; + } + else { + sts = (rc < 0) ? neterror() : EINVAL; + } + + if (sts != 0) { + /* Unsuccessful connection. */ + if (sts == ENOENT) + sts = ECONNREFUSED; + __pmCloseSocket(fd); + fd = -sts; + } + + if (fd < 0) + return fd; + + /* + * If we're here, it means we have a valid connection; restore the + * flags and make sure this file descriptor is closed if exec() is + * called + */ + return __pmConnectRestoreFlags(fd, fdFlags); +#else + __pmNotifyErr(LOG_ERR, + "%s:__pmAuxConnectPMCDUnixSocket(%s) is not supported\n", __FILE__, sock_path); + return -1; +#endif +} + +char * +__pmHostEntGetName(__pmHostEnt *he) +{ + __pmSockAddr *addr; + void *enumIx; + + if (he->name == NULL) { + /* Try to reverse lookup the host name. + * Check each address until the reverse lookup succeeds. + */ + enumIx = NULL; + for (addr = __pmHostEntGetSockAddr(he, &enumIx); + addr != NULL; + addr = __pmHostEntGetSockAddr(he, &enumIx)) { + he->name = __pmGetNameInfo(addr); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DESPERATE) + fprintf(stderr, "%s:__pmHostEntGetName: __pmGetNameInfo(%s) returns %s\n", __FILE__, __pmSockAddrToString(addr), he->name); +#endif + __pmSockAddrFree(addr); + if (he->name != NULL) + break; + } + if (he->name == NULL) + return NULL; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DESPERATE) + fprintf(stderr, "%s:__pmHostEntGetName -> %s\n", __FILE__, he->name); +#endif + + return strdup(he->name); +} + +__pmSockAddr * +__pmHostEntGetSockAddr(const __pmHostEnt *he, void **ei) +{ + __pmAddrInfo *ai; + __pmSockAddr *addr; + + /* The enumerator index (*ei) is actually a pointer to the current address info. */ + if (*ei == NULL) + *ei = ai = he->addresses; + else { + ai = *ei; + *ei = ai = ai->ai_next; + } + if (*ei == NULL) + return NULL; /* no (more) addresses in the chain. */ + + /* Now allocate a socket address and copy the data. */ + addr = __pmSockAddrAlloc(); + if (addr == NULL) { + __pmNotifyErr(LOG_ERR, "%s:__pmHostEntGetSockAddr: out of memory\n", __FILE__); + *ei = NULL; + return NULL; + } + memcpy(&addr->sockaddr.raw, ai->ai_addr, ai->ai_addrlen); + + return addr; +} + +char * +__pmGetNameInfo(__pmSockAddr *address) +{ + int sts; + char buf[NI_MAXHOST]; + + if (address->sockaddr.raw.sa_family == AF_INET) { + sts = getnameinfo(&address->sockaddr.raw, sizeof(address->sockaddr.inet), + buf, sizeof(buf), NULL, 0, 0); + } + else if (address->sockaddr.raw.sa_family == AF_INET6) { + sts = getnameinfo(&address->sockaddr.raw, sizeof(address->sockaddr.ipv6), + buf, sizeof(buf), NULL, 0, 0); + } +#if defined(HAVE_STRUCT_SOCKADDR_UN) + else if (address->sockaddr.raw.sa_family == AF_UNIX) { + /* The name info is the socket path. */ + return strdup(address->sockaddr.local.sun_path); + } +#endif + else { + __pmNotifyErr(LOG_ERR, + "%s:__pmGetNameInfo: Invalid address family: %d\n", __FILE__, address->sockaddr.raw.sa_family); + sts = EAI_FAMILY; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DESPERATE) { + if (sts != 0) { + fprintf(stderr, "%s:__pmGetNameInfo: family=%d getnameinfo()-> %d %s\n", __FILE__, address->sockaddr.raw.sa_family, sts, gai_strerror(sts)); + } + } +#endif + + + return sts == 0 ? strdup(buf) : NULL; +} + +__pmHostEnt * +__pmGetAddrInfo(const char *hostName) +{ + __pmHostEnt *hostEntry; + struct addrinfo hints; + int sts; + + hostEntry = __pmHostEntAlloc(); + if (hostEntry != NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ +#ifdef HAVE_AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; /* Only return configured address types */ +#endif + + sts = getaddrinfo(hostName, NULL, &hints, &hostEntry->addresses); + if (sts != 0) { + __pmHostEntFree(hostEntry); + hostEntry = NULL; + } + /* Leave the host name NULL. It will be looked up on demand in __pmHostEntGetName(). */ + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DESPERATE) { + if (hostEntry == NULL) + fprintf(stderr, "%s:__pmGetAddrInfo(%s) -> NULL\n", __FILE__, hostName); + else + fprintf(stderr, "%s:__pmGetAddrInfo(%s) -> %s\n", __FILE__, hostName, hostEntry->name); + } +#endif + return hostEntry; +} + +unsigned +__pmFirstInetSubnetAddr(unsigned ip, int maskBits) +{ + unsigned mask = ~((1 << (32 - maskBits)) - 1); + return ip & mask; +} + +unsigned +__pmNextInetSubnetAddr(unsigned ip, int maskBits) +{ + unsigned mask = (1 << (32 - maskBits)) - 1; + + /* Is this the final address? If so then return the address unchanged.*/ + if ((ip & mask) == mask) + return ip; + + /* Bump up the address. */ + return ++ip; +} + +unsigned char * +__pmFirstIpv6SubnetAddr(unsigned char *addr, int maskBits) +{ + unsigned mask; + int ix; + /* + * Manipulate the ipv6 address one byte at a time. There is no + * host/network byte order. + * Mask the byte at the subnet mask boundary. Leave the higher order bytes + * alone and clear the lower order bytes. + */ + ix = maskBits / 8; + if (ix < 16) { + maskBits %= 8; + mask = ~((1 << (8 - maskBits)) - 1); + addr[ix] &= mask; + for (++ix; ix < 16; ++ix) + addr[ix] = 0; + } + + return addr; +} + +unsigned char * +__pmNextIpv6SubnetAddr(unsigned char *addr, int maskBits) +{ + unsigned mask; + int ix, ix1; + /* + * Manipulate the ipv6 address one byte at a time. There is no + * host/network byte order. + * First determine whether this is the final address. Do this by + * comparing the high order bits of the subnet against the maximum. + */ + ix = maskBits / 8; + if (ix < 16) { + maskBits %= 8; + mask = (1 << (8 - maskBits)) - 1; + if ((addr[ix] & mask) == mask) { + /* The highest order bits are maxed out. Check the remaining bits. */ + for (++ix; ix < 16; ++ix) { + if (addr[ix] != 0xff) + break; + } + } + } + if (ix >= 16) { + /* This is the final address. */ + return NULL; + } + + /* Bump up the address. Don't forget to carry into the higher order bits + when necessary. */ + for (ix1 = 15; ix1 >= ix; --ix1) { + ++addr[ix1]; + if (addr[ix1] != 0) + break; /* no carry */ + } + + return addr; +} + +#if !defined(HAVE_SECURE_SOCKETS) + +void +__pmCloseSocket(int fd) +{ + __pmResetIPC(fd); +#if defined(IS_MINGW) + closesocket(fd); +#else + close(fd); +#endif +} + +int +__pmSetSockOpt(int socket, int level, int option_name, const void *option_value, + __pmSockLen option_len) +{ + return setsockopt(socket, level, option_name, option_value, option_len); +} + +int +__pmGetSockOpt(int socket, int level, int option_name, void *option_value, + __pmSockLen *option_len) +{ + return getsockopt(socket, level, option_name, option_value, option_len); +} + +int +__pmInitCertificates(void) +{ + return 0; +} + +int +__pmShutdownCertificates(void) +{ + return 0; +} + +int +__pmInitSecureSockets(void) +{ + return 0; +} + +int +__pmShutdownSecureSockets(void) +{ + return 0; +} + +int +__pmInitAuthClients(void) +{ + return 0; +} + +int +__pmDataIPCSize(void) +{ + return 0; +} + +int +__pmSecureClientHandshake(int fd, int flags, const char *hostname, __pmHashCtl *attrs) +{ + (void)fd; + (void)hostname; + + /* + * We cannot handle many flags here (no support), in particular: + * PDU_FLAG_SECURE (NSS) + * PDU_FLAG_SECURE_ACK (NSS) + * PDU_FLAG_NO_NSS_INIT (NSS) + * PDU_FLAG_COMPRESS (NSS) + * PDU_FLAG_AUTH (SASL2) + * + * But we can still talk to a pmcd that requires credentials, provided + * we are using unix domain sockets (the kernel provides the auth info + * to pmcd in this case, with no other special sauce required). + */ + if (flags == PDU_FLAG_CREDS_REQD) + if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL) + return 0; + return -EOPNOTSUPP; +} + +int +__pmSocketClosed(void) +{ + switch (oserror()) { + /* + * Treat this like end of file on input. + * + * failed as a result of pmcd exiting and the connection + * being reset, or as a result of the kernel ripping + * down the connection (most likely because the host at + * the other end just took a dive) + * + * from IRIX BDS kernel sources, seems like all of the + * following are peers here: + * ECONNRESET (pmcd terminated?) + * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH + * ECONNREFUSED + * peers for BDS but not here: + * ENETRESET ENONET ESHUTDOWN (cache_fs only?) + * ECONNABORTED (accept, user req only?) + * ENOTCONN (udp?) + * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?) + */ + case ECONNRESET: + case EPIPE: + case ETIMEDOUT: + case ENETDOWN: + case ENETUNREACH: + case EHOSTDOWN: + case EHOSTUNREACH: + case ECONNREFUSED: + return 1; + } + return 0; +} + +ssize_t +__pmWrite(int socket, const void *buffer, size_t length) +{ + return write(socket, buffer, length); +} + +ssize_t +__pmRead(int socket, void *buffer, size_t length) +{ + return read(socket, buffer, length); +} + +ssize_t +__pmSend(int socket, const void *buffer, size_t length, int flags) +{ + return send(socket, buffer, length, flags); +} + +ssize_t +__pmRecv(int socket, void *buffer, size_t length, int flags) +{ + ssize_t size; + size = recv(socket, buffer, length, flags); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "%s:__pmRecv(%d, ..., %d, " PRINTF_P_PFX "%x) -> %d\n", + __FILE__, socket, (int)length, flags, (int)size); + } +#endif + return size; +} + +int +__pmSocketReady(int fd, struct timeval *timeout) +{ + __pmFdSet onefd; + + FD_ZERO(&onefd); + FD_SET(fd, &onefd); + return select(fd+1, &onefd, NULL, NULL, timeout); +} + +#endif /* !HAVE_SECURE_SOCKETS */ diff --git a/src/libpcp/src/auxserver.c b/src/libpcp/src/auxserver.c new file mode 100644 index 0000000..498bac4 --- /dev/null +++ b/src/libpcp/src/auxserver.c @@ -0,0 +1,940 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * + * 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. + */ + +#include <sys/stat.h> + +#include "pmapi.h" +#include "impl.h" +#define SOCKET_INTERNAL +#include "internal.h" +#if defined(HAVE_GETPEERUCRED) +#include <ucred.h> +#endif + +#define STRINGIFY(s) #s +#define TO_STRING(s) STRINGIFY(s) + +/* + * Info about a request port that clients may connect to a server on + */ +enum { + INET_FD = 0, + IPV6_FD, + FAMILIES +}; +typedef struct { + int fds[FAMILIES]; /* Inet and IPv6 File descriptors */ + int port; /* Listening port */ + const char *address; /* Network address string (or NULL) */ + __pmServerPresence *presence; /* For advertising server presence on the network. */ +} ReqPortInfo; + +static unsigned nReqPorts; /* number of ports */ +static unsigned szReqPorts; /* capacity of ports array */ +static ReqPortInfo *reqPorts; /* ports array */ + +/* + * Interfaces we're willing to listen for clients on, from -i + */ +static int nintf; +static char **intflist; + +/* + * Ports we're willing to listen for clients on, from -p (or env) + */ +static int nport; +static int *portlist; + +/* + * The unix domain socket we're willing to listen for clients on, + * from -s (or env) + */ +static const char *localSocketPath; +static int localSocketFd = -EPROTO; +static const char *serviceSpec; + +int +__pmServiceAddPorts(const char *service, int **ports, int nports) +{ + /* + * The list of ports referenced by *ports may be (re)allocated + * using calls to realloc(3) with a new size based on nports. + * For an empty list, *ports must be NULL and nports must be 0. + * It is the responsibility of the caller to free this memory. + * + * If -EOPNOTSUPP is not returned, then this function is + * guaranteed to return a list containing at least 1 element. + * + * The service is a service name (e.g. pmcd). + */ + if (strcmp(service, PM_SERVER_SERVICE_SPEC) == 0) + nports = __pmPMCDAddPorts(ports, nports); + else if (strcmp(service, PM_SERVER_PROXY_SPEC) == 0) + nports = __pmProxyAddPorts(ports, nports); + else if (strcmp(service, PM_SERVER_WEBD_SPEC) == 0) + nports = __pmWebdAddPorts(ports, nports); + else + nports = -EOPNOTSUPP; + + return nports; +} + +int +__pmPMCDAddPorts(int **ports, int nports) +{ + /* + * The list of ports referenced by *ports may be (re)allocated + * using calls to realloc(3) with a new size based on nports. + * For an empty list, *ports must be NULL and nports must be 0. + * It is the responsibility of the caller to free this memory. + * + * This function is guaranteed to return a list containing at least + * 1 element. + */ + char *env; + int new_nports = nports; + + if ((env = getenv("PMCD_PORT")) != NULL) + new_nports = __pmAddPorts(env, ports, nports); + + /* + * Add the default port, if no new ports were added or if there was an + * error. + */ + if (new_nports <= nports) + new_nports = __pmAddPorts(TO_STRING(SERVER_PORT), ports, nports); + + return new_nports; +} + +int +__pmProxyAddPorts(int **ports, int nports) +{ + /* + * The list of ports referenced by *ports may be (re)allocated + * using calls to realloc(3) with a new size based on nports. + * For an empty list, *ports must be NULL and nports must be 0. + * It is the responsibility of the caller to free this memory. + * + * This function is guaranteed to return a list containing at least + * 1 element. + */ + char *env; + int new_nports = nports; + + if ((env = getenv("PMPROXY_PORT")) != NULL) + new_nports = __pmAddPorts(env, ports, nports); + + /* + * Add the default port, if no new ports were added or if there was an + * error. + */ + if (new_nports <= nports) + new_nports = __pmAddPorts(TO_STRING(PROXY_PORT), ports, nports); + + return new_nports; +} + +int +__pmWebdAddPorts(int **ports, int nports) +{ + /* + * The list of ports referenced by *ports may be (re)allocated + * using calls to realloc(3) with a new size based on nports. + * For an empty list, *ports must be NULL and nports must be 0. + * It is the responsibility of the caller to free this memory. + * + * This function is guaranteed to return a list containing at least + * 1 element. + */ + char *env; + int new_nports = nports; + + if ((env = getenv("PMWEBD_PORT")) != NULL) + new_nports = __pmAddPorts(env, ports, nports); + + /* + * Add the default port, if no new ports were added or if there was an + * error. + */ + if (new_nports <= nports) + new_nports = __pmAddPorts(TO_STRING(PMWEBD_PORT), ports, nports); + + return new_nports; +} + +int +__pmAddPorts(const char *portstr, int **ports, int nports) +{ + /* + * The list of ports referenced by *ports may be (re)allocated + * using calls to realloc(3) with a new size based on nports. + * For an empty list, *ports must be NULL and nports must be 0. + * It is the responsibility of the caller to free this memory. + * + * If sufficient memory cannot be allocated, then this function + * calls __pmNoMem() and does not return. + */ + char *endptr, *p = (char *)portstr; + size_t size; + + /* + * one (of possibly several) ports for client requests + * ... accept a comma separated list of ports here + */ + for ( ; ; ) { + int port = (int)strtol(p, &endptr, 0); + if ((*endptr != '\0' && *endptr != ',') || port < 0) + return -EINVAL; + + size = (nports + 1) * sizeof(int); + if ((*ports = (int *)realloc(*ports, size)) == NULL) + __pmNoMem("__pmAddPorts: cannot grow port list", size, PM_FATAL_ERR); + (*ports)[nports++] = port; + if (*endptr == '\0') + break; + p = &endptr[1]; + } + return nports; +} + +int +__pmServerAddPorts(const char *ports) +{ + nport = __pmAddPorts(ports, &portlist, nport); + return nport; +} + +int +__pmServerAddInterface(const char *address) +{ + size_t size = (nintf+1) * sizeof(char *); + char *intf; + + /* one (of possibly several) IP addresses for client requests */ + intflist = (char **)realloc(intflist, nintf * sizeof(char *)); + if (intflist == NULL) + __pmNoMem("AddInterface: cannot grow interface list", size, PM_FATAL_ERR); + if ((intf = strdup(address)) == NULL) + __pmNoMem("AddInterface: cannot strdup interface", strlen(address), PM_FATAL_ERR); + intflist[nintf++] = intf; + return nintf; +} + +void +__pmServerSetLocalSocket(const char *path) +{ + if (path != NULL && *path != '\0') + localSocketPath = strdup(path); + else + localSocketPath = __pmPMCDLocalSocketDefault(); +} + +void +__pmServerSetServiceSpec(const char *spec) +{ + if (spec != NULL && *spec != '\0') + serviceSpec = strdup(spec); + else + serviceSpec = PM_SERVER_SERVICE_SPEC; +} + +static void +pidonexit(void) +{ + char pidpath[MAXPATHLEN]; + + if (serviceSpec) { + snprintf(pidpath, sizeof(pidpath), "%s%c%s.pid", + pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator(), serviceSpec); + unlink(pidpath); + } +} + +int +__pmServerCreatePIDFile(const char *spec, int verbose) +{ + char pidpath[MAXPATHLEN]; + FILE *pidfile; + + if (!serviceSpec) + __pmServerSetServiceSpec(spec); + + snprintf(pidpath, sizeof(pidpath), "%s%c%s.pid", + pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator(), spec); + + if ((pidfile = fopen(pidpath, "w")) == NULL) { + if (verbose) + fprintf(stderr, "Error: cannot open PID file %s\n", pidpath); + return -oserror(); + } + atexit(pidonexit); + fprintf(pidfile, "%" FMT_PID, getpid()); + fclose(pidfile); + chmod(pidpath, S_IRUSR | S_IRGRP | S_IROTH); + return 0; +} + +void +__pmCheckAcceptedAddress(__pmSockAddr *addr) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + /* + * accept(3) doesn't set the peer address for unix domain sockets. + * We need to do it ourselves. The address family + * is set, so we can use it to test. There is only one unix domain socket + * open, so we know its path. + */ + if (__pmSockAddrGetFamily(addr) == AF_UNIX) + __pmSockAddrSetPath(addr, localSocketPath); +#endif +} + +/* Increase the capacity of the reqPorts array (maintain the contents) */ +static void +GrowRequestPorts(void) +{ + size_t need; + + szReqPorts += 4; + need = szReqPorts * sizeof(ReqPortInfo); + reqPorts = (ReqPortInfo*)realloc(reqPorts, need); + if (reqPorts == NULL) { + __pmNoMem("GrowRequestPorts: can't grow request port array", + need, PM_FATAL_ERR); + } +} + +/* Add a request port to the reqPorts array */ +static int +AddRequestPort(const char *address, int port) +{ + ReqPortInfo *rp; + + if (address == NULL) + address = "INADDR_ANY"; + + if (nReqPorts == szReqPorts) + GrowRequestPorts(); + rp = &reqPorts[nReqPorts]; + rp->fds[INET_FD] = -1; + rp->fds[IPV6_FD] = -1; + rp->address = address; + rp->port = port; + rp->presence = NULL; + nReqPorts++; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "AddRequestPort: %s port %d\n", rp->address, rp->port); +#endif + + return nReqPorts; /* success */ +} + +static int +SetupRequestPorts(void) +{ + int i, n; + + /* check for duplicate ports, remove duplicates */ + for (i = 0; i < nport; i++) { + for (n = i + 1; n < nport; n++) { + if (portlist[i] == portlist[n]) + break; + } + if (n < nport) { + __pmNotifyErr(LOG_WARNING, + "%s: duplicate client request port (%d) will be ignored\n", + pmProgname, portlist[n]); + portlist[n] = -1; + } + } + + if (nintf == 0) { + /* no interface options specified, allow connections on any address */ + for (n = 0; n < nport; n++) { + if (portlist[n] != -1) + AddRequestPort(NULL, portlist[n]); + } + } + else { + for (i = 0; i < nintf; i++) { + for (n = 0; n < nport; n++) { + if (portlist[n] == -1 || intflist[i] == NULL) + continue; + AddRequestPort(intflist[i], portlist[n]); + } + } + } + return 0; +} + +static const char * +AddressFamily(int family) +{ + if (family == AF_INET) + return "inet"; + if (family == AF_INET6) + return "ipv6"; +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (family == AF_UNIX) + return "unix"; +#endif + return "unknown"; +} + +/* + * Create socket for incoming connections and bind to it an address for + * clients to use. Returns -1 on failure. + * + * If '*family' is AF_UNIX and unix domain sockets are supported: + * 'port' is ignored and 'address' is the path to the socket file in the filesystem. + * + * Otherwise: + * address is a string representing the Inet/IPv6 address that the port + * is advertised for. To allow connections to all this host's internet + * addresses from clients use address = "INADDR_ANY". + * On input, 'family' is a pointer to the address family to use (AF_INET, + * AF_INET6) if the address specified is empty. If the spec is not + * empty then family is ignored and is set to the actual address family + * used. 'family' must be initialized to AF_UNSPEC, in this case. + */ +static int +OpenRequestSocket(int port, const char *address, int *family, + int backlog, __pmFdSet *fdset, int *maximum) +{ + int fd = -1; + int one, sts; + __pmSockAddr *myAddr; + int isUnix = 0; + + /* + * Using this flag will eliminate the need for more conditional + * compilation below, hopefully making the code easier to read and maintain. + */ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (*family == AF_UNIX) + isUnix = 1; +#endif + + if (isUnix) { + if ((myAddr = __pmSockAddrAlloc()) == NULL) { + __pmNoMem("OpenRequestSocket: can't allocate socket address", + sizeof(*myAddr), PM_FATAL_ERR); + } + + /* Initialize the address. */ + __pmSockAddrSetFamily(myAddr, *family); + __pmSockAddrSetPath(myAddr, address); + + /* Create the socket. */ + fd = __pmCreateUnixSocket(); + } + else { + if ((myAddr = __pmStringToSockAddr(address)) == NULL) { + __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s) invalid address\n", + port, address); + goto fail; + } + + /* + * If the address is unspecified, then use the address family we + * have been given, otherwise the family will be determined by + * __pmStringToSockAddr. + */ + if (address == NULL || strcmp(address, "INADDR_ANY") == 0) + __pmSockAddrSetFamily(myAddr, *family); + else + *family = __pmSockAddrGetFamily(myAddr); + __pmSockAddrSetPort(myAddr, port); + + /* Create the socket. */ + if (*family == AF_INET) + fd = __pmCreateSocket(); + else if (*family == AF_INET6) + fd = __pmCreateIPv6Socket(); + else { + __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s) invalid address family: %d\n", + port, address, *family); + goto fail; + } + } + + if (fd < 0) { + __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmCreateSocket: %s\n", + port, address, AddressFamily(*family), netstrerror()); + goto fail; + } + + /* Ignore dead client connections. */ + one = 1; +#ifndef IS_MINGW + if (__pmSetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, + (__pmSockLen)sizeof(one)) < 0) { + __pmNotifyErr(LOG_ERR, + "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(SO_REUSEADDR): %s\n", + port, address, AddressFamily(*family), netstrerror()); + goto fail; + } +#else + if (__pmSetSockOpt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&one, + (__pmSockLen)sizeof(one)) < 0) { + __pmNotifyErr(LOG_ERR, + "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(EXCLUSIVEADDRUSE): %s\n", + port, address, AddressFamily(*family), netstrerror()); + goto fail; + } +#endif + + /* and keep alive please - bad networks eat fds */ + if (__pmSetSockOpt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, + (__pmSockLen)sizeof(one)) < 0) { + __pmNotifyErr(LOG_ERR, + "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(SO_KEEPALIVE): %s\n", + port, address, AddressFamily(*family), netstrerror()); + goto fail; + } + + sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize()); + __pmSockAddrFree(myAddr); + myAddr = NULL; + if (sts < 0) { + sts = neterror(); + __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmBind: %s\n", + port, address, AddressFamily(*family), netstrerror()); + if (sts == EADDRINUSE) + __pmNotifyErr(LOG_ERR, "%s may already be running\n", pmProgname); + goto fail; + } + + if (isUnix) { + /* + * For unix domain sockets, grant rw access to the socket for all, + * otherwise, on linux platforms, connection will not be possible. + * This must be done AFTER binding the address. See Unix(7) for details. + */ + sts = chmod(address, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (sts != 0) { + __pmNotifyErr(LOG_ERR, + "OpenRequestSocket(%d, %s, %s) chmod(%s): %s\n", + port, address, AddressFamily(*family), address, strerror(errno)); + goto fail; + } + } + + sts = __pmListen(fd, backlog); /* Max. pending connection requests */ + if (sts < 0) { + __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmListen: %s\n", + port, address, AddressFamily(*family), netstrerror()); + goto fail; + } + + if (fd > *maximum) + *maximum = fd; + __pmFD_SET(fd, fdset); + return fd; + +fail: + if (fd != -1) { + __pmCloseSocket(fd); + /* We must unlink the socket file. */ + if (isUnix) + unlink(address); + } + if (myAddr) + __pmSockAddrFree(myAddr); + return -1; +} + +/* + * Open request ports for client connections. + * For each request port, open an inet and IPv6 (if supported) socket + * for clients. Also open a local unix domain socket, if it has been + * specified + */ +static int +OpenRequestPorts(__pmFdSet *fdset, int backlog) +{ + int i, fd, family, success = 0, maximum = -1; + int with_ipv6 = strcmp(__pmGetAPIConfig("ipv6"), "true") == 0; + + for (i = 0; i < nReqPorts; i++) { + ReqPortInfo *rp = &reqPorts[i]; + int portsOpened = 0;; + + /* + * If the spec is NULL or "INADDR_ANY", then we open one socket + * for each address family (inet, IPv6). Otherwise, the address + * family will be determined by OpenRequestSocket. Reporting of + * all errors is left to OpenRequestSocket to avoid doubling up. + */ + if (rp->address == NULL || strcmp(rp->address, "INADDR_ANY") == 0) { + family = AF_INET; + if ((fd = OpenRequestSocket(rp->port, rp->address, &family, + backlog, fdset, &maximum)) >= 0) { + rp->fds[INET_FD] = fd; + ++portsOpened; + success = 1; + } + if (with_ipv6) { + family = AF_INET6; + if ((fd = OpenRequestSocket(rp->port, rp->address, &family, + backlog, fdset, &maximum)) >= 0) { + rp->fds[IPV6_FD] = fd; + ++portsOpened; + success = 1; + } + } + else + rp->fds[IPV6_FD] = -EPROTO; + } + else { + family = AF_UNSPEC; + if ((fd = OpenRequestSocket(rp->port, rp->address, &family, + backlog, fdset, &maximum)) >= 0) { + if (family == AF_INET) { + rp->fds[INET_FD] = fd; + ++portsOpened; + success = 1; + } + else if (family == AF_INET6) { + rp->fds[IPV6_FD] = fd; + ++portsOpened; + success = 1; + } + } + } + if (portsOpened > 0) { + /* Advertise our presence on the network, if requested. */ + if (serviceSpec != NULL) { + rp->presence = __pmServerAdvertisePresence(serviceSpec, + rp->port); + } + } + } + + /* Open a local unix domain socket, if specified, and supported. */ + if (localSocketPath != NULL) { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + family = AF_UNIX; + if ((localSocketFd = OpenRequestSocket(0, localSocketPath, &family, + backlog, fdset, &maximum)) >= 0) { + __pmServerSetFeature(PM_SERVER_FEATURE_UNIX_DOMAIN); + success = 1; + } +#else + __pmNotifyErr(LOG_ERR, "%s: unix domain sockets are not supported\n", + pmProgname); +#endif + } + + if (success) + return maximum; + + __pmNotifyErr(LOG_ERR, "%s: can't open any request ports, exiting\n", + pmProgname); + return -1; +} + +int +__pmServerOpenRequestPorts(__pmFdSet *fdset, int backlog) +{ + int sts; + + if ((sts = SetupRequestPorts()) < 0) + return sts; + return OpenRequestPorts(fdset, backlog); +} + +void +__pmServerCloseRequestPorts(void) +{ + int i, fd; + + for (i = 0; i < nReqPorts; i++) { + /* No longer advertise our presence on the network. */ + if (reqPorts[i].presence != NULL) + __pmServerUnadvertisePresence(reqPorts[i].presence); + if ((fd = reqPorts[i].fds[INET_FD]) >= 0) + __pmCloseSocket(fd); + if ((fd = reqPorts[i].fds[IPV6_FD]) >= 0) + __pmCloseSocket(fd); + } +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (localSocketFd >= 0) { + __pmCloseSocket(localSocketFd); + localSocketFd = -EPROTO; + + /* We must remove the socket file. */ + if (unlink(localSocketPath) != 0 && oserror() != ENOENT) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, "%s: can't unlink %s (uid=%d,euid=%d): %s", + pmProgname, localSocketPath, getuid(), geteuid(), + osstrerror_r(errmsg, sizeof(errmsg))); + } + } +#endif +} + +/* + * Accept any new client connections + */ +void +__pmServerAddNewClients(__pmFdSet *fdset, __pmServerCallback NewClient) +{ + int i, fd; + +#if defined(HAVE_STRUCT_SOCKADDR_UN) + /* Check the local unix domain fd. */ + if (localSocketFd >= 0) + NewClient(fdset, localSocketFd, AF_UNIX); +#endif + + for (i = 0; i < nReqPorts; i++) { + /* Check the inet and ipv6 fds. */ + if ((fd = reqPorts[i].fds[INET_FD]) >= 0) + NewClient(fdset, fd, AF_INET); + if ((fd = reqPorts[i].fds[IPV6_FD]) >= 0) + NewClient(fdset, fd, AF_INET6); + } +} + +static int +SetCredentialAttrs(__pmHashCtl *attrs, unsigned int pid, unsigned int uid, unsigned int gid) +{ + char name[32], *namep; + + snprintf(name, sizeof(name), "%u", uid); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_USERID, namep, attrs); + + snprintf(name, sizeof(name), "%u", gid); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs); + + if (!pid) /* not available on all platforms */ + return 0; + + snprintf(name, sizeof(name), "%u", pid); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_PROCESSID, namep, attrs); + + return 0; +} + +/* + * Set local connection credentials into given hash structure + */ +int +__pmServerSetLocalCreds(int fd, __pmHashCtl *attrs) +{ +#if defined(HAVE_STRUCT_UCRED) /* Linux */ + struct ucred ucred; + __pmSockLen length = sizeof(ucred); + + if (__pmGetSockOpt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length) < 0) + return -oserror(); + return SetCredentialAttrs(attrs, ucred.pid, ucred.uid, ucred.gid); + +#elif defined(HAVE_GETPEEREID) /* MacOSX */ + uid_t uid; + gid_t gid; + + if (getpeereid(__pmFD(fd), &uid, &gid) < 0) + return -oserror(); + return SetCredentialAttrs(attrs, 0, uid, gid); + +#elif defined(HAVE_GETPEERUCRED) /* Solaris */ + unsigned int uid, gid, pid; + ucred_t *ucred = NULL; + + if (getpeerucred(__pmFD(fd), &ucred) < 0) + return -oserror(); + pid = ucred_getpid(ucred); + uid = ucred_geteuid(ucred); + gid = ucred_getegid(ucred); + ucred_free(ucred); + return SetCredentialAttrs(attrs, pid, uid, gid); + +#else + return -EOPNOTSUPP; +#endif +} + +static const char * +RequestFamilyString(int index) +{ + if (index == INET_FD) + return "inet"; + if (index == IPV6_FD) + return "ipv6"; + return "unknown"; +} + +void +__pmServerDumpRequestPorts(FILE *stream) +{ + int i, j; + + fprintf(stream, "%s request port(s):\n" + " sts fd port family address\n" + " === ==== ===== ====== =======\n", pmProgname); + + if (localSocketFd != -EPROTO) + fprintf(stderr, " %-3s %4d %5s %-6s %s\n", + (localSocketFd != -1) ? "ok" : "err", + localSocketFd, "", "unix", + localSocketPath); + + for (i = 0; i < nReqPorts; i++) { + ReqPortInfo *rp = &reqPorts[i]; + for (j = 0; j < FAMILIES; j++) { + if (rp->fds[j] != -EPROTO) + fprintf(stderr, " %-3s %4d %5d %-6s %s\n", + (rp->fds[j] != -1) ? "ok" : "err", + rp->fds[j], rp->port, RequestFamilyString(j), + rp->address ? rp->address : "(any address)"); + } + } +} + +char * +__pmServerRequestPortString(int fd, char *buffer, size_t sz) +{ + int i, j; + + if (fd == localSocketFd) { + snprintf(buffer, sz, "%s unix request socket %s", + pmProgname, localSocketPath); + return buffer; + } + + for (i = 0; i < nReqPorts; i++) { + ReqPortInfo *rp = &reqPorts[i]; + for (j = 0; j < FAMILIES; j++) { + if (fd == rp->fds[j]) { + snprintf(buffer, sz, "%s %s request socket %s", + pmProgname, RequestFamilyString(j), rp->address); + return buffer; + } + } + } + return NULL; +} + +#if !defined(HAVE_SECURE_SOCKETS) + +int +__pmSecureServerSetup(const char *db, const char *passwd) +{ + (void)db; + (void)passwd; + return 0; +} + +int +__pmSecureServerIPCFlags(int fd, int flags) +{ + (void)fd; + (void)flags; + return -EOPNOTSUPP; +} + +void +__pmSecureServerShutdown(void) +{ + /* nothing to do here */ +} + +int +__pmSecureServerHandshake(int fd, int flags, __pmHashCtl *attrs) +{ + (void)fd; + (void)flags; + (void)attrs; + return -EOPNOTSUPP; +} + +int +__pmSecureServerHasFeature(__pmServerFeature query) +{ + (void)query; + return 0; +} + +int +__pmSecureServerSetFeature(__pmServerFeature wanted) +{ + (void)wanted; + return 0; +} + +int +__pmSecureServerClearFeature(__pmServerFeature clear) +{ + (void)clear; + return 0; +} + +#endif /* !HAVE_SECURE_SOCKETS */ + +static unsigned int server_features; + +int +__pmServerClearFeature(__pmServerFeature clear) +{ + if (clear == PM_SERVER_FEATURE_DISCOVERY) { + server_features &= ~(1<<clear); + return 1; + } + return __pmSecureServerClearFeature(clear); +} + +int +__pmServerSetFeature(__pmServerFeature wanted) +{ + if (wanted == PM_SERVER_FEATURE_DISCOVERY || + wanted == PM_SERVER_FEATURE_CREDS_REQD || + wanted == PM_SERVER_FEATURE_UNIX_DOMAIN) { + server_features |= (1 << wanted); + return 1; + } + return __pmSecureServerSetFeature(wanted); +} + +int +__pmServerHasFeature(__pmServerFeature query) +{ + int sts = 0; + + switch (query) { + case PM_SERVER_FEATURE_IPV6: + sts = (strcmp(__pmGetAPIConfig("ipv6"), "true") == 0); + break; + case PM_SERVER_FEATURE_DISCOVERY: + case PM_SERVER_FEATURE_CREDS_REQD: + case PM_SERVER_FEATURE_UNIX_DOMAIN: + if (server_features & (1 << query)) + sts = 1; + break; + default: + sts = __pmSecureServerHasFeature(query); + break; + } + return sts; +} diff --git a/src/libpcp/src/avahi.c b/src/libpcp/src/avahi.c new file mode 100644 index 0000000..26d73a5 --- /dev/null +++ b/src/libpcp/src/avahi.c @@ -0,0 +1,800 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * + * 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. + */ + +#include <assert.h> +#include <avahi-client/publish.h> +#include <avahi-client/lookup.h> +#include <avahi-common/alternative.h> +#include <avahi-common/simple-watch.h> +#include <avahi-common/thread-watch.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include "avahi.h" + +/* Support for servers advertising their presence. */ +static AvahiThreadedPoll *threadedPoll; +static AvahiClient *client; +static AvahiEntryGroup *group; + +static __pmServerPresence **activeServices; +static int nActiveServices; +static int szActiveServices; + +struct __pmServerAvahiPresence { + char *serviceName; + char *serviceTag; + int collisions; +}; + +static void entryGroupCallback(AvahiEntryGroup *, AvahiEntryGroupState, void *); + +static int +renameService(__pmServerPresence *s) +{ + /* + * Each service must have a unique name on the local network. + * When there is a collision, we try to rename the service. + * However, we need to limit the number of attempts, since the + * service namespace could be maliciously flooded with service + * names designed to maximize collisions. + * Arbitrarily choose a limit of 65535, which is the number of + * TCP ports. + */ + ++s->avahi->collisions; + if (s->avahi->collisions >= 65535) { + __pmNotifyErr(LOG_ERR, "Too many service name collisions for Avahi service %s", + s->avahi->serviceTag); + return -EBUSY; + } + + /* + * Use the avahi-supplied function to generate a new service name. + */ + char *n = avahi_alternative_service_name(s->avahi->serviceName); + + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_INFO, "Avahi service name collision, renaming service '%s' to '%s'", + s->avahi->serviceName, n); + avahi_free(s->avahi->serviceName); + s->avahi->serviceName = n; + + return 0; +} + +static int +renameServices(void) +{ + int i; + int rc = 0; + __pmServerPresence *s; + + for (i = 0; i < szActiveServices; ++i) { + s = activeServices[i]; + if (s == NULL) + continue; /* empty entry */ + if ((rc = renameService(s)) < 0) + break; + } + + return rc; +} + +static void +createServices(AvahiClient *c) +{ + __pmServerPresence *s; + int ret; + int i; + + assert(c); + + /* + * Create a new entry group, if necessary, or reset the existing one. + */ + if (group == NULL) { + if ((group = avahi_entry_group_new(c, entryGroupCallback, NULL)) == NULL) { + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_ERR, "avahi_entry_group_new failed: %s", + avahi_strerror(avahi_client_errno(c))); + return; + } + } + else + avahi_entry_group_reset(group); + + /* + * We will now add our services to the entry group. + */ + for (i = 0; i < szActiveServices; ++i) { + s = activeServices[i]; + if (s == NULL) + continue; /* empty table entry */ + + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_INFO, "Adding %s Avahi service on port %d", + s->avahi->serviceName, s->port); + + /* Loop until no collisions */ + for (;;) { + ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + (AvahiPublishFlags)0, + s->avahi->serviceName, + s->avahi->serviceTag, + NULL, NULL, s->port, NULL); + if (ret == AVAHI_OK) + break; /* success! */ + if (ret == AVAHI_ERR_COLLISION) { + /* + * A service name collision with a local service happened. + * Pick a new name. Since a service may be listening on + * multiple ports, this is expected to happen sometimes - + * do not issue warnings here. + */ + if (renameService(s) < 0) { + /* Too many collisions. Message already issued */ + goto fail; + } + continue; /* try again */ + } + + __pmNotifyErr(LOG_ERR, "Failed to add %s Avahi service on port %d: %s", + s->avahi->serviceName, s->port, avahi_strerror(ret)); + goto fail; + } + } + + /* Tell the server to register the services. */ + if ((ret = avahi_entry_group_commit(group)) < 0) { + __pmNotifyErr(LOG_ERR, "Failed to commit avahi entry group: %s", + avahi_strerror(ret)); + goto fail; + } + + return; + + fail: + avahi_entry_group_reset(group); +} + +static void +entryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *data) +{ + (void)data; + + assert(g != NULL); + assert(group == NULL || group == g); + group = g; + + /* Called whenever the entry group state changes. */ + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully. */ + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_INFO, "Avahi services successfully established."); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + /* + * A service name collision with a remote service happened. + * Unfortunately, we don't know which entry collided. + * We need to rename them all and recreate the services. + */ + if (renameServices() == 0) + createServices(avahi_entry_group_get_client(g)); + break; + + case AVAHI_ENTRY_GROUP_FAILURE: + /* Some kind of failure happened. */ + __pmNotifyErr(LOG_ERR, "Avahi entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +static void +cleanupClient(void) +{ + /* This also frees the entry group, if any. */ + if (client) { + avahi_client_free(client); + client = NULL; + group = NULL; + } +} + +static void +advertisingClientCallback(AvahiClient *c, AvahiClientState state, void *userData) +{ + assert(c); + (void)userData; + + /* Called whenever the client or server state changes. */ + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + /* + * The server has started successfully and registered its host + * name on the network, so it's time to create our services. + */ + createServices(c); + break; + + case AVAHI_CLIENT_FAILURE: + __pmNotifyErr(LOG_ERR, "Avahi client failure: %s", + avahi_strerror(avahi_client_errno(c))); + if (avahi_client_errno (c) == AVAHI_ERR_DISCONNECTED) { + int error; + /* + * The client has been disconnected; probably because the + * avahi daemon has been restarted. We can free the client + * here and try to reconnect using a new one. + * Passing AVAHI_CLIENT_NO_FAIL allows the new client to be + * created, even if the avahi daemon is not running. Our + * service will be advertised if/when the daemon is started. + */ + cleanupClient(); + client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), + (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL, + advertisingClientCallback, NULL, & error); + } + break; + + case AVAHI_CLIENT_S_COLLISION: + /* + * Drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. + * Fall through ... + */ + case AVAHI_CLIENT_S_REGISTERING: + /* + * The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. + */ + if (group) + avahi_entry_group_reset (group); + break; + + case AVAHI_CLIENT_CONNECTING: + /* + * The avahi-daemon is not currently running. Our service will be + * advertised if/when the daemon is started. + */ + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_INFO, + "The Avahi daemon is not running. " + "Avahi services will be established when the daemon is started"); + break; + } +} + +static void +cleanup(__pmServerAvahiPresence *s) +{ + if (s == NULL) + return; + + if (s->serviceName) { + avahi_free(s->serviceName); + s->serviceName = NULL; + } + if (s->serviceTag) { + avahi_free(s->serviceTag); + s->serviceTag = NULL; + } +} + +/* Publish a new service. */ +static void +addService(__pmServerPresence *s) +{ + int i; + size_t size; + + /* Find an empty slot in the table of active services. */ + for (i = 0; i < szActiveServices; ++i) { + if (activeServices[i] == NULL) + break; + } + + /* Do we need to grow the table? */ + if (i >= szActiveServices) { + ++szActiveServices; + size = szActiveServices * sizeof(*activeServices); + activeServices = realloc(activeServices, size); + if (activeServices == NULL) { + __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service table", + size, PM_FATAL_ERR); + } + } + + /* Add the service to the table. */ + activeServices[i] = s; + ++nActiveServices; +} + +/* Publish a new service. */ +static void +removeService(__pmServerPresence *s) +{ + int i; + + /* + * Find the service in the table of active services. + * We can do this by comparing the pointers directly, since + * that's how the services were added. + */ + for (i = 0; i < szActiveServices; ++i) { + /* + * Did we find it? If so, clear the entry. + * We don't free it here. + */ + if (activeServices[i] == s) { + activeServices[i] = NULL; + --nActiveServices; + return; + } + } +} + +/* Publish a new service. */ +static void +publishService(__pmServerPresence *s) +{ + int error; + + /* Add the service to our list of active services. */ + addService(s); + + /* Is this the first service to be added? */ + if (threadedPoll == NULL) { + /* Allocate main loop object. */ + if ((threadedPoll = avahi_threaded_poll_new()) == NULL) { + __pmNotifyErr(LOG_ERR, "Failed to create avahi threaded poll object."); + goto fail; + } + + /* + * Always allocate a new client. Passing AVAHI_CLIENT_NO_FAIL allows + * the client to be created, even if the avahi daemon is not running. + * Our service will be advertised if/when the daemon is started. + */ + client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), + (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL, + advertisingClientCallback, NULL, &error); + + /* Check whether creating the client object succeeded. */ + if (! client) { + __pmNotifyErr(LOG_ERR, "Failed to create avahi client: %s", + avahi_strerror(error)); + goto fail; + } + + /* Start the main loop. */ + avahi_threaded_poll_start(threadedPoll); + } + else { + /* Stop the main loop while we recreate the services. */ + avahi_threaded_poll_lock(threadedPoll); + createServices(client); + avahi_threaded_poll_unlock(threadedPoll); + } + + return; + + fail: + cleanup(s->avahi); + free(s->avahi); +} + +void +__pmServerAvahiAdvertisePresence(__pmServerPresence *s) +{ + size_t size; + char host[MAXHOSTNAMELEN]; + + /* Allocate the avahi server presence. */ + s->avahi = malloc(sizeof(*s->avahi)); + if (s->avahi == NULL) { + __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate avahi service data", + sizeof(*s->avahi), PM_FATAL_ERR); + } + + /* + * The service spec is simply the name of the server. Use it to + * construct the avahi service name and service tag. + * The service name cannot be longer than AVAHI_LABEL_MAX - 1. + */ + gethostname(host, sizeof(host)); + host[sizeof(host)-1] = '\0'; + + size = sizeof("PCP..on.") + strlen(host) + + strlen(s->serviceSpec); /* includes room for the nul */ + if (size > AVAHI_LABEL_MAX) + size = AVAHI_LABEL_MAX; + if ((s->avahi->serviceName = avahi_malloc(size)) == NULL) { + __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service name", + size, PM_FATAL_ERR); + } + snprintf(s->avahi->serviceName, size, "PCP %s on %s", s->serviceSpec, host); + assert (avahi_is_valid_service_name(s->avahi->serviceName)); + + size = sizeof("_._tcp") + strlen(s->serviceSpec); /* includes room for the nul */ + if ((s->avahi->serviceTag = avahi_malloc(size)) == NULL) { + __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service tag", + size, PM_FATAL_ERR); + } + sprintf(s->avahi->serviceTag, "_%s._tcp", s->serviceSpec); + s->avahi->collisions = 0; + + /* Now publish the avahi service. */ + publishService(s); +} + +void +__pmServerAvahiUnadvertisePresence(__pmServerPresence *s) +{ + /* Not an avahi service? */ + if (s->avahi == NULL) + return; + + if (pmDebug & DBG_TRACE_DISCOVERY) + __pmNotifyErr(LOG_INFO, "Removing Avahi service '%s' on port %d", + s->avahi->serviceName , s->port); + + /* Remove and cleanup the service. */ + removeService(s); + cleanup(s->avahi); + free(s->avahi); + s->avahi = NULL; + + /* Nothing to do if the client is not running. */ + if (threadedPoll == NULL) + return; + + /* Stop the main loop. */ + + /* If no services remain, then shut down the avahi client. */ + if (nActiveServices == 0) { + /* Clean up the avahi objects. The order of freeing these is significant. */ + avahi_threaded_poll_stop(threadedPoll); + cleanupClient(); + avahi_threaded_poll_free(threadedPoll); + threadedPoll = NULL; + return; + } + + /* Otherwise, stop the main loop while we recreate the services. */ + avahi_threaded_poll_lock(threadedPoll); + createServices(client); + avahi_threaded_poll_unlock(threadedPoll); +} + +/* Support for clients searching for services. */ +typedef struct browsingContext { + const __pmServiceDiscoveryOptions *discoveryOptions; + AvahiSimplePoll *simplePoll; + char ***urls; + int numUrls; + int error; +} browsingContext; + +/* Called whenever a service has been resolved successfully or timed out. */ +static void +resolveCallback( + AvahiServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *hostName, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata +) +{ + char addressString[AVAHI_ADDRESS_STR_MAX]; + browsingContext *context = (browsingContext *)userdata; + char ***urls = context->urls; + int numUrls = context->numUrls; + __pmServiceInfo serviceInfo; + + /* Unused arguments. */ + (void)protocol; + (void)hostName; + (void)txt; + (void)flags; + assert(r); + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + context->error = avahi_client_errno(avahi_service_resolver_get_client(r)); + break; + + case AVAHI_RESOLVER_FOUND: + if (strcmp(type, "_" PM_SERVER_SERVICE_SPEC "._tcp") == 0) { + serviceInfo.spec = PM_SERVER_SERVICE_SPEC; + serviceInfo.protocol = SERVER_PROTOCOL; + } + else if (strcmp(type, "_" PM_SERVER_PROXY_SPEC "._tcp") == 0) { + serviceInfo.spec = PM_SERVER_PROXY_SPEC; + serviceInfo.protocol = PROXY_PROTOCOL; + } + else if (strcmp(type, "_" PM_SERVER_WEBD_SPEC "._tcp") == 0) { + serviceInfo.spec = PM_SERVER_WEBD_SPEC; + serviceInfo.protocol = PMWEBD_PROTOCOL; + } + else { + context->error = EINVAL; + break; + } + + avahi_address_snprint(addressString, sizeof(addressString), address); + serviceInfo.address = __pmStringToSockAddr(addressString); + if (serviceInfo.address == NULL) { + context->error = ENOMEM; + break; + } + __pmSockAddrSetPort(serviceInfo.address, port); + __pmSockAddrSetScope(serviceInfo.address, interface); + context->numUrls = __pmAddDiscoveredService(&serviceInfo, + context->discoveryOptions, + numUrls, urls); + __pmSockAddrFree(serviceInfo.address); + break; + + default: + break; + } + + avahi_service_resolver_free(r); +} + +/* + * Called whenever a new service becomes available on the LAN + * or is removed from the LAN. + */ +static void +browseCallback( + AvahiServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata +) +{ + browsingContext *context = (browsingContext *)userdata; + AvahiClient *c = avahi_service_browser_get_client(b); + AvahiSimplePoll *simplePoll = context->simplePoll; + assert(b); + + /* Unused argument. */ + (void)flags; + + switch (event) { + case AVAHI_BROWSER_FAILURE: + context->error = avahi_client_errno(c); + avahi_simple_poll_quit(simplePoll); + break; + + case AVAHI_BROWSER_NEW: + /* + * We ignore the returned resolver object. In the callback + * function we free it. If the server is terminated before + * the callback function is called the server will free + * the resolver for us. + */ + if (!(avahi_service_resolver_new(c, interface, protocol, + name, type, domain, + AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, + resolveCallback, context))) { + context->error = avahi_client_errno(c); + } + break; + + case AVAHI_BROWSER_REMOVE: + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + } +} + +/* Called whenever the client or server state changes. */ +static void +browsingClientCallback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + assert(c); + if (state == AVAHI_CLIENT_FAILURE) { + browsingContext *context = (browsingContext *)userdata; + AvahiSimplePoll *simplePoll = context->simplePoll; + context->error = avahi_client_errno(c); + avahi_simple_poll_quit(simplePoll); + } +} + +static void +timeoutCallback(AvahiTimeout *e, void *userdata) +{ + browsingContext *context = (browsingContext *)userdata; + AvahiSimplePoll *simplePoll = context->simplePoll; + (void)e; + avahi_simple_poll_quit(simplePoll); +} + + +static double +discoveryTimeout(void) +{ + static int done_default = 0; + static double def_timeout = 0.5; /* 0.5 seconds */ + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (!done_default) { + char *timeout_str; + char *end_ptr; + double new_timeout; + if ((timeout_str = getenv("AVAHI_DISCOVERY_TIMEOUT")) != NULL) { + new_timeout = strtod(timeout_str, &end_ptr); + if (*end_ptr != '\0' || def_timeout < 0.0) { + __pmNotifyErr(LOG_WARNING, + "ignored bad AVAHI_DISCOVERY_TIMEOUT = '%s'\n", + timeout_str); + } + else + def_timeout = new_timeout; + } + done_default = 1; + } + PM_UNLOCK(__pmLock_libpcp); + return def_timeout; +} + +int +__pmAvahiDiscoverServices(const char *service, + const char *mechanism, + const __pmServiceDiscoveryOptions *options, + int numUrls, + char ***urls) +{ + AvahiClient *client = NULL; + AvahiServiceBrowser *sb = NULL; + AvahiSimplePoll *simplePoll; + struct timeval tv; + browsingContext context; + char *serviceTag; + size_t size; + const char *timeoutBegin; + char *timeoutEnd; + double timeout; + int sts; + + /* Allocate the main loop object. */ + if (!(simplePoll = avahi_simple_poll_new())) + return -ENOMEM; + + context.discoveryOptions = options; + context.error = 0; + context.simplePoll = simplePoll; + context.urls = urls; + context.numUrls = numUrls; + + /* Allocate a new Avahi client */ + client = avahi_client_new(avahi_simple_poll_get(simplePoll), + (AvahiClientFlags)0, + browsingClientCallback, &context, &context.error); + + /* Check whether creating the client object succeeded. */ + if (! client) + goto done; + + /* Create the service browser. */ + size = sizeof("_._tcp") + strlen(service); /* includes room for the nul */ + if ((serviceTag = malloc(size)) == NULL) { + context.error = ENOMEM; + goto done; + } + sprintf(serviceTag, "_%s._tcp", service); + sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, serviceTag, + NULL, (AvahiLookupFlags)0, + browseCallback, & context); + free(serviceTag); + if (sb == NULL) { + context.error = ENOMEM; + goto done; + } + + /* Extract any ,timeout=NNN parameters. */ + timeout = discoveryTimeout(); /* default */ + + timeoutBegin = strstr(mechanism ? mechanism : "", ",timeout="); + if (timeoutBegin) { + timeoutBegin += strlen(",timeout="); /* skip over it */ + timeout = strtod (timeoutBegin, & timeoutEnd); + if ((*timeoutEnd != '\0' && *timeoutEnd != ',') || (timeout < 0.0)) { + __pmNotifyErr(LOG_WARNING, + "ignored bad avahi timeout = '%*s'\n", + (int)(timeoutEnd-timeoutBegin), timeoutBegin); + timeout = discoveryTimeout(); + } + } + + /* Set the timeout. */ + avahi_simple_poll_get(simplePoll)->timeout_new( + avahi_simple_poll_get(simplePoll), + avahi_elapse_time(&tv, (unsigned)(timeout * 1000), 0), + timeoutCallback, &context); + + /* + * This loop is based on the one in avahi_simple_poll_loop(). + * + * Run the main loop one iteration at a time until it times out + * or until we are interrupted. + * The overall timeout within simplePoll will be respected and + * avahi_simple_poll_iterate() will return 1 if it occurs. + * Otherwise, avahi_simple_poll_iterate() returns -1 on error and + * zero on success. + * The discovered services will be added to 'urls' during the call back + * to resolveCallback + */ + while (! options->timedOut && + (! options->flags || + (*options->flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0)) { + if ((sts = avahi_simple_poll_iterate(simplePoll, -1)) != 0) + if (sts > 0 || errno != EINTR) + break; + } + numUrls = context.numUrls; + + done: + /* Cleanup. */ + if (client) { + /* Also frees the service browser. */ + avahi_client_free(client); + } + if (simplePoll) + avahi_simple_poll_free(simplePoll); + + /* + * Check to see if there was an error. Make sure that the returned error + * code is negative. + */ + if (context.error > 0) + return -context.error; + if (context.error < 0) + return context.error; + + return numUrls; +} diff --git a/src/libpcp/src/avahi.h b/src/libpcp/src/avahi.h new file mode 100644 index 0000000..bc375ff --- /dev/null +++ b/src/libpcp/src/avahi.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * + * 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. + */ +#ifndef AVAHI_H +#define AVAHI_H + +#ifdef HAVE_AVAHI +void __pmServerAvahiAdvertisePresence(__pmServerPresence *) _PCP_HIDDEN; +void __pmServerAvahiUnadvertisePresence(__pmServerPresence *) _PCP_HIDDEN; +int __pmAvahiDiscoverServices(const char *, + const char *, + const __pmServiceDiscoveryOptions *, + int, + char ***) _PCP_HIDDEN; +#else +#define __pmServerAvahiAdvertisePresence(p) do { } while (0) +#define __pmServerAvahiUnadvertisePresence(p) do { } while (0) +#define __pmAvahiDiscoverServices(s, m, o, n, u) 0 +#endif + +#endif /* AVAHI_H */ diff --git a/src/libpcp/src/check-statics b/src/libpcp/src/check-statics new file mode 100755 index 0000000..6a69435 --- /dev/null +++ b/src/libpcp/src/check-statics @@ -0,0 +1,502 @@ +#!/bin/sh +# +# Check symbols for static variables against list of exceptions +# that are known to be thread-safe +# + +set -e # detect syntax errors or subsidiary command failures +sts=1 # presume failure, in case of an unexpected early exit +tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1 +trap "rm -rf $tmp; exit \$sts" 0 1 2 3 15 + +# Note +# Really want to make this run on as many platforms as possible ... +eval `grep PCP_PLATFORM= ../../include/pcp.conf` +case "$PCP_PLATFORM" +in + linux|darwin) + # only works for some architectures ... and in particular not + # Power PC! + # + arch=`uname -m 2>/dev/null` + case "$arch" + in + i?86|x86_64) + ;; + *) + echo "Warning: check-statics skipped for $arch architecture" + sts=0 + exit + ;; + esac + ;; + freebsd|netbsd|solaris) + ;; + *) + echo "Warning: check-statics skipped for PCP_PLATFORM=$PCP_PLATFORM" + sts=0 + exit + ;; +esac + +obj='' +cat <<End-of-File \ +| sed -e 's/[ ]*#.*//' \ + -e '/^$/d' >$tmp/ctl +# Format for the control file ... +# All text after a # is treated as a comment +# +# Lines consisting of a FOO.o name are assumed to be the name of an +# object file ... if any object file is found in the current directory +# that is not named in the control file, this is an error. Object +# file names beginning with '?' are optional, otherwise the object +# file is expected to exist. +# +# Following the name of an object file follows zero or more lines +# defining static data symbols from that object file that is known to +# be thread-safe ... these lines contain the symbol's name and by +# convention an comment explaining why the symbol is thread-safe. The +# symbol may be preceded by a '?' character to indicate the symbol may +# or may not be in the object file, otherwise a symbol named here that +# is not in the object file produces a warning. +# +access.o + all_ops # single-threaded PM_SCOPE_ACL + gotmyhostid # single-threaded PM_SCOPE_ACL + grouplist # single-threaded PM_SCOPE_ACL + hostlist # single-threaded PM_SCOPE_ACL + myhostid # single-threaded PM_SCOPE_ACL + myhostname # single-threaded PM_SCOPE_ACL + nhosts # single-threaded PM_SCOPE_ACL + ngroups # single-threaded PM_SCOPE_ACL + nusers # single-threaded PM_SCOPE_ACL + oldgrouplist # single-threaded PM_SCOPE_ACL + oldhostlist # single-threaded PM_SCOPE_ACL + olduserlist # single-threaded PM_SCOPE_ACL + oldngroups # single-threaded PM_SCOPE_ACL + oldnhosts # single-threaded PM_SCOPE_ACL + oldnusers # single-threaded PM_SCOPE_ACL + oldszgrouplist # single-threaded PM_SCOPE_ACL + oldszhostlist # single-threaded PM_SCOPE_ACL + oldszuserlist # single-threaded PM_SCOPE_ACL + saved # single-threaded PM_SCOPE_ACL + szhostlist # single-threaded PM_SCOPE_ACL + szgrouplist # single-threaded PM_SCOPE_ACL + szuserlist # single-threaded PM_SCOPE_ACL + userlist # single-threaded PM_SCOPE_ACL +accounts.o +AF.o + afid # single-threaded PM_SCOPE_AF + block # single-threaded PM_SCOPE_AF + root # single-threaded PM_SCOPE_AF + ?afblock # guarded by __pmLock_libpcp mutex + ?afsetup # guarded by __pmLock_libpcp mutex + ?aftimer # guarded by __pmLock_libpcp mutex +auxconnect.o + canwait # guarded by __pmLock_libpcp mutex + first_time # guarded by __pmLock_libpcp mutex + pmcd_ports # guarded by __pmLock_libpcp mutex + pmcd_socket # guarded by __pmLock_libpcp mutex +auxserver.o + nport # single-threaded server scope + portlist # single-threaded server scope + nintf # single-threaded server scope + intflist # single-threaded server scope + nReqPorts # single-threaded server scope + szReqPorts # single-threaded server scope + reqPorts # single-threaded server scope + localSocketPath # single-threaded server scope + serviceSpec # single-threaded server scope + localSocketFd # single-threaded server scope + server_features # single-threaded server scope +discovery.o +?avahi.o + nActiveServices # single-threaded server scope + szActiveServices # single-threaded server scope + activeServices # single-threaded server scope + threadedPoll # single-threaded server scope + simplePoll # single-threaded server scope + client # single-threaded server scope + group # single-threaded server scope + done_default # guarded by __pmLock_libpcp mutex + def_timeout # guarded by __pmLock_libpcp mutex +checksum.o +config.o + ?__pmNativeConfig # const + state # guarded by __pmLock_libpcp mutex + ?features # const +connectlocal.o + atexit_installed # guarded by __pmLock_libpcp mutex + buffer # assert safe, see notes in connectlocal.c + dsotab # assert safe, see notes in connectlocal.c + numdso # assert safe, see notes in connectlocal.c +connect.o + global_nports # guarded by __pmLock_libpcp mutex + global_portlist # guarded by __pmLock_libpcp mutex + first_time # guarded by __pmLock_libpcp mutex + proxy # guarded by __pmLock_libpcp mutex +context.o + _mode # const + def_backoff # guarded by __pmLock_libpcp mutex + backoff # guarded by __pmLock_libpcp mutex + n_backoff # guarded by __pmLock_libpcp mutex + contexts # guarded by __pmLock_libpcp mutex + contexts_len # guarded by __pmLock_libpcp mutex + hostbuf # single-threaded + ?curcontext # thread private + ?__emutls_t.curcontext # thread private (MinGW) + ?__emutls_v.curcontext # thread private (MinGW) +derive_fetch.o +derive.o + ?func # const + ?init # local initialize_mutex mutex + ?done # guarded by local initialize_mutex mutex + type_dbg # const + ?type_c # const + state_dbg # const + ?promote # const + ?timefactor # const + need_init # guarded by registered.mutex + tokbuf # guarded by registered.mutex + tokbuflen # guarded by registered.mutex + string # guarded by registered.mutex + lexpeek # guarded by registered.mutex + this # guarded by registered.mutex + ?registered # guarded by registered.mutex + pmid # guarded by registered.mutex + ?derive_errmsg # thread private + ?__emutls_v.derive_errmsg # thread private (MinGW) + ?func # const (MinGW) + ?promote # const (MinGW) + ?timefactor # const (MinGW) + ?type_c # const (MinGW) +desc.o +endian.o +err.o + ?errtab # const + ?first # guarded by __pmLock_libpcp mutex + unknown # guarded by __pmLock_libpcp mutex or const (MinGW) + errmsg # pmErrStr deprecated by pmErrStr_r +events.o + first # guarded by __pmLock_libpcp mutex + name_flags # guarded by __pmLock_libpcp mutex + name_missed # guarded by __pmLock_libpcp mutex + pmid_flags # no unsafe side-effects + pmid_missed # no unsafe side-effects +fault.o +fetchlocal.o + splitlist # single-threaded PM_SCOPE_DSO_PMDA + splitmax # single-threaded PM_SCOPE_DSO_PMDA +fetch.o +freeresult.o +getdate.tab.o + dst_table # const + meridian_table # const + military_table # const + month_and_day_table # const + relative_time_table # const + time_units_table # const + time_zone_table # const + universal_time_zone_table # const + yycheck # const + yydefact # const + yydefgoto # const + yypact # const + yypgoto # const + yyr1 # const + yyr2 # const + ?yystos # const, may be optimized away + ?yyval_default # local to parser ... depends on yacc/bison version + yytable # const + yytranslate # const +getopt.o +hash.o +help.o +instance.o +interp.o + dowrap # guarded by __pmLock_libpcp mutex + nr # diag counters, no atomic updates + nr_cache # diag counters, no atomic updates + statestr # const +ipc.o + __pmIPCTable # guarded by __pmLock_libpcp mutex + __pmLastUsedFd # guarded by __pmLock_libpcp mutex + ipcentrysize # guarded by __pmLock_libpcp mutex + ipctablecount # guarded by __pmLock_libpcp mutex +lock.o + __pmLock_libpcp # the global libpcp mutex + ?init # local __pmInitLocks mutex + ?done # guarded by local __pmInitLocks mutex + ?__pmTPDKey # one-trip initialization then read-only + ?multi_init # guarded by __pmLock_libpcp mutex + ?multi_seen # guarded by __pmLock_libpcp mutex + ?hashctl # for lock debug tracing + ?__pmTPDKey # if don't have __thread support +logconnect.o + done_default # guarded by __pmLock_libpcp mutex + timeout # guarded by __pmLock_libpcp mutex +logcontrol.o +logmeta.o +logportmap.o + nlogports # single-threaded PM_SCOPE_LOGPORT + szlogport # single-threaded PM_SCOPE_LOGPORT + logport # single-threaded PM_SCOPE_LOGPORT + match # single-threaded PM_SCOPE_LOGPORT +logutil.o + tbuf # __pmLogName deprecated by __pmLogName_r + compress_ctl # const + ?ncompress # const + __pmLogReads # diag counter, no atomic updates + pc_hc # guarded by __pmLock_libpcp mutex +secureserver.o + secure_server # guarded by __pmLock_libpcp mutex +secureconnect.o + common_callbacks # const + initialized # single-threaded +optfetch.o + optcost # guarded by __pmLock_libpcp mutex +p_auth.o +p_creds.o +p_desc.o +pdubuf.o + buf_free # guarded by __pmLock_libpcp mutex + buf_pin # guarded by __pmLock_libpcp mutex + buf_pin_tail # guarded by __pmLock_libpcp mutex +pdu.o + done_default # guarded by __pmLock_libpcp mutex + def_timeout # guarded by __pmLock_libpcp mutex + def_wait # guarded by __pmLock_libpcp mutex + pmDebug # set-once in main(), read-only elsewhere + ceiling # no unsafe side-effects + ?sigpipe_done # no unsafe side-effects + mypid # no unsafe side-effects + tbuf # __pmPDUTypeStr deprecated by __pmPDUTypeStr_r + __pmPDUCntIn # pointer to diag counters, no atomic updates + __pmPDUCntOut # pointer to diag counters, no atomic updates + inctrs # diag counters, no atomic updates + outctrs # diag counters, no atomic updates + maxsize # guarded by __pmLock_libpcp mutex +p_error.o +p_profile.o +p_result.o +profile.o +p_text.o +p_fetch.o +p_instance.o +p_lcontrol.o +p_lrequest.o +p_lstatus.o +pmns.o + lineno # guarded by __pmLock_libpcp mutex + export # guarded by __pmLock_libpcp mutex + fin # guarded by __pmLock_libpcp mutex + first # guarded by __pmLock_libpcp mutex + fname # guarded by __pmLock_libpcp mutex + havePmLoadCall # guarded by __pmLock_libpcp mutex + last_mtim # guarded by __pmLock_libpcp mutex + last_pmns_location # guarded by __pmLock_libpcp mutex + linebuf # guarded by __pmLock_libpcp mutex + linep # guarded by __pmLock_libpcp mutex + lp # guarded by __pmLock_libpcp mutex + seen # guarded by __pmLock_libpcp mutex + seenpmid # guarded by __pmLock_libpcp mutex + tokbuf # guarded by __pmLock_libpcp mutex + tokpmid # guarded by __pmLock_libpcp mutex + useExtPMNS # guarded by __pmLock_libpcp mutex + repname # guarded by __pmLock_libpcp mutex + main_pmns # guarded by __pmLock_libpcp mutex + curr_pmns # guarded by __pmLock_libpcp mutex + locerr # no unsafe side-effects, see notes in pmns.c +p_pmns.o +p_profile.o +p_result.o +probe.o +profile.o +p_text.o +rtime.o + ?wdays # const + ?months # const + ?ampm # const + int_tab # const struct {...} int_tab[] = {...} + ?numint # const + ?ampm # const (MinGW) + ?months # const (MinGW) + ?wdays # const (MinGW) + ?startend_relative_terms # const +sortinst.o +spec.o +store.o +stuffvalue.o +tv.o +tz.o + curzone # guarded by __pmLock_libpcp mutex + envtz # guarded by __pmLock_libpcp mutex + envtzlen # guarded by __pmLock_libpcp mutex + zone # guarded by __pmLock_libpcp mutex + nzone # guarded by __pmLock_libpcp mutex + savetz # guarded by __pmLock_libpcp mutex + savetzp # guarded by __pmLock_libpcp mutex + tzbuffer # guarded by __pmLock_libpcp mutex + ?wildabbr # const (MinGW) +units.o + typename # const + abuf # pmAtomStr deprecated by pmAtomStr_r + tbuf # pmTypeStr deprecated by pmTypeStr_r + ubuf # pmUnitsStr deprecated by pmUnitsStr_r +util.o + idbuf # pmIDStr deprecated by pmIDStr_r + indombuf # pmInDomStr deprecated by pmInDomStr_r + ebuf # pmEventFlagsStr deprecated by pmEventFlagsStr_r + nbuf # pmNumberStr deprecated by pmNumberStr_r + ?unknownVal # const, variable may be optimized away by gcc + debug_map # const + ?num_debug # const + pmState # no unsafe side-effects, see notes in util.c + pmProgname # no unsafe side-effects, see notes in util.c + filelog # guarded by __pmLock_libpcp mutex + nfilelog # guarded by __pmLock_libpcp mutex + dosyslog # guarded by __pmLock_libpcp mutex + done_exit # guarded by __pmLock_libpcp mutex + ferr # guarded by __pmLock_libpcp mutex + errtype # guarded by __pmLock_libpcp mutex + fptr # guarded by __pmLock_libpcp mutex + fname # guarded by __pmLock_libpcp mutex + msgsize # guarded by __pmLock_libpcp mutex + ?base # no unsafe side-effects, see notes in util.c + first # __pmEventType deprecated by __pmEventType_r + last # __pmEventType deprecated by __pmEventType_r + sum # __pmEventType deprecated by __pmEventType_r + ?bp # const + ?dp_h # const + ?dp_l # const +?win32.o +END # this is magic, DO NOT DELETE THIS LINE +End-of-File + +for file in *.o +do + case "$file" + in + '*.o') + echo "Error: no object files!! Need some drive-by make action?" + exit 1 + ;; + esac + + if grep "^?*$file\$" $tmp/ctl >/dev/null 2>&1 + then + : + else + echo "$file: Error: object file not mentioned in control file" + touch $tmp/fail + fi +done + +skip_file=false + +cat $tmp/ctl \ +| while read line +do + if expr $line : '.*\.o$' >/dev/null # .o file + then + if [ -n "$obj" ] + then + if [ -s $tmp/out ] + then + # extras from the last object code file + sed <$tmp/out \ + -e 's/^[^ ]* //' \ + -e "s/^\(.\) \(.*\)/$obj: \1 \2 : Error: additional symbol/" + touch $tmp/fail + fi + fi + if [ "$line" != END ] + then + if [ -f $line ] # .o file rather than symbol name + then + # Need some nm special case logic ... + # for darwin + # + const data and text symbols both appear as "S", but + # the latter have .eh appended to the name + # + static arrays and some debug (?) symbols appear as + # "s", but the latter have _.NNN appended, or start + # with LC, or have .eh appended, or start with EH_ + # + older versions insert get_pc_thunk symbols in all + # object files + # for MinGW + # + strip .bss and .data lines + # + strip .rdata and .eh_frame lines + # + external symbols tend to have "C" lines + # for FreeBSD + # + strip r __func__.NNN lines + # + skip_file=false + nm $line \ + | sed -n >$tmp/out \ + -e '/ S ___i686.get_pc_thunk.[bc]x/d' \ + -e '/ [sS] .*\.eh$/d' \ + -e '/ s .*_\.[0-9][0-9]*$/d' \ + -e '/ s LC[0-9][0-9]*$/d' \ + -e '/ s EH_/d' \ + -e '/ b \.bss/d' \ + -e '/ d \.data/d' \ + -e '/ r \.rdata/d' \ + -e '/ r \.eh_frame/d' \ + -e '/ r __PRETTY_FUNCTION__.[0-9][0-9]*$/d' \ + -e '/ r __func__.[0-9][0-9]*$/d' \ + -e '/ r \.LC[0-9][0-9]*$/d' \ + -e '/ C ___pmLogReads/d' \ + -e '/ C ___pmNativeConfig/d' \ + -e '/ C ___pmPDUCntIn/d' \ + -e '/ C ___pmPDUCntOut/d' \ + -e '/ C _pmProgname/d' \ + -e '/ [dDbBCsSrR] /p' + obj=$line + else + case "$line" + in + secure*.o) + echo "$line: Info: security object file skipped, not configured" + skip_file=true + ;; + \?*) + skip_file=true + ;; + *) + echo "$line: Error: object file in control file but not found" + touch $tmp/fail + esac + fi + fi + continue + fi + $skip_file && continue + opt=`echo $line | sed -n -e 's/?.*/?/p'` + name=`echo $line | sed -e 's/?//'` + #debug# echo "obj=$obj type=$line opt=$opt" + # + # We accept the given symbol name with several decorations: + # + # - in any section type (bss data, whatever; as compilers can + # be fickle) + # - with or without a _ prefix + # - with or without a .NNN suffix (coming from function statics + # or optimizations) + # + sed <$tmp/out >$tmp/tmp \ + -e "/ [dDbBCsSrR] $name\$/d" \ + -e "/ [dDbBCsSrR] _$name\$/d" \ + -e "/ [dDbBCsSrR] $name\.[0-9]*\$/d" \ + -e "/ [dDbBCsSrR] _$name\.[0-9]*\$/d" + if cmp -s $tmp/out $tmp/tmp + then + if [ "$opt" != "?" ] + then + echo "$obj: $name: Warning: exceptioned symbol ($line) no longer present" + fi + else + mv $tmp/tmp $tmp/out + fi +done + +[ ! -f $tmp/fail ] && sts=0 # success at last diff --git a/src/libpcp/src/checksum.c b/src/libpcp/src/checksum.c new file mode 100644 index 0000000..d614035 --- /dev/null +++ b/src/libpcp/src/checksum.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* + * __pmCheckSum(FILE *f) - algorithm stolen from sum(1), changed from 16-bit + * to 32-bit + */ + +#include "pmapi.h" +#include "impl.h" + +__int32_t +__pmCheckSum(FILE *f) +{ + __int32_t sum = 0x19700520; + int c; + + while ((c = fgetc(f)) != EOF) { + if (sum & 1) + sum = (sum >> 1) + 0x80000000; + else + sum >>= 1; + sum += c; + } + return sum; +} diff --git a/src/libpcp/src/config.c b/src/libpcp/src/config.c new file mode 100644 index 0000000..34d78b8 --- /dev/null +++ b/src/libpcp/src/config.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2008-2009 Aconex. All Rights Reserved. + * Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +#ifdef IS_MINGW +/* + * Fix up the Windows path separator quirkiness - PCP code deals + * typically with forward-slash separators (i.e. if not passed in + * on the command line, but hard-coded), but only very little now. + * In addition, we also need to cater for native Windows programs + * versus the MinimalSYStem POSIX-alike shell (which translates a + * drive letter into a root filesystem entry for us). Yee-hah! + * NB: Only single drive letters allowed (Wikipedia says so) + */ +char * +dos_native_path(char *path) +{ + char *p = path; + + if (path[0] == '/' && isalpha((int)path[1]) && path[2] == '/') { + p[0] = tolower(p[1]); + p[1] = ':'; + p += 2; + } + for (; *p; p++) + if (*p == '/') *p = '\\'; + return path; +} + +static int +dos_absolute_path(char *path) +{ + return (isalpha((int)path[0]) && path[1] == ':' && path[2] == '\\'); +} + +static char * +msys_native_path(char *path) +{ + char *p = path; + + /* Only single drive letters allowed (Wikipedia says so) */ + if (isalpha((int)path[0]) && path[1] == ':') { + p[1] = tolower(p[0]); + p[0] = '/'; + p += 2; + } + for (; *p; p++) { + if (*p == '\\') *p = '/'; + else *p = tolower(*p); + } + return path; +} + +static char * +dos_rewrite_path(char *var, char *val, int msys) +{ + char *p = (char *)rindex(var, '_'); + + if (p && (strcmp(p, "_PATH") == 0 || strcmp(p, "_DIR") == 0)) { + if (msys) + return msys_native_path(val); + return dos_native_path(val); + } + return NULL; +} + +/* + * For native Win32 console tools, we need to translate the paths + * used in scripts to native paths with PCP_DIR prefix prepended. + * + * For Win32 MSYS shell usage, we need to translate the paths + * used in scripts to paths with PCP_DIR prefix prepended AND + * drive letter path mapping done AND posix-style separators. + * + * Choose which way to go based on our environment (SHELL). + */ +static int posix_style(void) +{ + char *s; + int sts; + PM_LOCK(__pmLock_libpcp); + s = getenv("SHELL"); + sts = (s && strncmp(s, "/bin/", 5) == 0); + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +static void +dos_formatter(char *var, char *prefix, char *val) +{ + char envbuf[MAXPATHLEN]; + int msys = posix_style(); + + if (prefix && dos_rewrite_path(var, val, msys)) { + char *p = msys ? msys_native_path(prefix) : prefix; + snprintf(envbuf, sizeof(envbuf), "%s=%s%s", var, p, val); + } + else { + snprintf(envbuf, sizeof(envbuf), "%s=%s", var, val); + } + PM_LOCK(__pmLock_libpcp); + putenv(strdup(envbuf)); + PM_UNLOCK(__pmLock_libpcp); +} + +INTERN const __pmConfigCallback __pmNativeConfig = dos_formatter; +char *__pmNativePath(char *path) { return dos_native_path(path); } +int __pmPathSeparator() { return posix_style() ? '/' : '\\'; } +int __pmAbsolutePath(char *path) { return posix_style() ? path[0] == '/' : dos_absolute_path(path); } +#else +char *__pmNativePath(char *path) { return path; } +int __pmAbsolutePath(char *path) { return path[0] == '/'; } +int __pmPathSeparator() { return '/'; } + +static void +posix_formatter(char *var, char *prefix, char *val) +{ + /* +40 bytes for max PCP env variable name */ + char envbuf[MAXPATHLEN+40]; + char *vp; + char *vend; + + snprintf(envbuf, sizeof(envbuf), "%s=", var); + vend = &val[strlen(val)-1]; + if (val[0] == *vend && (val[0] == '\'' || val[0] == '"')) { + /* + * have quoted value like "gawk --posix" for $PCP_AWK_PROG ... + * strip quotes + */ + vp = &val[1]; + vend--; + } + else + vp = val; + strncat(envbuf, vp, vend-vp+1); + envbuf[strlen(var)+1+vend-vp+1+1] = '\0'; + + PM_LOCK(__pmLock_libpcp); + putenv(strdup(envbuf)); + PM_UNLOCK(__pmLock_libpcp); + (void)prefix; +} + +INTERN const __pmConfigCallback __pmNativeConfig = posix_formatter; +#endif + +void +__pmConfig(__pmConfigCallback formatter) +{ + /* + * Scan ${PCP_CONF-$PCP_DIR/etc/pcp.conf} and put all PCP config + * variables found therein into the environment. + */ + FILE *fp; + char confpath[32]; + char dir[MAXPATHLEN]; + char var[MAXPATHLEN]; + char *prefix; + char *conf; + char *val; + char *p; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + prefix = getenv("PCP_DIR"); + if ((conf = getenv("PCP_CONF")) == NULL) { + strncpy(confpath, "/etc/pcp.conf", sizeof(confpath)); + if (prefix == NULL) + conf = __pmNativePath(confpath); + else { + snprintf(dir, sizeof(dir), + "%s%s", prefix, __pmNativePath(confpath)); + conf = dir; + } + } + + if (access((const char *)conf, R_OK) < 0 || + (fp = fopen(conf, "r")) == (FILE *)NULL) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("FATAL PCP ERROR: could not open config file \"%s\" : %s\n", + conf, osstrerror_r(errmsg, sizeof(errmsg))); + pmprintf("You may need to set PCP_CONF or PCP_DIR in your environment.\n"); + pmflush(); + PM_UNLOCK(__pmLock_libpcp); + exit(1); + } + + while (fgets(var, sizeof(var), fp) != NULL) { + if (var[0] == '#' || (p = strchr(var, '=')) == NULL) + continue; + *p = '\0'; + val = p+1; + if ((p = strrchr(val, '\n')) != NULL) + *p = '\0'; + if ((p = getenv(var)) != NULL) + val = p; + else + formatter(var, prefix, val); + + if (pmDebug & DBG_TRACE_CONFIG) + fprintf(stderr, "pmGetConfig: (init) %s=%s\n", var, val); + } + fclose(fp); + PM_UNLOCK(__pmLock_libpcp); +} + +char * +pmGetConfig(const char *name) +{ + /* + * state controls one-trip initialization, and recursion guard + * for pathological failures in initialization + */ + static int state = 0; + char *val; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (state == 0) { + state = 1; + PM_UNLOCK(__pmLock_libpcp); + __pmConfig(__pmNativeConfig); + PM_LOCK(__pmLock_libpcp); + state = 2; + } + else if (state == 1) { + /* recursion from error in __pmConfig() ... no value is possible */ + PM_UNLOCK(__pmLock_libpcp); + if (pmDebug & DBG_TRACE_CONFIG) + fprintf(stderr, "pmGetConfig: %s= ... recursion error\n", name); + val = ""; + return val; + } + + if ((val = getenv(name)) == NULL) { + val = ""; + } + + if (pmDebug & DBG_TRACE_CONFIG) + fprintf(stderr, "pmGetConfig: %s=%s\n", name, val); + + PM_UNLOCK(__pmLock_libpcp); + return val; +} + +/* + * Details of runtime features available in the built libpcp + */ + +static const char *enabled(void) { return "true"; } +static const char *disabled(void) { return "false"; } + +#define STRINGIFY(s) #s +#define TO_STRING(s) STRINGIFY(s) +static const char *pmapi_version(void) { return TO_STRING(PMAPI_VERSION); } +static const char *pcp_version(void) { return PCP_VERSION; } +#if defined(HAVE_SECURE_SOCKETS) +#include "nss.h" +#include "nspr.h" +#include "sasl.h" +static const char *nspr_version(void) { return PR_VERSION; } +static const char *nss_version(void) { return NSS_VERSION; } +static const char *sasl_version_string(void) +{ + return TO_STRING(SASL_VERSION_MAJOR.SASL_VERSION_MINOR.SASL_VERSION_STEP); +} +#endif + +static const char * +ipv6_enabled(void) +{ +#if defined(IS_LINUX) + return access("/proc/net/if_inet6", F_OK) == 0 ? enabled() : disabled(); +#else + return enabled(); +#endif +} + +#ifdef PM_MULTI_THREAD +#define MULTI_THREAD_ENABLED enabled +#else +#define MULTI_THREAD_ENABLED disabled +#endif +#ifdef PM_FAULT_INJECTION +#define FAULT_INJECTION_ENABLED enabled +#else +#define FAULT_INJECTION_ENABLED disabled +#endif +#if defined(HAVE_SECURE_SOCKETS) +#define SECURE_SOCKETS_ENABLED enabled +#define AUTHENTICATION_ENABLED enabled +#else +#define SECURE_SOCKETS_ENABLED disabled +#define AUTHENTICATION_ENABLED disabled +#endif +#if defined(HAVE_STRUCT_SOCKADDR_UN) +#define UNIX_DOMAIN_SOCKETS_ENABLED enabled +#else +#define UNIX_DOMAIN_SOCKETS_ENABLED disabled +#endif +#if defined(HAVE_STATIC_PROBES) +#define STATIC_PROBES_ENABLED enabled +#else +#define STATIC_PROBES_ENABLED disabled +#endif +#if defined(HAVE_SERVICE_DISCOVERY) +#define SERVICE_DISCOVERY_ENABLED enabled +#else +#define SERVICE_DISCOVERY_ENABLED disabled +#endif + +typedef const char *(*feature_detector)(void); +static struct { + const char *feature; + feature_detector detector; +} features[] = { + { "pcp_version", pcp_version }, + { "pmapi_version", pmapi_version }, +#if defined(HAVE_SECURE_SOCKETS) + { "nss_version", nss_version }, + { "nspr_version", nspr_version }, + { "sasl_version", sasl_version_string }, +#endif + { "multi_threaded", MULTI_THREAD_ENABLED }, + { "fault_injection", FAULT_INJECTION_ENABLED }, + { "secure_sockets", SECURE_SOCKETS_ENABLED }, /* from pcp-3.7.x */ + { "ipv6", ipv6_enabled }, + { "authentication", AUTHENTICATION_ENABLED }, /* from pcp-3.8.x */ + { "unix_domain_sockets",UNIX_DOMAIN_SOCKETS_ENABLED }, /* from pcp-3.8.2 */ + { "static_probes", STATIC_PROBES_ENABLED }, /* from pcp-3.8.3 */ + { "service_discovery", SERVICE_DISCOVERY_ENABLED }, /* from pcp-3.8.6 */ +}; + +void +__pmAPIConfig(__pmAPIConfigCallback formatter) +{ + int i; + + for (i = 0; i < sizeof(features)/sizeof(features[0]); i++) { + const char *value = features[i].detector(); + if (pmDebug & DBG_TRACE_CONFIG) + fprintf(stderr, "__pmAPIConfig: %s=%s\n", + features[i].feature, value); + formatter(features[i].feature, value); + } +} + +const char * +__pmGetAPIConfig(const char *name) +{ + int i; + + for (i = 0; i < sizeof(features)/sizeof(features[0]); i++) + if (strcasecmp(name, features[i].feature) == 0) + return features[i].detector(); + return NULL; +} diff --git a/src/libpcp/src/connect.c b/src/libpcp/src/connect.c new file mode 100644 index 0000000..2a025dc --- /dev/null +++ b/src/libpcp/src/connect.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * Do not need ctxp->c_pmcd->pc_lock lock around __pmSendCreds() call, + * as the context has not been created, so no-one else could be using + * the context's fd. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* MY_BUFLEN needs to big enough to hold "hostname port" */ +#define MY_BUFLEN (MAXHOSTNAMELEN+10) +#define MY_VERSION "pmproxy-client 1\n" + +static int +negotiate_proxy(int fd, const char *hostname, int port) +{ + char buf[MY_BUFLEN]; + char *bp; + int ok = 0; + + /* + * version negotiation (converse to pmproxy logic) + * __pmSend my client version message + * __pmRecv server version message + * __pmSend hostname and port + */ + + if (__pmSend(fd, MY_VERSION, strlen(MY_VERSION), 0) != strlen(MY_VERSION)) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_WARNING, + "__pmConnectPMCD: send version string to pmproxy failed: %s\n", + pmErrStr_r(-neterror(), errmsg, sizeof(errmsg))); + return PM_ERR_IPC; + } + for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) { + if (__pmRecv(fd, bp, 1, 0) != 1) { + *bp = '\0'; + bp = &buf[MY_BUFLEN]; + break; + } + if (*bp == '\n' || *bp == '\r') { + *bp = '\0'; + break; + } + } + if (bp < &buf[MY_BUFLEN]) { + if (strcmp(buf, "pmproxy-server 1") == 0) + ok = 1; + } + + if (!ok) { + __pmNotifyErr(LOG_WARNING, + "__pmConnectPMCD: bad version string from pmproxy: \"%s\"\n", + buf); + return PM_ERR_IPC; + } + + snprintf(buf, sizeof(buf), "%s %d\n", hostname, port); + if (__pmSend(fd, buf, strlen(buf), 0) != strlen(buf)) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_WARNING, + "__pmConnectPMCD: send hostname+port string to pmproxy failed: %s'\n", + pmErrStr_r(-neterror(), errmsg, sizeof(errmsg))); + return PM_ERR_IPC; + } + + return ok; +} + +/* + * client connects to pmcd handshake + */ +static int +__pmConnectHandshake(int fd, const char *hostname, int ctxflags, __pmHashCtl *attrs) +{ + __pmPDU *pb; + int ok; + int version; + int challenge; + int sts; + int pinpdu; + + /* Expect an error PDU back from PMCD: ACK/NACK for connection */ + pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb); + if (sts == PDU_ERROR) { + /* + * See comments in pmcd ... we actually get an extended error PDU + * from pmcd, of the form + * + * :----------:-----------: + * | status | challenge | + * :----------:-----------: + * + * For a good connection, status is 0, else a PCP error code; + * challenge contains server-side info (e.g. enabled features) + */ + version = __pmDecodeXtendError(pb, &sts, &challenge); + if (version < 0) { + __pmUnpinPDUBuf(pb); + return version; + } + if (sts < 0) { + __pmUnpinPDUBuf(pb); + return sts; + } + + if (version == PDU_VERSION2) { + __pmPDUInfo pduinfo; + __pmVersionCred handshake; + int pduflags = 0; + + pduinfo = __ntohpmPDUInfo(*(__pmPDUInfo *)&challenge); + + if (pduinfo.features & PDU_FLAG_CREDS_REQD) + /* + * This is a mandatory connection feature - pmcd must be + * sent user credential information one way or another - + * i.e. via SASL2 authentication, or AF_UNIX peer creds. + */ + pduflags |= PDU_FLAG_CREDS_REQD; + + if (ctxflags) { + /* + * If an optional connection feature (e.g. encryption) is + * desired, the pmcd that we're talking to must advertise + * support for the feature. And if it did, the client in + * turn must request it be enabled (now, via pduflags). + */ + if (ctxflags & (PM_CTXFLAG_SECURE|PM_CTXFLAG_RELAXED)) { + if (pduinfo.features & PDU_FLAG_SECURE) { + pduflags |= PDU_FLAG_SECURE; + /* + * Determine whether the server can send an ACK for a + * secure connection request. We can still connect + * whether it does or not, but we need to know the + * protocol. + */ + if (pduinfo.features & PDU_FLAG_SECURE_ACK) + pduflags |= PDU_FLAG_SECURE_ACK; + } else if (ctxflags & PM_CTXFLAG_SECURE) { + __pmUnpinPDUBuf(pb); + return -EOPNOTSUPP; + } + } + if (ctxflags & PM_CTXFLAG_COMPRESS) { + if (pduinfo.features & PDU_FLAG_COMPRESS) + pduflags |= PDU_FLAG_COMPRESS; + else { + __pmUnpinPDUBuf(pb); + return -EOPNOTSUPP; + } + } + if (ctxflags & PM_CTXFLAG_AUTH) { + if (pduinfo.features & PDU_FLAG_AUTH) + pduflags |= PDU_FLAG_AUTH; + else { + __pmUnpinPDUBuf(pb); + return -EOPNOTSUPP; + } + } + } + + /* + * Negotiate connection version and features (via creds PDU) + */ + if ((ok = __pmSetVersionIPC(fd, version)) < 0) { + __pmUnpinPDUBuf(pb); + return ok; + } + + memset(&handshake, 0, sizeof(handshake)); + handshake.c_type = CVERSION; + handshake.c_version = PDU_VERSION; + handshake.c_flags = pduflags; + + sts = __pmSendCreds(fd, (int)getpid(), 1, (__pmCred *)&handshake); + + /* + * At this point we know caller wants to set channel options and + * pmcd supports them so go ahead and update the socket now (this + * completes the SSL handshake in encrypting mode, authentication + * via SASL, and/or enabling compression in NSS). + */ + if (sts >= 0 && pduflags) + sts = __pmSecureClientHandshake(fd, pduflags, hostname, attrs); + } + else + sts = PM_ERR_IPC; + } + else if (sts != PM_ERR_TIMEOUT) + sts = PM_ERR_IPC; + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + return sts; +} + +static int global_nports; +static int *global_portlist; + +static void +load_pmcd_ports(void) +{ + if (global_portlist == NULL) { + /* __pmPMCDAddPorts discovers at least one valid port, if it returns. */ + global_nports = __pmPMCDAddPorts(&global_portlist, global_nports); + } +} + +static void +load_proxy_hostspec(pmHostSpec *proxy) +{ + char errmsg[PM_MAXERRMSGLEN]; + char *envstr; + + if ((envstr = getenv("PMPROXY_HOST")) != NULL) { + proxy->name = strdup(envstr); + if (proxy->name == NULL) { + __pmNotifyErr(LOG_WARNING, + "__pmConnectPMCD: cannot save PMPROXY_HOST: %s\n", + pmErrStr_r(-oserror(), errmsg, sizeof(errmsg))); + } + else { + /* + *__pmProxyAddPorts discovers at least one valid port, if it + * returns. + */ + proxy->nports = __pmProxyAddPorts(&proxy->ports, proxy->nports); + } + } +} + +void +__pmConnectGetPorts(pmHostSpec *host) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + load_pmcd_ports(); + if (__pmAddHostPorts(host, global_portlist, global_nports) < 0) { + __pmNotifyErr(LOG_WARNING, + "__pmConnectGetPorts: portlist dup failed, " + "using default PMCD_PORT (%d)\n", SERVER_PORT); + host->ports[0] = SERVER_PORT; + host->nports = 1; + } + PM_UNLOCK(__pmLock_libpcp); +} + +int +__pmConnectPMCD(pmHostSpec *hosts, int nhosts, int ctxflags, __pmHashCtl *attrs) +{ + int sts = -1; + int fd = -1; /* Fd for socket connection to pmcd */ + int *ports; + int nports; + int portIx; + int version = -1; + int proxyport; + pmHostSpec *proxyhost; + + static int first_time = 1; + static pmHostSpec proxy; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (first_time) { + /* + * One-trip check for use of pmproxy(1) in lieu of pmcd(1), + * and to extract the optional environment variables ... + * PMCD_PORT, PMPROXY_HOST and PMPROXY_PORT. + * We also check for the presense of a certificate database + * and load it up if either a user or system (global) DB is + * found. + */ + first_time = 0; + load_pmcd_ports(); + load_proxy_hostspec(&proxy); + } + + if (hosts[0].nports == 0) { + nports = global_nports; + ports = global_portlist; + } + else { + nports = hosts[0].nports; + ports = hosts[0].ports; + } + + if (proxy.name == NULL && nhosts == 1) { + const char *name = (const char *)hosts[0].name; + + /* + * no proxy, connecting directly to pmcd + */ + PM_UNLOCK(__pmLock_libpcp); + + sts = -1; + /* Try connecting via the local unix domain socket, if requested and supported. */ + if (nports == PM_HOST_SPEC_NPORTS_LOCAL || nports == PM_HOST_SPEC_NPORTS_UNIX) { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if ((fd = __pmAuxConnectPMCDUnixSocket(name)) >= 0) { + if ((sts = __pmConnectHandshake(fd, name, ctxflags, attrs)) < 0) { + __pmCloseSocket(fd); + } + else + sts = fd; + portIx = -1; /* no port */ + } +#endif + /* + * If the connection failed, or is not supported, and the protocol was 'local:', + * then try connecting to localhost via the default port(s). + */ + if (sts < 0) { + if (nports == PM_HOST_SPEC_NPORTS_LOCAL) { + name = "localhost"; + nports = global_nports; + ports = global_portlist; + sts = -1; /* keep trying */ + } + else + sts = -2; /* no more connection attempts. */ + } + } + + /* If still not connected, try via the given host name and ports, if requested. */ + if (sts == -1) { + for (portIx = 0; portIx < nports; portIx++) { + if ((fd = __pmAuxConnectPMCDPort(name, ports[portIx])) >= 0) { + if ((sts = __pmConnectHandshake(fd, name, ctxflags, attrs)) < 0) { + __pmCloseSocket(fd); + } + else + /* success */ + break; + } + else + sts = fd; + } + } + + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection port=", + hosts[0].name); + for (portIx = 0; portIx < nports; portIx++) { + if (portIx == 0) fprintf(stderr, "%d", ports[portIx]); + else fprintf(stderr, ",%d", ports[portIx]); + } + fprintf(stderr, " failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + return sts; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + if (portIx >= 0) { + fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection port=%d fd=%d PDU version=%u\n", + hosts[0].name, ports[portIx], fd, __pmVersionIPC(fd)); + } + else { + fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection path=%s fd=%d PDU version=%u\n", + hosts[0].name, name, fd, __pmVersionIPC(fd)); + } + __pmPrintIPC(); + } +#endif + + return fd; + } + + /* + * connecting to pmproxy, and then to pmcd ... not a direct + * connection to pmcd + */ + proxyhost = (nhosts > 1) ? &hosts[1] : &proxy; + proxyport = (proxyhost->nports > 0) ? proxyhost->ports[0] : PROXY_PORT; + + for (portIx = 0; portIx < nports; portIx++) { + fd = __pmAuxConnectPMCDPort(proxyhost->name, proxyport); + if (fd < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectPMCD(%s): proxy to %s port=%d failed: %s \n", + hosts[0].name, proxyhost->name, proxyport, pmErrStr_r(-neterror(), errmsg, sizeof(errmsg))); + } +#endif + PM_UNLOCK(__pmLock_libpcp); + return fd; + } + if ((sts = version = negotiate_proxy(fd, hosts[0].name, ports[portIx])) < 0) + __pmCloseSocket(fd); + else if ((sts = __pmConnectHandshake(fd, proxyhost->name, ctxflags, attrs)) < 0) + __pmCloseSocket(fd); + else + /* success */ + break; + } + + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectPMCD(%s): proxy connection to %s port=", + hosts[0].name, proxyhost->name); + for (portIx = 0; portIx < nports; portIx++) { + if (portIx == 0) fprintf(stderr, "%d", ports[portIx]); + else fprintf(stderr, ",%d", ports[portIx]); + } + fprintf(stderr, " failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "__pmConnectPMCD(%s): proxy connection host=%s port=%d fd=%d version=%d\n", + hosts[0].name, proxyhost->name, ports[portIx], fd, version); + } +#endif + + PM_UNLOCK(__pmLock_libpcp); + return fd; +} diff --git a/src/libpcp/src/connectlocal.c b/src/libpcp/src/connectlocal.c new file mode 100644 index 0000000..24663c7 --- /dev/null +++ b/src/libpcp/src/connectlocal.c @@ -0,0 +1,692 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2010 Ken McDonell. All Rights Reserved. + * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * atexit_installed is protected by the __pmLock_libpcp mutex. + * + * __pmSpecLocalPMDA() uses buffer[], but this routine is only called + * from main() in single-threaded apps like pminfo, pmprobe, pmval + * and pmevent ... so we can ignore any multi-threading issues, + * especially as buffer[] is only used on an error handling code path. + * + * dsotab[] and numdso are obviously of interest via calls to + * __pmLookupDSO(), EndLocalContext(), __pmConnectLocal() or + * __pmLocalPMDA(). + * + * Within libpcp, __pmLookupDSO() is called _only_ for PM_CONTEXT_LOCAL + * and it is not called from outside libpcp. Local contexts are only + * supported for single-threaded applications in the scope + * PM_SCOPE_DSO_PMDA that is enforced in pmNewContext. Multi-threaded + * applications are not supported for local contexts, so we do not need + * additional concurrency control for __pmLookupDSO(). + * + * The same arguments apply to EndLocalContext() and __pmConnectLocal(). + * + * __pmLocalPMDA() is a mixed bag, sharing some of the justification from + * __pmSpecLocalPMDA() and some from __pmConnectLocal(). + * + * Because __pmConnectLocal() is not going to be used in a multi-threaded + * environment, the call to the thread-unsafe dlerror() is OK. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include <ctype.h> +#include <sys/stat.h> + +static __pmDSO *dsotab; +static int numdso = -1; + +static int +build_dsotab(void) +{ + /* + * parse pmcd's config file extracting details from dso lines + * + * very little syntactic checking here ... pmcd(1) does that job + * nicely and even if we get confused, the worst thing that happens + * is we don't include one or more of the DSO PMDAs in dsotab[] + * + * lines for DSO PMDAs generally look like this ... + * Name Domain Type Init Routine Path + * mmv 70 dso mmv_init /var/lib/pcp/pmdas/mmv/pmda_mmv.so + * + */ + char configFileName[MAXPATHLEN]; + FILE *configFile; + char *config; + char *p; + char *q; + struct stat sbuf; + int lineno = 1; + int domain; + char *init; + char *name; + char peekc; + + numdso = 0; + dsotab = NULL; + + strcpy(configFileName, pmGetConfig("PCP_PMCDCONF_PATH")); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "build_dsotab: parsing %s\n", configFileName); + } +#endif + if (stat(configFileName, &sbuf) < 0) { + return -oserror(); + } + configFile = fopen(configFileName, "r"); + if (configFile == NULL) { + return -oserror(); + } + if ((config = malloc(sbuf.st_size+1)) == NULL) { + __pmNoMem("build_dsotbl:", sbuf.st_size+1, PM_RECOV_ERR); + fclose(configFile); + return -oserror(); + } + if (fread(config, 1, sbuf.st_size, configFile) != sbuf.st_size) { + fclose(configFile); + free(config); + return -oserror(); + } + config[sbuf.st_size] = '\0'; + + p = config; + while (*p != '\0') { + /* each time through here we're at the start of a new line */ + if (*p == '#') + goto eatline; + if (strncmp(p, "pmcd", 4) == 0) { + /* + * the pmcd PMDA is an exception ... it makes reference to + * symbols in pmcd, and only makes sense when attached to the + * pmcd process, so we skip this one + */ + goto eatline; + } + /* skip the PMDA's name */ + while (*p != '\0' && *p != '\n' && !isspace((int)*p)) + p++; + while (*p != '\0' && *p != '\n' && isspace((int)*p)) + p++; + /* extract domain number */ + domain = (int)strtol(p, &q, 10); + p = q; + while (*p != '\0' && *p != '\n' && isspace((int)*p)) + p++; + /* only interested if the type is "dso" */ + if (strncmp(p, "dso", 3) != 0) + goto eatline; + p += 3; + while (*p != '\0' && *p != '\n' && isspace((int)*p)) + p++; + /* up to the init routine name */ + init = p; + while (*p != '\0' && *p != '\n' && !isspace((int)*p)) + p++; + *p = '\0'; + p++; + while (*p != '\0' && *p != '\n' && isspace((int)*p)) + p++; + /* up to the dso pathname */ + name = p; + while (*p != '\0' && *p != '\n' && !isspace((int)*p)) + p++; + peekc = *p; + *p = '\0'; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "[%d] domain=%d, name=%s, init=%s\n", lineno, domain, name, init); + } +#endif + /* + * a little bit recursive if we got here via __pmLocalPMDA(), + * but numdso has been set correctly, so this is OK + */ + __pmLocalPMDA(PM_LOCAL_ADD, domain, name, init); + *p = peekc; + +eatline: + while (*p != '\0' && *p != '\n') + p++; + if (*p == '\n') { + lineno++; + p++; + } + } + + fclose(configFile); + free(config); + return 0; +} + +static int +build_dsoattrs(pmdaInterface *dispatch, __pmHashCtl *attrs) +{ + __pmHashNode *node; + char name[32]; + char *namep; + int sts = 0; + +#ifdef HAVE_GETUID + snprintf(name, sizeof(name), "%u", getuid()); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_USERID, namep, attrs); +#endif + +#ifdef HAVE_GETGID + snprintf(name, sizeof(name), "%u", getgid()); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs); +#endif + + snprintf(name, sizeof(name), "%u", getpid()); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_PROCESSID, namep, attrs); + + if (dispatch->version.six.attribute != NULL) { + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { + if ((sts = dispatch->version.six.attribute( + 0, node->key, node->data, + node->data ? strlen(node->data)+1 : 0, + dispatch->version.six.ext)) < 0) + break; + } + } + return sts; +} + +#if defined(HAVE_DLFCN_H) +#include <dlfcn.h> +#endif + +/* + * As of PCP version 2.1, we're no longer searching for DSO's; + * pmcd's config file should have full paths to each of 'em. + */ +const char * +__pmFindPMDA(const char *name) +{ + return (access(name, F_OK) == 0) ? name : NULL; +} + +__pmDSO * +__pmLookupDSO(int domain) +{ + int i; + for (i = 0; i < numdso; i++) { + if (dsotab[i].domain == domain && dsotab[i].handle != NULL) + return &dsotab[i]; + } + return NULL; +} + +static void +EndLocalContext(void) +{ + int i; + __pmDSO *dp; + int ctx = pmWhichContext(); + + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* + * Local context requires single-threaded applications + * ... should not really get here, so do nothing! + */ + return; + + for (i = 0; i < numdso; i++) { + dp = &dsotab[i]; + if (dp->domain != -1 && + dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 && + dp->dispatch.version.four.ext->e_endCallBack != NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "NotifyEndLocalContext: DSO PMDA %s (%d) notified of context %d close\n", + dp->name, dp->domain, ctx); + } +#endif + (*(dp->dispatch.version.four.ext->e_endCallBack))(ctx); + } + } +} + +/* + * Note order is significant here. Also EndLocalContext can be + * called from atexit handler (if previously registered), but if + * caller invokes shutdown beforehand, thats OK; EndLocalContext + * will safely do nothing on the second call. + */ +int +__pmShutdownLocal(void) +{ + /* Call through to any PMDA termination callbacks */ + EndLocalContext(); + + /* dso close and free up local memory allocations */ + return __pmLocalPMDA(PM_LOCAL_CLEAR, 0, NULL, NULL); +} + +int +__pmConnectLocal(__pmHashCtl *attrs) +{ + int i; + __pmDSO *dp; + char pathbuf[MAXPATHLEN]; + const char *path; +#if defined(HAVE_DLOPEN) + unsigned int challenge; + void (*initp)(pmdaInterface *); +#ifdef HAVE_ATEXIT + static int atexit_installed = 0; +#endif +#endif + + if (numdso == -1) { + int sts; + sts = build_dsotab(); + if (sts < 0) return sts; + } + + for (i = 0; i < numdso; i++) { + dp = &dsotab[i]; + if (dp->domain == -1 || dp->handle != NULL) + continue; + /* + * __pmLocalPMDA() means the path to the DSO may be something + * other than relative to $PCP_PMDAS_DIR ... need to try both + * options and also with and without DSO_SUFFIX (so, dll, etc) + */ + snprintf(pathbuf, sizeof(pathbuf), "%s%c%s", + pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name); + if ((path = __pmFindPMDA(pathbuf)) == NULL) { + snprintf(pathbuf, sizeof(pathbuf), "%s%c%s.%s", + pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name, DSO_SUFFIX); + if ((path = __pmFindPMDA(pathbuf)) == NULL) { + if ((path = __pmFindPMDA(dp->name)) == NULL) { + snprintf(pathbuf, sizeof(pathbuf), "%s.%s", dp->name, DSO_SUFFIX); + if ((path = __pmFindPMDA(pathbuf)) == NULL) { + pmprintf("__pmConnectLocal: Warning: cannot find DSO at \"%s\" or \"%s\"\n", + pathbuf, dp->name); + pmflush(); + dp->domain = -1; + dp->handle = NULL; + continue; + } + } + } + } +#if defined(HAVE_DLOPEN) + dp->handle = dlopen(path, RTLD_NOW); + if (dp->handle == NULL) { + pmprintf("__pmConnectLocal: Warning: error attaching DSO " + "\"%s\"\n%s\n\n", path, dlerror()); + pmflush(); + dp->domain = -1; + } +#else /* ! HAVE_DLOPEN */ + dp->handle = NULL; + pmprintf("__pmConnectLocal: Warning: error attaching DSO \"%s\"\n", + path); + pmprintf("No dynamic DSO/DLL support on this platform\n\n"); + pmflush(); + dp->domain = -1; +#endif + + if (dp->handle == NULL) + continue; + +#if defined(HAVE_DLOPEN) + /* + * rest of this only makes sense if the dlopen() worked + */ + if (dp->init == NULL) + initp = NULL; + else + initp = (void (*)(pmdaInterface *))dlsym(dp->handle, dp->init); + if (initp == NULL) { + pmprintf("__pmConnectLocal: Warning: couldn't find init function " + "\"%s\" in DSO \"%s\"\n", dp->init, path); + pmflush(); + dlclose(dp->handle); + dp->domain = -1; + continue; + } + + /* + * Pass in the expected domain id. + * The PMDA initialization routine can (a) ignore it, (b) check it + * is the expected value, or (c) self-adapt. + */ + dp->dispatch.domain = dp->domain; + + /* + * the PMDA interface / PMAPI version discovery as a "challenge" ... + * for pmda_interface it is all the bits being set, + * for pmapi_version it is the complement of the one you are using now + */ + challenge = 0xff; + dp->dispatch.comm.pmda_interface = challenge; + dp->dispatch.comm.pmapi_version = ~PMAPI_VERSION; + dp->dispatch.comm.flags = 0; + dp->dispatch.status = 0; + + (*initp)(&dp->dispatch); + + if (dp->dispatch.status != 0) { + /* initialization failed for some reason */ + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmConnectLocal: Warning: initialization " + "routine \"%s\" failed in DSO \"%s\": %s\n", + dp->init, path, pmErrStr_r(dp->dispatch.status, errmsg, sizeof(errmsg))); + pmflush(); + dlclose(dp->handle); + dp->domain = -1; + } + else { + if (dp->dispatch.comm.pmda_interface < PMDA_INTERFACE_2 || + dp->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) { + pmprintf("__pmConnectLocal: Error: Unknown PMDA interface " + "version %d in \"%s\" DSO\n", + dp->dispatch.comm.pmda_interface, path); + pmflush(); + dlclose(dp->handle); + dp->domain = -1; + } + else if (dp->dispatch.comm.pmapi_version != PMAPI_VERSION_2) { + pmprintf("__pmConnectLocal: Error: Unknown PMAPI version %d " + "in \"%s\" DSO\n", + dp->dispatch.comm.pmapi_version, path); + pmflush(); + dlclose(dp->handle); + dp->domain = -1; + } + else if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_6 && + (dp->dispatch.comm.flags & PDU_FLAG_AUTH) != 0) { + /* Agent wants to know about connection attributes */ + build_dsoattrs(&dp->dispatch, attrs); + } + } +#ifdef HAVE_ATEXIT + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 && + atexit_installed == 0) { + /* install end of local context handler */ + atexit(EndLocalContext); + atexit_installed = 1; + } + PM_UNLOCK(__pmLock_libpcp); +#endif +#endif /* HAVE_DLOPEN */ + } + + return 0; +} + +int +__pmLocalPMDA(int op, int domain, const char *name, const char *init) +{ + int sts = 0; + int i; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "__pmLocalPMDA(op="); + if (op == PM_LOCAL_ADD) fprintf(stderr, "ADD"); + else if (op == PM_LOCAL_DEL) fprintf(stderr, "DEL"); + else if (op == PM_LOCAL_CLEAR) fprintf(stderr, "CLEAR"); + else fprintf(stderr, "%d ???", op); + fprintf(stderr, ", domain=%d, name=%s, init=%s)\n", domain, name, init); + } +#endif + + if (numdso == -1) { + if (op != PM_LOCAL_CLEAR) + if ((sts = build_dsotab()) < 0) + return sts; + } + + switch (op) { + case PM_LOCAL_ADD: + if ((dsotab = (__pmDSO *)realloc(dsotab, (numdso+1)*sizeof(__pmDSO))) == NULL) { + __pmNoMem("__pmLocalPMDA realloc", (numdso+1)*sizeof(__pmDSO), PM_FATAL_ERR); + /*NOTREACHED*/ + } + dsotab[numdso].domain = domain; + if (name == NULL) { + /* odd, will fail later at dlopen */ + dsotab[numdso].name = NULL; + } + else { + if ((dsotab[numdso].name = strdup(name)) == NULL) { + sts = -oserror(); + __pmNoMem("__pmLocalPMDA name", strlen(name)+1, PM_RECOV_ERR); + return sts; + } + } + if (init == NULL) { + /* odd, will fail later at initialization call */ + dsotab[numdso].init = NULL; + } + else { + if ((dsotab[numdso].init = strdup(init)) == NULL) { + sts = -oserror(); + __pmNoMem("__pmLocalPMDA init", strlen(init)+1, PM_RECOV_ERR); + return sts; + } + } + dsotab[numdso].handle = NULL; + numdso++; + break; + + case PM_LOCAL_DEL: + sts = PM_ERR_INDOM; + for (i = 0; i < numdso; i++) { + if ((domain != -1 && dsotab[i].domain == domain) || + (name != NULL && strcmp(dsotab[i].name, name) == 0)) { + if (dsotab[i].handle) { + dlclose(dsotab[i].handle); + dsotab[i].handle = NULL; + } + dsotab[i].domain = -1; + sts = 0; + } + } + break; + + case PM_LOCAL_CLEAR: + for (i = 0; i < numdso; i++) { + free(dsotab[i].name); + free(dsotab[i].init); + if (dsotab[i].handle) + dlclose(dsotab[i].handle); + } + free(dsotab); + dsotab = NULL; + numdso = 0; + break; + + default: + sts = PM_ERR_CONV; + break; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + if (sts != 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmLocalPMDA -> %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + fprintf(stderr, "Local Context PMDA Table"); + if (numdso == 0) + fprintf(stderr, " ... empty"); + fputc('\n', stderr); + for (i = 0; i < numdso; i++) { + fprintf(stderr, PRINTF_P_PFX "%p [%d] domain=%d name=%s init=%s handle=" PRINTF_P_PFX "%p\n", + &dsotab[i], i, dsotab[i].domain, dsotab[i].name, dsotab[i].init, dsotab[i].handle); + } + } +#endif + + return sts; +} + +/* + * Parse a command line string that encodes arguments to __pmLocalPMDA(), + * then call __pmLocalPMDA(). + * + * The syntax for the string is 1 to 4 fields separated by colons: + * - op ("add" for add, "del" for delete, "clear" for clear) + * - domain (PMDA's PMD) + * - path (path to DSO PMDA) + * - init (name of DSO's initialization routine) + */ +char * +__pmSpecLocalPMDA(const char *spec) +{ + int op; + int domain = -1; + char *name = NULL; + char *init = NULL; + int sts; + char *arg; + char *sbuf; + char *ap; + + if ((arg = sbuf = strdup(spec)) == NULL) { + sts = -oserror(); + __pmNoMem("__pmSpecLocalPMDA dup spec", strlen(spec)+1, PM_RECOV_ERR); + return "strdup failed"; + } + if (strncmp(arg, "add", 3) == 0) { + op = PM_LOCAL_ADD; + ap = &arg[3]; + } + else if (strncmp(arg, "del", 3) == 0) { + op = PM_LOCAL_DEL; + ap = &arg[3]; + } + else if (strncmp(arg, "clear", 5) == 0) { + op = PM_LOCAL_CLEAR; + ap = &arg[5]; + if (*ap == '\0') + goto doit; + else { + free(sbuf); + return "unexpected text after clear op in spec"; + } + } + else { + free(sbuf); + return "bad op in spec"; + } + + if (*ap != ',') { + free(sbuf); + return "expected , after op in spec"; + } + /* ap-> , after add or del */ + arg = ++ap; + if (*ap == '\0') { + free(sbuf); + return "missing domain in spec"; + } + else if (*ap != ',') { + /* ap-> domain */ + domain = (int)strtol(arg, &ap, 10); + if ((*ap != ',' && *ap != '\0') || domain < 0 || domain > 510) { + free(sbuf); + return "bad domain in spec"; + } + if (*ap != '\0') + /* skip , after domain */ + ap++; + } + else { + if (op != PM_LOCAL_DEL) { + /* found ,, where ,domain, expected */ + free(sbuf); + return "missing domain in spec"; + } + ap++; + } + /* ap -> char after , following domain */ + if (*ap == ',') { + /* no path, could have init (not useful but possible!) */ + ap++; + if (*ap != '\0') + init = ap; + } + else if (*ap != '\0') { + /* have path and possibly init */ + name = ap; + while (*ap != ',' && *ap != '\0') + ap++; + if (*ap == ',') { + *ap++ = '\0'; + if (*ap != '\0') + init = ap; + else { + if (op != PM_LOCAL_DEL) { + /* found end of string where init-routine expected */ + free(sbuf); + return "missing init-routine in spec"; + } + } + } + else { + if (op != PM_LOCAL_DEL) { + /* found end of string where init-routine expected */ + free(sbuf); + return "missing init-routine in spec"; + } + } + } + else { + if (op != PM_LOCAL_DEL) { + /* found end of string where path expected */ + free(sbuf); + return "missing dso-path in spec"; + } + } + + if (domain == -1 && name == NULL) { + free(sbuf); + return "missing domain and dso-path in spec"; + } + +doit: + sts = __pmLocalPMDA(op, domain, name, init); + if (sts < 0) { + /* see thread-safe note at the head of this file */ + static char buffer[256]; + char errmsg[PM_MAXERRMSGLEN]; + snprintf(buffer, sizeof(buffer), "__pmLocalPMDA: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + free(sbuf); + return buffer; + } + + free(sbuf); + return NULL; +} diff --git a/src/libpcp/src/context.c b/src/libpcp/src/context.c new file mode 100644 index 0000000..3f47d8e --- /dev/null +++ b/src/libpcp/src/context.c @@ -0,0 +1,1038 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2007-2008 Aconex. All Rights Reserved. + * Copyright (c) 1995-2002,2004,2006,2008 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * curcontext needs to be thread-private + * + * contexts[] et al and def_backoff[] et al are protected from changes + * using the libpcp lock + * + * The actual contexts (__pmContext) are protected by the (recursive) + * c_lock mutex which is intialized in pmNewContext() and pmDupContext(), + * then locked in __pmHandleToPtr() ... it is the responsibility of all + * __pmHandleToPtr() callers to call PM_UNLOCK(ctxp->c_lock) when they + * are finished with the context. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include <string.h> + +static __pmContext **contexts; /* array of context ptrs */ +static int contexts_len; /* number of contexts */ + +#ifdef PM_MULTI_THREAD +#ifdef HAVE___THREAD +/* using a gcc construct here to make curcontext thread-private */ +static __thread int curcontext = PM_CONTEXT_UNDEF; /* current context */ +#endif +#else +static int curcontext = PM_CONTEXT_UNDEF; /* current context */ +#endif + +static int n_backoff; +static int def_backoff[] = {5, 10, 20, 40, 80}; +static int *backoff; + +static void +waitawhile(__pmPMCDCtl *ctl) +{ + /* + * after failure, compute delay before trying again ... + */ + PM_LOCK(__pmLock_libpcp); + if (n_backoff == 0) { + char *q; + /* first time ... try for PMCD_RECONNECT_TIMEOUT from env */ + if ((q = getenv("PMCD_RECONNECT_TIMEOUT")) != NULL) { + char *pend; + char *p; + int val; + + for (p = q; *p != '\0'; ) { + val = (int)strtol(p, &pend, 10); + if (val <= 0 || (*pend != ',' && *pend != '\0')) { + __pmNotifyErr(LOG_WARNING, + "pmReconnectContext: ignored bad PMCD_RECONNECT_TIMEOUT = '%s'\n", + q); + n_backoff = 0; + if (backoff != NULL) + free(backoff); + break; + } + if ((backoff = (int *)realloc(backoff, (n_backoff+1) * sizeof(backoff[0]))) == NULL) { + __pmNoMem("pmReconnectContext", (n_backoff+1) * sizeof(backoff[0]), PM_FATAL_ERR); + } + backoff[n_backoff++] = val; + if (*pend == '\0') + break; + p = &pend[1]; + } + } + if (n_backoff == 0) { + /* use default */ + n_backoff = 5; + backoff = def_backoff; + } + } + PM_UNLOCK(__pmLock_libpcp); + if (ctl->pc_timeout == 0) + ctl->pc_timeout = 1; + else if (ctl->pc_timeout < n_backoff) + ctl->pc_timeout++; + ctl->pc_again = time(NULL) + backoff[ctl->pc_timeout-1]; +} + +/* + * On success, context is locked and caller should unlock it + */ +__pmContext * +__pmHandleToPtr(int handle) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (handle < 0 || handle >= contexts_len || + contexts[handle]->c_type == PM_CONTEXT_FREE) { + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + else { + __pmContext *sts; + sts = contexts[handle]; + PM_UNLOCK(__pmLock_libpcp); + PM_LOCK(sts->c_lock); + return sts; + } +} + +int +__pmPtrToHandle(__pmContext *ctxp) +{ + int i; + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + for (i = 0; i < contexts_len; i++) { + if (ctxp == contexts[i]) { + PM_UNLOCK(__pmLock_libpcp); + return i; + } + } + PM_UNLOCK(__pmLock_libpcp); + return PM_CONTEXT_UNDEF; +} + +/* + * Determine the hostname associated with the given context. + */ +char * +pmGetContextHostName_r(int ctxid, char *buf, int buflen) +{ + __pmContext *ctxp; + char *name; + pmID pmid; + pmResult *resp; + int original; + int sts; + + buf[0] = '\0'; + + if ((ctxp = __pmHandleToPtr(ctxid)) != NULL) { + switch (ctxp->c_type) { + case PM_CONTEXT_HOST: + /* + * Try and establish the hostname from PMCD (possibly remote). + * Do not nest the successive actions. That way, if any one of + * them fails, we take the default. + * Note: we must *temporarily* switch context (see pmUseContext) + * in the host case, then switch back afterward. We already hold + * locks and have validated the context pointer, so we do a mini + * context switch, then switch back. + */ + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmGetContextHostName_r context(%d) -> 0\n", ctxid); + original = PM_TPD(curcontext); + PM_TPD(curcontext) = ctxid; + + name = "pmcd.hostname"; + sts = pmLookupName(1, &name, &pmid); + if (sts >= 0) + sts = pmFetch(1, &pmid, &resp); + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmGetContextHostName_r reset(%d) -> 0\n", original); + + PM_TPD(curcontext) = original; + if (sts >= 0) { + if (resp->vset[0]->numval > 0) { /* pmcd.hostname present */ + strncpy(buf, resp->vset[0]->vlist[0].value.pval->vbuf, buflen); + pmFreeResult(resp); + break; + } + pmFreeResult(resp); + /* FALLTHROUGH */ + } + + /* + * We could not get the hostname from PMCD. If the name in the + * context structure is a filesystem path (AF_UNIX address) or + * 'localhost', then use gethostname(). Otherwise, use the name + * from the context structure. + */ + name = ctxp->c_pmcd->pc_hosts[0].name; + if (!name || name[0] == __pmPathSeparator() || /* AF_UNIX */ + (strncmp(name, "localhost", 9) == 0)) /* localhost[46] */ + gethostname(buf, buflen); + else + strncpy(buf, name, buflen-1); + break; + + case PM_CONTEXT_LOCAL: + gethostname(buf, buflen); + break; + + case PM_CONTEXT_ARCHIVE: + strncpy(buf, ctxp->c_archctl->ac_log->l_label.ill_hostname, buflen-1); + break; + } + + buf[buflen-1] = '\0'; + PM_UNLOCK(ctxp->c_lock); + } + + return buf; +} + +/* + * Backward-compatibility interface, non-thread-safe variant. + */ +const char * +pmGetContextHostName(int ctxid) +{ + static char hostbuf[MAXHOSTNAMELEN]; + return (const char *)pmGetContextHostName_r(ctxid, hostbuf, (int)sizeof(hostbuf)); +} + +int +pmWhichContext(void) +{ + /* + * return curcontext, provided it is defined + */ + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (PM_TPD(curcontext) > PM_CONTEXT_UNDEF) + sts = PM_TPD(curcontext); + else + sts = PM_ERR_NOCONTEXT; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmWhichContext() -> %d, cur=%d\n", + sts, PM_TPD(curcontext)); +#endif + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmConvertTimeout(int timeo) +{ + int tout_msec; + const struct timeval *tv; + + switch (timeo) { + case TIMEOUT_NEVER: + tout_msec = -1; + break; + + case TIMEOUT_DEFAULT: + tv = __pmDefaultRequestTimeout(); + tout_msec = tv->tv_sec *1000 + tv->tv_usec / 1000; + break; + + case TIMEOUT_CONNECT: + tv = __pmConnectTimeout(); + tout_msec = tv->tv_sec *1000 + tv->tv_usec / 1000; + break; + + default: + tout_msec = timeo * 1000; + break; + } + + return tout_msec; +} + +#ifdef PM_MULTI_THREAD +static void +__pmInitContextLock(pthread_mutex_t *lock) +{ + pthread_mutexattr_t attr; + int sts; + char errmsg[PM_MAXERRMSGLEN]; + + /* + * Need context lock to be recursive as we sometimes call + * __pmHandleToPtr() while the current context is already + * locked + */ + if ((sts = pthread_mutexattr_init(&attr)) != 0) { + pmErrStr_r(-sts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmNewContext: " + "context=%d lock pthread_mutexattr_init failed: %s", + contexts_len-1, errmsg); + exit(4); + } + if ((sts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) { + pmErrStr_r(-sts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmNewContext: " + "context=%d lock pthread_mutexattr_settype failed: %s", + contexts_len-1, errmsg); + exit(4); + } + if ((sts = pthread_mutex_init(lock, &attr)) != 0) { + pmErrStr_r(-sts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmNewContext: " + "context=%d lock pthread_mutex_init failed: %s", + contexts_len-1, errmsg); + exit(4); + } +} + +static void +__pmInitChannelLock(pthread_mutex_t *lock) +{ + int sts; + char errmsg[PM_MAXERRMSGLEN]; + + if ((sts = pthread_mutex_init(lock, NULL)) != 0) { + pmErrStr_r(-sts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmNewContext: " + "context=%d pmcd channel lock pthread_mutex_init failed: %s", + contexts_len, errmsg); + exit(4); + } +} +#else +#define __pmInitContextLock(x) do { } while (1) +#define __pmInitChannelLock(x) do { } while (1) +#endif + +static int +ctxflags(__pmHashCtl *attrs) +{ + int flags = 0; + char *secure = NULL; + __pmHashNode *node; + + if ((node = __pmHashSearch(PCP_ATTR_PROTOCOL, attrs)) != NULL) { + if (strcmp((char *)node->data, "pcps") == 0) { + if ((node = __pmHashSearch(PCP_ATTR_SECURE, attrs)) != NULL) + secure = (char *)node->data; + else + secure = "enforce"; + } + } + + if (!secure) + secure = getenv("PCP_SECURE_SOCKETS"); + + if (secure) { + if (secure[0] == '\0' || + (strcmp(secure, "1")) == 0 || + (strcmp(secure, "enforce")) == 0) { + flags |= PM_CTXFLAG_SECURE; + } else if (strcmp(secure, "relaxed") == 0) { + flags |= PM_CTXFLAG_RELAXED; + } + } + + if (__pmHashSearch(PCP_ATTR_COMPRESS, attrs) != NULL) + flags |= PM_CTXFLAG_COMPRESS; + + if (__pmHashSearch(PCP_ATTR_USERAUTH, attrs) != NULL || + __pmHashSearch(PCP_ATTR_USERNAME, attrs) != NULL || + __pmHashSearch(PCP_ATTR_PASSWORD, attrs) != NULL || + __pmHashSearch(PCP_ATTR_METHOD, attrs) != NULL || + __pmHashSearch(PCP_ATTR_REALM, attrs) != NULL) + flags |= PM_CTXFLAG_AUTH; + + return flags; +} + +int +pmNewContext(int type, const char *name) +{ + __pmContext *new = NULL; + __pmContext **list; + int i; + int sts; + int old_curcontext; + int old_contexts_len; + + PM_INIT_LOCKS(); + + if (PM_CONTEXT_LOCAL == (type & PM_CONTEXT_TYPEMASK) && + PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + return PM_ERR_THREAD; + + PM_LOCK(__pmLock_libpcp); + + old_curcontext = PM_TPD(curcontext); + old_contexts_len = contexts_len; + + /* See if we can reuse a free context */ + for (i = 0; i < contexts_len; i++) { + if (contexts[i]->c_type == PM_CONTEXT_FREE) { + PM_TPD(curcontext) = i; + new = contexts[i]; + goto INIT_CONTEXT; + } + } + + /* Create a new one */ + if (contexts == NULL) + list = (__pmContext **)malloc(sizeof(__pmContext *)); + else + list = (__pmContext **)realloc((void *)contexts, (1+contexts_len) * sizeof(__pmContext *)); + new = (__pmContext *)malloc(sizeof(__pmContext)); + if (list == NULL || new == NULL) { + /* fail : nothing changed, but new may have been allocated (in theory) */ + if (new) + memset(new, 0, sizeof(__pmContext)); + sts = -oserror(); + goto FAILED; + } + + contexts = list; + PM_TPD(curcontext) = contexts_len; + contexts[contexts_len] = new; + contexts_len++; + +INIT_CONTEXT: + /* + * Set up the default state + */ + memset(new, 0, sizeof(__pmContext)); + __pmInitContextLock(&new->c_lock); + new->c_type = (type & PM_CONTEXT_TYPEMASK); + new->c_flags = (type & ~PM_CONTEXT_TYPEMASK); + if ((new->c_instprof = (__pmProfile *)calloc(1, sizeof(__pmProfile))) == NULL) { + /* + * fail : nothing changed -- actually list is changed, but restoring + * contexts_len should make it ok next time through + */ + sts = -oserror(); + goto FAILED; + } + new->c_instprof->state = PM_PROFILE_INCLUDE; /* default global state */ + + if (new->c_type == PM_CONTEXT_HOST) { + __pmHashCtl *attrs = &new->c_attrs; + pmHostSpec *hosts; + int nhosts; + char *errmsg; + + /* break down a host[:port@proxy:port][?attributes] specification */ + __pmHashInit(attrs); + sts = __pmParseHostAttrsSpec(name, &hosts, &nhosts, attrs, &errmsg); + if (sts < 0) { + pmprintf("pmNewContext: bad host specification\n%s", errmsg); + pmflush(); + free(errmsg); + goto FAILED; + } else if (nhosts == 0) { + sts = PM_ERR_NOTHOST; + goto FAILED; + } else { + new->c_flags |= ctxflags(attrs); + } + + /* As an optimization, if there is already a connection to the same PMCD, + we try to reuse (share) it. */ + if (nhosts == 1) { /* not proxied */ + for (i = 0; i < contexts_len; i++) { + if (i == PM_TPD(curcontext)) + continue; + if (contexts[i]->c_type == new->c_type && + contexts[i]->c_flags == new->c_flags && + strcmp(contexts[i]->c_pmcd->pc_hosts[0].name, hosts[0].name) == 0 && + contexts[i]->c_pmcd->pc_hosts[0].nports == hosts[0].nports) { + int j; + int ports_same = 1; + for (j=0; j<hosts[0].nports; j++) + if (contexts[i]->c_pmcd->pc_hosts[0].ports[j] != hosts[0].ports[j]) + ports_same = 0; + if (ports_same) + new->c_pmcd = contexts[i]->c_pmcd; + } + } + } + if (new->c_pmcd == NULL) { + /* + * Try to establish the connection. + * If this fails, restore the original current context + * and return an error. + */ + sts = __pmConnectPMCD(hosts, nhosts, new->c_flags, &new->c_attrs); + if (sts < 0) { + __pmFreeHostAttrsSpec(hosts, nhosts, attrs); + __pmHashClear(attrs); + goto FAILED; + } + + new->c_pmcd = (__pmPMCDCtl *)calloc(1,sizeof(__pmPMCDCtl)); + if (new->c_pmcd == NULL) { + sts = -oserror(); + __pmCloseSocket(sts); + __pmFreeHostAttrsSpec(hosts, nhosts, attrs); + __pmHashClear(attrs); + goto FAILED; + } + new->c_pmcd->pc_fd = sts; + new->c_pmcd->pc_hosts = hosts; + new->c_pmcd->pc_nhosts = nhosts; + new->c_pmcd->pc_tout_sec = __pmConvertTimeout(TIMEOUT_DEFAULT) / 1000; + __pmInitChannelLock(&new->c_pmcd->pc_lock); + } + else { + /* duplicate of an existing context, don't need the __pmHostSpec */ + __pmFreeHostAttrsSpec(hosts, nhosts, attrs); + __pmHashClear(attrs); + } + new->c_pmcd->pc_refcnt++; + } + else if (new->c_type == PM_CONTEXT_LOCAL) { + if ((sts = __pmConnectLocal(&new->c_attrs)) != 0) + goto FAILED; + } + else if (new->c_type == PM_CONTEXT_ARCHIVE) { + if ((new->c_archctl = (__pmArchCtl *)malloc(sizeof(__pmArchCtl))) == NULL) { + sts = -oserror(); + goto FAILED; + } + new->c_archctl->ac_log = NULL; + for (i = 0; i < contexts_len; i++) { + if (i == PM_TPD(curcontext)) + continue; + if (contexts[i]->c_type == PM_CONTEXT_ARCHIVE && + strcmp(name, contexts[i]->c_archctl->ac_log->l_name) == 0) { + new->c_archctl->ac_log = contexts[i]->c_archctl->ac_log; + } + } + if (new->c_archctl->ac_log == NULL) { + if ((new->c_archctl->ac_log = (__pmLogCtl *)malloc(sizeof(__pmLogCtl))) == NULL) { + free(new->c_archctl); + sts = -oserror(); + goto FAILED; + } + if ((sts = __pmLogOpen(name, new)) < 0) { + free(new->c_archctl->ac_log); + free(new->c_archctl); + goto FAILED; + } + } + else { + /* archive already open, set default starting state as per __pmLogOpen */ + new->c_origin.tv_sec = (__int32_t)new->c_archctl->ac_log->l_label.ill_start.tv_sec; + new->c_origin.tv_usec = (__int32_t)new->c_archctl->ac_log->l_label.ill_start.tv_usec; + new->c_mode = (new->c_mode & 0xffff0000) | PM_MODE_FORW; + } + + /* start after header + label record + trailer */ + new->c_archctl->ac_offset = sizeof(__pmLogLabel) + 2*sizeof(int); + new->c_archctl->ac_vol = new->c_archctl->ac_log->l_curvol; + new->c_archctl->ac_serial = 0; /* not serial access, yet */ + new->c_archctl->ac_pmid_hc.nodes = 0; /* empty hash list */ + new->c_archctl->ac_pmid_hc.hsize = 0; + new->c_archctl->ac_end = 0.0; + new->c_archctl->ac_want = NULL; + new->c_archctl->ac_unbound = NULL; + new->c_archctl->ac_cache = NULL; + new->c_archctl->ac_log->l_refcnt++; + } + else { + /* bad type */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "pmNewContext(%d, %s): illegal type\n", + type, name); + } +#endif + PM_UNLOCK(__pmLock_libpcp); + return PM_ERR_NOCONTEXT; + } + + /* bind defined metrics if any ... */ + __dmopencontext(new); + + /* return the handle to the new (current) context */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "pmNewContext(%d, %s) -> %d\n", type, name, PM_TPD(curcontext)); + __pmDumpContext(stderr, PM_TPD(curcontext), PM_INDOM_NULL); + } +#endif + sts = PM_TPD(curcontext); + + PM_UNLOCK(__pmLock_libpcp); + return sts; + +FAILED: + if (new != NULL) { + if (new->c_instprof != NULL) + free(new->c_instprof); + /* only free this pointer if it was not reclaimed from old contexts */ + for (i = 0; i < old_contexts_len; i++) { + if (contexts[i] != new) + continue; + new->c_type = PM_CONTEXT_FREE; + break; + } + if (i == old_contexts_len) + free(new); + } + PM_TPD(curcontext) = old_curcontext; + contexts_len = old_contexts_len; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmNewContext(%d, %s) -> %d, curcontext=%d\n", + type, name, sts, PM_TPD(curcontext)); +#endif + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +pmReconnectContext(int handle) +{ + __pmContext *ctxp; + __pmPMCDCtl *ctl; + int i, sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (handle < 0 || handle >= contexts_len || + contexts[handle]->c_type == PM_CONTEXT_FREE) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmReconnectContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT); +#endif + PM_UNLOCK(__pmLock_libpcp); + return PM_ERR_NOCONTEXT; + } + + ctxp = contexts[handle]; + ctl = ctxp->c_pmcd; + if (ctxp->c_type == PM_CONTEXT_HOST) { + if (ctl->pc_timeout && time(NULL) < ctl->pc_again) { + /* too soon to try again */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmReconnectContext(%d) -> %d, too soon (need wait another %d secs)\n", + handle, (int)-ETIMEDOUT, (int)(ctl->pc_again - time(NULL))); +#endif + PM_UNLOCK(__pmLock_libpcp); + return -ETIMEDOUT; + } + + if (ctl->pc_fd >= 0) { + /* don't care if this fails */ + __pmCloseSocket(ctl->pc_fd); + ctl->pc_fd = -1; + } + + if ((sts = __pmConnectPMCD(ctl->pc_hosts, ctl->pc_nhosts, + ctxp->c_flags, &ctxp->c_attrs)) < 0) { + waitawhile(ctl); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmReconnectContext(%d), failed (wait %d secs before next attempt)\n", + handle, (int)(ctl->pc_again - time(NULL))); +#endif + PM_UNLOCK(__pmLock_libpcp); + return -ETIMEDOUT; + } + else { + ctl->pc_fd = sts; + ctl->pc_timeout = 0; + ctxp->c_sent = 0; + + /* mark profile as not sent for all contexts sharing this socket */ + for (i = 0; i < contexts_len; i++) { + if (contexts[i]->c_type != PM_CONTEXT_FREE && contexts[i]->c_pmcd == ctl) { + contexts[i]->c_sent = 0; + } + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmReconnectContext(%d), done\n", handle); +#endif + } + } + + /* clear any derived metrics and re-bind */ + __dmclosecontext(ctxp); + __dmopencontext(ctxp); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmReconnectContext(%d) -> %d\n", handle, handle); +#endif + + PM_UNLOCK(__pmLock_libpcp); + return handle; +} + +int +pmDupContext(void) +{ + int sts, oldtype; + int old, new = -1; + char hostspec[4096]; + __pmContext *newcon, *oldcon; + __pmInDomProfile *q, *p, *p_end; + __pmProfile *save; + void *save_dm; +#ifdef PM_MULTI_THREAD + pthread_mutex_t save_lock; +#endif + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((old = pmWhichContext()) < 0) { + sts = old; + goto done; + } + oldcon = contexts[old]; + oldtype = oldcon->c_type | oldcon->c_flags; + if (oldcon->c_type == PM_CONTEXT_HOST) { + __pmUnparseHostSpec(oldcon->c_pmcd->pc_hosts, + oldcon->c_pmcd->pc_nhosts, hostspec, sizeof(hostspec)); + new = pmNewContext(oldtype, hostspec); + } + else if (oldcon->c_type == PM_CONTEXT_LOCAL) + new = pmNewContext(oldtype, NULL); + else + /* assume PM_CONTEXT_ARCHIVE */ + new = pmNewContext(oldtype, oldcon->c_archctl->ac_log->l_name); + if (new < 0) { + /* failed to connect or out of memory */ + sts = new; + goto done; + } + oldcon = contexts[old]; /* contexts[] may have been relocated */ + newcon = contexts[new]; + save = newcon->c_instprof; /* need this later */ + save_dm = newcon->c_dm; /* need this later */ +#ifdef PM_MULTI_THREAD + save_lock = newcon->c_lock; /* need this later */ +#endif + if (newcon->c_archctl != NULL) + free(newcon->c_archctl); /* will allocate a new one below */ + *newcon = *oldcon; /* struct copy */ + newcon->c_instprof = save; /* restore saved instprof from pmNewContext */ + newcon->c_dm = save_dm; /* restore saved derived metrics control also */ +#ifdef PM_MULTI_THREAD + newcon->c_lock = save_lock; /* restore saved lock with initialized state also */ +#endif + + /* clone the per-domain profiles (if any) */ + if (oldcon->c_instprof->profile_len > 0) { + newcon->c_instprof->profile = (__pmInDomProfile *)malloc( + oldcon->c_instprof->profile_len * sizeof(__pmInDomProfile)); + if (newcon->c_instprof->profile == NULL) { + sts = -oserror(); + goto done; + } + memcpy(newcon->c_instprof->profile, oldcon->c_instprof->profile, + oldcon->c_instprof->profile_len * sizeof(__pmInDomProfile)); + p = oldcon->c_instprof->profile; + p_end = p + oldcon->c_instprof->profile_len; + q = newcon->c_instprof->profile; + for (; p < p_end; p++, q++) { + if (p->instances) { + q->instances = (int *)malloc(p->instances_len * sizeof(int)); + if (q->instances == NULL) { + sts = -oserror(); + goto done; + } + memcpy(q->instances, p->instances, + p->instances_len * sizeof(int)); + } + } + } + + /* + * The call to pmNewContext (above) should have connected to the pmcd. + * Make sure the new profile will be sent before the next fetch. + */ + newcon->c_sent = 0; + + /* clone the archive control struct, if any */ + if (oldcon->c_archctl != NULL) { + if ((newcon->c_archctl = (__pmArchCtl *)malloc(sizeof(__pmArchCtl))) == NULL) { + sts = -oserror(); + goto done; + } + *newcon->c_archctl = *oldcon->c_archctl; /* struct assignment */ + /* + * Need to make hash list and read cache independent in case oldcon + * is subsequently closed via pmDestroyContext() and don't want + * __pmFreeInterpData() to trash our hash list and read cache. + * Start with an empty hash list and read cache for the dup'd context. + */ + newcon->c_archctl->ac_pmid_hc.nodes = 0; + newcon->c_archctl->ac_pmid_hc.hsize = 0; + newcon->c_archctl->ac_cache = NULL; + } + + sts = new; + +done: + /* return an error code, or the handle for the new context */ + if (sts < 0 && new >= 0) + contexts[new]->c_type = PM_CONTEXT_FREE; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "pmDupContext() -> %d\n", sts); + if (sts >= 0) + __pmDumpContext(stderr, sts, PM_INDOM_NULL); + } +#endif + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +pmUseContext(int handle) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (handle < 0 || handle >= contexts_len || + contexts[handle]->c_type == PM_CONTEXT_FREE) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmUseContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT); +#endif + PM_UNLOCK(__pmLock_libpcp); + return PM_ERR_NOCONTEXT; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmUseContext(%d) -> 0\n", handle); +#endif + PM_TPD(curcontext) = handle; + + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +int +pmDestroyContext(int handle) +{ + __pmContext *ctxp; + struct linger dolinger = {0, 1}; +#ifdef PM_MULTI_THREAD + int psts; + char errmsg[PM_MAXERRMSGLEN]; +#endif + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (handle < 0 || handle >= contexts_len || + contexts[handle]->c_type == PM_CONTEXT_FREE) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmDestroyContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT); +#endif + PM_UNLOCK(__pmLock_libpcp); + return PM_ERR_NOCONTEXT; + } + + ctxp = contexts[handle]; + PM_LOCK(ctxp->c_lock); + if (ctxp->c_pmcd != NULL) { + if (--ctxp->c_pmcd->pc_refcnt == 0) { + if (ctxp->c_pmcd->pc_fd >= 0) { + /* before close, unsent data should be flushed */ + __pmSetSockOpt(ctxp->c_pmcd->pc_fd, SOL_SOCKET, + SO_LINGER, (char *) &dolinger, (__pmSockLen)sizeof(dolinger)); + __pmCloseSocket(ctxp->c_pmcd->pc_fd); + } + __pmFreeHostSpec(ctxp->c_pmcd->pc_hosts, ctxp->c_pmcd->pc_nhosts); + free(ctxp->c_pmcd); + } + } + if (ctxp->c_archctl != NULL) { + __pmFreeInterpData(ctxp); + if (--ctxp->c_archctl->ac_log->l_refcnt == 0) { + __pmLogClose(ctxp->c_archctl->ac_log); + free(ctxp->c_archctl->ac_log); + } + if (ctxp->c_archctl->ac_cache != NULL) + free(ctxp->c_archctl->ac_cache); + free(ctxp->c_archctl); + } + ctxp->c_type = PM_CONTEXT_FREE; + + if (handle == PM_TPD(curcontext)) + /* we have no choice */ + PM_TPD(curcontext) = PM_CONTEXT_UNDEF; + + __pmFreeProfile(ctxp->c_instprof); + __dmclosecontext(ctxp); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmDestroyContext(%d) -> 0, curcontext=%d\n", + handle, PM_TPD(curcontext)); +#endif + + + PM_UNLOCK(ctxp->c_lock); +#ifdef PM_MULTI_THREAD + if ((psts = pthread_mutex_destroy(&ctxp->c_lock)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmDestroyContext(context=%d): pthread_mutex_destroy failed: %s\n", handle, errmsg); + /* + * Most likely cause is the mutex still being locked ... this is a + * a library bug, but potentially recoverable ... + */ + while (PM_UNLOCK(ctxp->c_lock) >= 0) { + fprintf(stderr, "pmDestroyContext(context=%d): extra unlock?\n", handle); + } + if ((psts = pthread_mutex_destroy(&ctxp->c_lock)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "pmDestroyContext(context=%d): pthread_mutex_destroy failed second try: %s\n", handle, errmsg); + } + /* keep going, rather than exit ... */ + } +#endif + + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +static const char *_mode[] = { "LIVE", "INTERP", "FORW", "BACK" }; + +/* + * dump context(s); context == -1 for all contexts, indom == PM_INDOM_NULL + * for all instance domains. + */ +void +__pmDumpContext(FILE *f, int context, pmInDom indom) +{ + int i; + __pmContext *con; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + fprintf(f, "Dump Contexts: current context = %d\n", PM_TPD(curcontext)); + if (PM_TPD(curcontext) < 0) { + PM_UNLOCK(__pmLock_libpcp); + return; + } + + if (indom != PM_INDOM_NULL) { + char strbuf[20]; + fprintf(f, "Dump restricted to indom=%d [%s]\n", + indom, pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + } + + for (i = 0; i < contexts_len; i++) { + con = contexts[i]; + if (context == -1 || context == i) { + fprintf(f, "Context[%d]", i); + if (con->c_type == PM_CONTEXT_HOST) { + fprintf(f, " host %s:", con->c_pmcd->pc_hosts[0].name); + fprintf(f, " pmcd=%s profile=%s fd=%d refcnt=%d", + (con->c_pmcd->pc_fd < 0) ? "NOT CONNECTED" : "CONNECTED", + con->c_sent ? "SENT" : "NOT_SENT", + con->c_pmcd->pc_fd, + con->c_pmcd->pc_refcnt); + if (con->c_flags) + fprintf(f, " flags=%x", con->c_flags); + } + else if (con->c_type == PM_CONTEXT_LOCAL) { + fprintf(f, " standalone:"); + fprintf(f, " profile=%s\n", + con->c_sent ? "SENT" : "NOT_SENT"); + } + else { + fprintf(f, " log %s:", con->c_archctl->ac_log->l_name); + fprintf(f, " mode=%s", _mode[con->c_mode & __PM_MODE_MASK]); + fprintf(f, " profile=%s tifd=%d mdfd=%d mfd=%d\nrefcnt=%d vol=%d", + con->c_sent ? "SENT" : "NOT_SENT", + con->c_archctl->ac_log->l_tifp == NULL ? -1 : fileno(con->c_archctl->ac_log->l_tifp), + fileno(con->c_archctl->ac_log->l_mdfp), + fileno(con->c_archctl->ac_log->l_mfp), + con->c_archctl->ac_log->l_refcnt, + con->c_archctl->ac_log->l_curvol); + fprintf(f, " offset=%ld (vol=%d) serial=%d", + (long)con->c_archctl->ac_offset, + con->c_archctl->ac_vol, + con->c_archctl->ac_serial); + } + if (con->c_type != PM_CONTEXT_LOCAL) { + fprintf(f, " origin=%d.%06d", + con->c_origin.tv_sec, con->c_origin.tv_usec); + fprintf(f, " delta=%d\n", con->c_delta); + } + __pmDumpProfile(f, indom, con->c_instprof); + } + } + + PM_UNLOCK(__pmLock_libpcp); +} + +#ifdef PM_MULTI_THREAD +#ifdef PM_MULTI_THREAD_DEBUG +/* + * return context if lock == c_lock for a context ... no locking here + * to avoid recursion ad nauseum + */ +int +__pmIsContextLock(void *lock) +{ + int i; + for (i = 0; i < contexts_len; i++) { + if ((void *)&contexts[i]->c_lock == lock) + return i; + } + return -1; +} + +/* + * return context if lock == pc_lock for a context ... no locking here + * to avoid recursion ad nauseum + */ +int +__pmIsChannelLock(void *lock) +{ + int i; + for (i = 0; i < contexts_len; i++) { + if ((void *)&contexts[i]->c_pmcd->pc_lock == lock) + return i; + } + return -1; +} +#endif +#endif diff --git a/src/libpcp/src/derive.c b/src/libpcp/src/derive.c new file mode 100644 index 0000000..55acf23 --- /dev/null +++ b/src/libpcp/src/derive.c @@ -0,0 +1,1864 @@ +/* + * Copyright (c) 2009,2014 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Debug Flags + * DERIVE - high-level diagnostics + * DERIVE & APPL0 - configuration and static syntax analysis + * DERIVE & APPL1 - expression binding, semantic analysis, PMNS ops + * DERIVE & APPL2 - fetch handling + * + * Caveats + * 0. No unary negation operator. + * 1. No derived metrics for pmFetchArchive() as this routine does + * not use a target pmidlist[] + * 2. Derived metrics will not work with pmRequestTraversePMNS() and + * pmReceiveTraversePMNS() because the by the time the list of + * names is received, the original name at the root of the search + * is no longer available. + * 3. pmRegisterDerived() does not apply retrospectively to any open + * contexts, so the normal use would be to make all calls to + * pmRegisterDerived() (possibly via pmLoadDerivedConfig()) and then + * call pmNewContext(). + * 4. There is no pmUnregisterDerived(), so once registered a derived + * metric persists for the life of the application. + * + * Thread-safe notes + * + * Need to call PM_INIT_LOCKS() in pmRegisterDerived() because we may + * be called before a context has been created, and missed the + * lock initialization in pmNewContext(). + * + * registered.mutex is held throughout pmRegisterDerived() and this + * protects all of the lexical scanner and parser state, i.e. tokbuf, + * tokbuflen, string, lexpeek and this. Same applies to pmid within + * pmRegisterDerived(). + * + * The return value from pmRegisterDerived is either a NULL or a pointer + * back into the expr argument, so use of "this" to carry the return + * value in the error case is OK. + * + * All access to registered is controlled by the registered.mutex. + * + * No locking needed in init() to protect need_init and the getenv() + * call, as we always lock the registered.mutex before calling init(). + * + * The context locking protocol ensures that when any of the routines + * below are called with a __pmContext * argument, that argument is + * not NULL and is associated with a context that is ALREADY locked + * via ctxp->c_lock. We should not unlock the context, that is the + * responsibility of our callers. + * + * derive_errmsg needs to be thread-private + */ + +#include <inttypes.h> +#include <assert.h> +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include "fault.h" +#ifdef IS_MINGW +extern const char *strerror_r(int, char *, size_t); +#endif + +static int need_init = 1; +static ctl_t registered = { +#ifdef PM_MULTI_THREAD +#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, +#else + PTHREAD_MUTEX_INITIALIZER, +#endif +#endif + 0, NULL, 0, 0 }; + +/* parser and lexer variables */ +static char *tokbuf = NULL; +static int tokbuflen; +static const char *this; /* start of current lexicon */ +static int lexpeek = 0; +static const char *string; + +#ifdef PM_MULTI_THREAD +#ifdef HAVE___THREAD +/* using a gcc construct here to make derive_errmsg thread-private */ +static __thread char *derive_errmsg; +#endif +#else +static char *derive_errmsg; +#endif + +static const char *type_dbg[] = { + "ERROR", "EOF", "UNDEF", "NUMBER", "NAME", "PLUS", "MINUS", + "STAR", "SLASH", "LPAREN", "RPAREN", "AVG", "COUNT", "DELTA", + "MAX", "MIN", "SUM", "ANON", "RATE" }; +static const char type_c[] = { + '\0', '\0', '\0', '\0', '\0', '+', '-', '*', '/', '(', ')', '\0' }; + +/* function table for lexer */ +static const struct { + int f_type; + char *f_name; +} func[] = { + { L_AVG, "avg" }, + { L_COUNT, "count" }, + { L_DELTA, "delta" }, + { L_MAX, "max" }, + { L_MIN, "min" }, + { L_SUM, "sum" }, + { L_ANON, "anon" }, + { L_RATE, "rate" }, + { L_UNDEF, NULL } +}; + +/* parser states */ +#define P_INIT 0 +#define P_LEAF 1 +#define P_LEAF_PAREN 2 +#define P_BINOP 3 +#define P_FUNC_OP 4 +#define P_FUNC_END 5 +#define P_END 99 + +static const char *state_dbg[] = { + "INIT", "LEAF", "LEAF_PAREN", "BINOP", "FUNC_OP", "FUNC_END" }; + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +static void +initialize_mutex(void) +{ + static pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER; + static int done = 0; + int psts; + char errmsg[PM_MAXERRMSGLEN]; + if ((psts = pthread_mutex_lock(&init)) != 0) { + strerror_r(psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "initializ_mutex: pthread_mutex_lock failed: %s", errmsg); + exit(4); + } + if (!done) { + /* + * Unable to initialize at compile time, need to do it here in + * a one trip for all threads run-time initialization. + */ + pthread_mutexattr_t attr; + + if ((psts = pthread_mutexattr_init(&attr)) != 0) { + strerror_r(psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "initialize_mutex: pthread_mutexattr_init failed: %s", errmsg); + exit(4); + } + if ((psts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) { + strerror_r(psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "initialize_mutex: pthread_mutexattr_settype failed: %s", errmsg); + exit(4); + } + if ((psts = pthread_mutex_init(®istered.mutex, &attr)) != 0) { + strerror_r(psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "initialize_mutex: pthread_mutex_init failed: %s", errmsg); + exit(4); + } + done = 1; + } + if ((psts = pthread_mutex_unlock(&init)) != 0) { + strerror_r(psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "initialize_mutex: pthread_mutex_unlock failed: %s", errmsg); + exit(4); + } +} +#endif +#endif + +/* Register an anonymous metric */ +int +__pmRegisterAnon(const char *name, int type) +{ + char *msg; + char buf[21]; /* anon(PM_TYPE_XXXXXX) */ + +PM_FAULT_CHECK(PM_FAULT_PMAPI); + switch (type) { + case PM_TYPE_32: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_32)"); + break; + case PM_TYPE_U32: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_U32)"); + break; + case PM_TYPE_64: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_64)"); + break; + case PM_TYPE_U64: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_U64)"); + break; + case PM_TYPE_FLOAT: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_FLOAT)"); + break; + case PM_TYPE_DOUBLE: + snprintf(buf, sizeof(buf), "anon(PM_TYPE_DOUBLE)"); + break; + default: + return PM_ERR_TYPE; + } + if ((msg = pmRegisterDerived(name, buf)) != NULL) { + pmprintf("__pmRegisterAnon(%s, %d): @ \"%s\" Error: %s\n", name, type, msg, pmDerivedErrStr()); + pmflush(); + return PM_ERR_GENERIC; + } + return 0; +} + +static void +init(void) +{ + if (need_init) { + char *configpath; + + if ((configpath = getenv("PCP_DERIVED_CONFIG")) != NULL) { + int sts; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + fprintf(stderr, "Derived metric initialization from $PCP_DERIVED_CONFIG\n"); + } +#endif + sts = pmLoadDerivedConfig(configpath); +#ifdef PCP_DEBUG + if (sts < 0 && (pmDebug & DBG_TRACE_DERIVE)) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "pmLoadDerivedConfig -> %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + } + need_init = 0; + } +} + +static void +unget(int c) +{ + lexpeek = c; +} + +static int +get() +{ + int c; + if (lexpeek != 0) { + c = lexpeek; + lexpeek = 0; + return c; + } + c = *string; + if (c == '\0') { + return L_EOF; + } + string++; + return c; +} + +static int +lex(void) +{ + int c; + char *p = tokbuf; + int ltype = L_UNDEF; + int i; + int firstch = 1; + + for ( ; ; ) { + c = get(); + if (firstch) { + if (isspace((int)c)) continue; + this = &string[-1]; + firstch = 0; + } + if (c == L_EOF) { + if (ltype != L_UNDEF) { + /* force end of last token */ + c = 0; + } + else { + /* really the end of the input */ + return L_EOF; + } + } + if (p == NULL) { + tokbuflen = 128; + if ((p = tokbuf = (char *)malloc(tokbuflen)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: alloc tokbuf", tokbuflen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + } + else if (p >= &tokbuf[tokbuflen]) { + int x = p - tokbuf; + tokbuflen *= 2; + if ((tokbuf = (char *)realloc(tokbuf, tokbuflen)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: realloc tokbuf", tokbuflen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + p = &tokbuf[x]; + } + + *p++ = (char)c; + + if (ltype == L_UNDEF) { + if (isdigit((int)c)) + ltype = L_NUMBER; + else if (isalpha((int)c)) + ltype = L_NAME; + else { + switch (c) { + case '+': + *p = '\0'; + return L_PLUS; + break; + + case '-': + *p = '\0'; + return L_MINUS; + break; + + case '*': + *p = '\0'; + return L_STAR; + break; + + case '/': + *p = '\0'; + return L_SLASH; + break; + + case '(': + *p = '\0'; + return L_LPAREN; + break; + + case ')': + *p = '\0'; + return L_RPAREN; + break; + + default: + return L_ERROR; + break; + } + } + } + else { + if (ltype == L_NUMBER) { + if (!isdigit((int)c)) { + unget(c); + p[-1] = '\0'; + return L_NUMBER; + } + } + else if (ltype == L_NAME) { + if (isalpha((int)c) || isdigit((int)c) || c == '_' || c == '.') + continue; + if (c == '(') { + /* check for functions ... */ + int namelen = p - tokbuf - 1; + for (i = 0; func[i].f_name != NULL; i++) { + if (namelen == strlen(func[i].f_name) && + strncmp(tokbuf, func[i].f_name, namelen) == 0) { + *p = '\0'; + return func[i].f_type; + } + } + } + /* current character is end of name */ + unget(c); + p[-1] = '\0'; + return L_NAME; + } + } + + } +} + +static node_t * +newnode(int type) +{ + node_t *np; + np = (node_t *)malloc(sizeof(node_t)); + if (np == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: newnode", sizeof(node_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + np->type = type; + np->save_last = 0; + np->left = NULL; + np->right = NULL; + np->value = NULL; + np->info = NULL; + return np; +} + +static void +free_expr(node_t *np) +{ + if (np == NULL) return; + if (np->left != NULL) free_expr(np->left); + if (np->right != NULL) free_expr(np->right); + /* value is only allocated once for the static nodes */ + if (np->info == NULL && np->value != NULL) free(np->value); + if (np->info != NULL) free(np->info); + free(np); +} + +/* + * copy a static expression tree to make the dynamic per context + * expression tree and initialize the info block + */ +static node_t * +bind_expr(int n, node_t *np) +{ + node_t *new; + + assert(np != NULL); + new = newnode(np->type); + if (np->left != NULL) { + if ((new->left = bind_expr(n, np->left)) == NULL) { + /* error, reported deeper in the recursion, clean up */ + free(new); + return(NULL); + } + } + if (np->right != NULL) { + if ((new->right = bind_expr(n, np->right)) == NULL) { + /* error, reported deeper in the recursion, clean up */ + free(new); + return(NULL); + } + } + if ((new->info = (info_t *)malloc(sizeof(info_t))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("bind_expr: info block", sizeof(info_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + new->info->pmid = PM_ID_NULL; + new->info->numval = 0; + new->info->mul_scale = new->info->div_scale = 1; + new->info->ivlist = NULL; + new->info->stamp.tv_sec = 0; + new->info->stamp.tv_usec = 0; + new->info->time_scale = -1; /* one-trip initialization if needed */ + new->info->last_numval = 0; + new->info->last_ivlist = NULL; + new->info->last_stamp.tv_sec = 0; + new->info->last_stamp.tv_usec = 0; + + /* need info to be non-null to protect copy of value in free_expr */ + new->value = np->value; + + new->save_last = np->save_last; + + if (new->type == L_NAME) { + int sts; + + sts = pmLookupName(1, &new->value, &new->info->pmid); + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "bind_expr: error: derived metric %s: operand: %s: %s\n", registered.mlist[n].name, new->value, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + free(new->info); + free(new); + return NULL; + } + sts = pmLookupDesc(new->info->pmid, &new->desc); + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "bind_expr: error: derived metric %s: operand (%s [%s]): %s\n", registered.mlist[n].name, new->value, pmIDStr_r(new->info->pmid, strbuf, sizeof(strbuf)), pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + free(new->info); + free(new); + return NULL; + } + } + else if (new->type == L_NUMBER) { + new->desc = np->desc; + } + + return new; +} + +static +void report_sem_error(char *name, node_t *np) +{ + pmprintf("Semantic error: derived metric %s: ", name); + switch (np->type) { + case L_PLUS: + case L_MINUS: + case L_STAR: + case L_SLASH: + if (np->left->type == L_NUMBER || np->left->type == L_NAME) + pmprintf("%s ", np->left->value); + else + pmprintf("<expr> "); + pmprintf("%c ", type_c[np->type+2]); + if (np->right->type == L_NUMBER || np->right->type == L_NAME) + pmprintf("%s", np->right->value); + else + pmprintf("<expr>"); + break; + case L_AVG: + case L_COUNT: + case L_DELTA: + case L_RATE: + case L_MAX: + case L_MIN: + case L_SUM: + case L_ANON: + pmprintf("%s(%s)", type_dbg[np->type+2], np->left->value); + break; + default: + /* should never get here ... */ + if (np->type+2 >= 0 && np->type+2 < sizeof(type_dbg)/sizeof(type_dbg[0])) + pmprintf("botch @ node type %s?", type_dbg[np->type+2]); + else + pmprintf("botch @ node type #%d?", np->type); + break; + } + pmprintf(": %s\n", PM_TPD(derive_errmsg)); + pmflush(); + PM_TPD(derive_errmsg) = NULL; +} + +/* type promotion */ +static const int promote[6][6] = { + { PM_TYPE_32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE }, + { PM_TYPE_U32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE }, + { PM_TYPE_64, PM_TYPE_64, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE }, + { PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE }, + { PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_DOUBLE }, + { PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE } +}; + +/* time scale conversion factors */ +static const int timefactor[] = { + 1000, /* NSEC -> USEC */ + 1000, /* USEC -> MSEC */ + 1000, /* MSEC -> SEC */ + 60, /* SEC -> MIN */ + 60, /* MIN -> HOUR */ +}; + +/* + * mapping pmUnits for the result, and refining pmDesc as we go ... + * we start with the pmDesc from the left operand and adjust as + * necessary + * + * scale conversion rules ... + * Count - choose larger, divide/multiply smaller by 10^(difference) + * Time - choose larger, divide/multiply smaller by appropriate scale + * Space - choose larger, divide/multiply smaller by 1024^(difference) + * and result is of type PM_TYPE_DOUBLE + * + * Need inverted logic to deal with numerator (dimension > 0) and + * denominator (dimension < 0) cases. + */ +static void +map_units(node_t *np) +{ + pmDesc *right = &np->right->desc; + pmDesc *left = &np->left->desc; + int diff; + int i; + + if (left->units.dimCount != 0 && right->units.dimCount != 0) { + diff = left->units.scaleCount - right->units.scaleCount; + if (diff > 0) { + /* use the left scaleCount, scale the right operand */ + for (i = 0; i < diff; i++) { + if (right->units.dimCount > 0) + np->right->info->div_scale *= 10; + else + np->right->info->mul_scale *= 10; + } + np->desc.type = PM_TYPE_DOUBLE; + } + else if (diff < 0) { + /* use the right scaleCount, scale the left operand */ + np->desc.units.scaleCount = right->units.scaleCount; + for (i = diff; i < 0; i++) { + if (left->units.dimCount > 0) + np->left->info->div_scale *= 10; + else + np->left->info->mul_scale *= 10; + } + np->desc.type = PM_TYPE_DOUBLE; + } + } + if (left->units.dimTime != 0 && right->units.dimTime != 0) { + diff = left->units.scaleTime - right->units.scaleTime; + if (diff > 0) { + /* use the left scaleTime, scale the right operand */ + for (i = right->units.scaleTime; i < left->units.scaleTime; i++) { + if (right->units.dimTime > 0) + np->right->info->div_scale *= timefactor[i]; + else + np->right->info->mul_scale *= timefactor[i]; + } + np->desc.type = PM_TYPE_DOUBLE; + } + else if (diff < 0) { + /* use the right scaleTime, scale the left operand */ + np->desc.units.scaleTime = right->units.scaleTime; + for (i = left->units.scaleTime; i < right->units.scaleTime; i++) { + if (right->units.dimTime > 0) + np->left->info->div_scale *= timefactor[i]; + else + np->left->info->mul_scale *= timefactor[i]; + } + np->desc.type = PM_TYPE_DOUBLE; + } + } + if (left->units.dimSpace != 0 && right->units.dimSpace != 0) { + diff = left->units.scaleSpace - right->units.scaleSpace; + if (diff > 0) { + /* use the left scaleSpace, scale the right operand */ + for (i = 0; i < diff; i++) { + if (right->units.dimSpace > 0) + np->right->info->div_scale *= 1024; + else + np->right->info->mul_scale *= 1024; + } + np->desc.type = PM_TYPE_DOUBLE; + } + else if (diff < 0) { + /* use the right scaleSpace, scale the left operand */ + np->desc.units.scaleSpace = right->units.scaleSpace; + for (i = diff; i < 0; i++) { + if (right->units.dimSpace > 0) + np->left->info->div_scale *= 1024; + else + np->left->info->mul_scale *= 1024; + } + np->desc.type = PM_TYPE_DOUBLE; + } + } + + if (np->type == L_STAR) { + np->desc.units.dimCount = left->units.dimCount + right->units.dimCount; + np->desc.units.dimTime = left->units.dimTime + right->units.dimTime; + np->desc.units.dimSpace = left->units.dimSpace + right->units.dimSpace; + } + else if (np->type == L_SLASH) { + np->desc.units.dimCount = left->units.dimCount - right->units.dimCount; + np->desc.units.dimTime = left->units.dimTime - right->units.dimTime; + np->desc.units.dimSpace = left->units.dimSpace - right->units.dimSpace; + } + + /* + * for division and multiplication, dimension may have come from + * right operand, need to pick up scale from there also + */ + if (np->desc.units.dimCount != 0 && left->units.dimCount == 0) + np->desc.units.scaleCount = right->units.scaleCount; + if (np->desc.units.dimTime != 0 && left->units.dimTime == 0) + np->desc.units.scaleTime = right->units.scaleTime; + if (np->desc.units.dimSpace != 0 && left->units.dimSpace == 0) + np->desc.units.scaleSpace = right->units.scaleSpace; + +} + +static int +map_desc(int n, node_t *np) +{ + /* + * pmDesc mapping for binary operators ... + * + * semantics acceptable operators + * counter, counter + - + * non-counter, non-counter + - * / + * counter, non-counter * / + * non-counter, counter * + * + * in the non-counter and non-counter case, the semantics for the + * result are PM_SEM_INSTANT, unless both operands are + * PM_SEM_DISCRETE in which case the result is also PM_SEM_DISCRETE + * + * type promotion (similar to ANSI C) + * PM_TYPE_STRING, PM_TYPE_AGGREGATE, PM_TYPE_AGGREGATE_STATIC, + * PM_TYPE_EVENT and PM_TYPE_HIGHRES_EVENT are illegal operands + * except for renaming (where no operator is involved) + * for all operands, division => PM_TYPE_DOUBLE + * else PM_TYPE_DOUBLE & any type => PM_TYPE_DOUBLE + * else PM_TYPE_FLOAT & any type => PM_TYPE_FLOAT + * else PM_TYPE_U64 & any type => PM_TYPE_U64 + * else PM_TYPE_64 & any type => PM_TYPE_64 + * else PM_TYPE_U32 & any type => PM_TYPE_U32 + * else PM_TYPE_32 & any type => PM_TYPE_32 + * + * units mapping + * operator checks + * +, - same dimension + * *, / if only one is a counter, non-counter must + * have pmUnits of "none" + */ + pmDesc *right = &np->right->desc; + pmDesc *left = &np->left->desc; + + if (left->sem == PM_SEM_COUNTER) { + if (right->sem == PM_SEM_COUNTER) { + if (np->type != L_PLUS && np->type != L_MINUS) { + PM_TPD(derive_errmsg) = "Illegal operator for counters"; + goto bad; + } + } + else { + if (np->type != L_STAR && np->type != L_SLASH) { + PM_TPD(derive_errmsg) = "Illegal operator for counter and non-counter"; + goto bad; + } + } + } + else { + if (right->sem == PM_SEM_COUNTER) { + if (np->type != L_STAR) { + PM_TPD(derive_errmsg) = "Illegal operator for non-counter and counter"; + goto bad; + } + } + else { + if (np->type != L_PLUS && np->type != L_MINUS && + np->type != L_STAR && np->type != L_SLASH) { + /* + * this is not possible at the present since only + * arithmetic operators are supported and all are + * acceptable here ... check added for completeness + */ + PM_TPD(derive_errmsg) = "Illegal operator for non-counters"; + goto bad; + } + } + } + + /* + * Choose candidate descriptor ... prefer metric or expression + * over constant + */ + if (np->left->type != L_NUMBER) + np->desc = *left; /* struct copy */ + else + np->desc = *right; /* struct copy */ + + /* + * most non-counter expressions produce PM_SEM_INSTANT results + */ + if (left->sem != PM_SEM_COUNTER && right->sem != PM_SEM_COUNTER) { + if (left->sem == PM_SEM_DISCRETE && right->sem == PM_SEM_DISCRETE) + np->desc.sem = PM_SEM_DISCRETE; + else + np->desc.sem = PM_SEM_INSTANT; + } + + /* + * type checking and promotion + */ + switch (left->type) { + case PM_TYPE_32: + case PM_TYPE_U32: + case PM_TYPE_64: + case PM_TYPE_U64: + case PM_TYPE_FLOAT: + case PM_TYPE_DOUBLE: + break; + default: + PM_TPD(derive_errmsg) = "Non-arithmetic type for left operand"; + goto bad; + } + switch (right->type) { + case PM_TYPE_32: + case PM_TYPE_U32: + case PM_TYPE_64: + case PM_TYPE_U64: + case PM_TYPE_FLOAT: + case PM_TYPE_DOUBLE: + break; + default: + PM_TPD(derive_errmsg) = "Non-arithmetic type for right operand"; + goto bad; + } + np->desc.type = promote[left->type][right->type]; + if (np->type == L_SLASH) { + /* for division result is real number */ + np->desc.type = PM_TYPE_DOUBLE; + } + + if (np->type == L_PLUS || np->type == L_MINUS) { + /* + * unit dimensions have to be identical + */ + if (left->units.dimCount != right->units.dimCount || + left->units.dimTime != right->units.dimTime || + left->units.dimSpace != right->units.dimSpace) { + PM_TPD(derive_errmsg) = "Dimensions are not the same"; + goto bad; + } + map_units(np); + } + + if (np->type == L_STAR || np->type == L_SLASH) { + /* + * if multiply or divide and operands are a counter and a non-counter, + * then non-counter needs to be dimensionless + */ + if (left->sem == PM_SEM_COUNTER && right->sem != PM_SEM_COUNTER) { + if (right->units.dimCount != 0 || + right->units.dimTime != 0 || + right->units.dimSpace != 0) { + PM_TPD(derive_errmsg) = "Non-counter and not dimensionless for right operand"; + goto bad; + } + } + if (left->sem != PM_SEM_COUNTER && right->sem == PM_SEM_COUNTER) { + if (left->units.dimCount != 0 || + left->units.dimTime != 0 || + left->units.dimSpace != 0) { + PM_TPD(derive_errmsg) = "Non-counter and not dimensionless for left operand"; + goto bad; + } + } + map_units(np); + } + + /* + * if not both singular, then both operands must have the same + * instance domain + */ + if (left->indom != PM_INDOM_NULL && right->indom != PM_INDOM_NULL && left->indom != right->indom) { + PM_TPD(derive_errmsg) = "Operands should have the same instance domain"; + goto bad; + } + + return 0; + +bad: + report_sem_error(registered.mlist[n].name, np); + return -1; +} + +static int +check_expr(int n, node_t *np) +{ + int sts; + + assert(np != NULL); + + if (np->type == L_NUMBER || np->type == L_NAME) + return 0; + + /* otherwise, np->left is never NULL ... */ + assert(np->left != NULL); + + if ((sts = check_expr(n, np->left)) < 0) + return sts; + if (np->right != NULL) { + if ((sts = check_expr(n, np->right)) < 0) + return sts; + /* build pmDesc from pmDesc of both operands */ + if ((sts = map_desc(n, np)) < 0) + return sts; + } + else { + np->desc = np->left->desc; /* struct copy */ + /* + * special cases for functions ... + * delta() expect numeric operand, result is instantaneous + * rate() expect numeric operand, dimension of time must be + * 0 or 1, result is instantaneous + * aggr funcs most expect numeric operand, result is instantaneous + * and singular + */ + if (np->type == L_AVG || np->type == L_COUNT + || np->type == L_DELTA || np->type == L_RATE + || np->type == L_MAX || np->type == L_MIN || np->type == L_SUM) { + if (np->type == L_COUNT) { + /* count() has its own type and units */ + np->desc.type = PM_TYPE_U32; + memset((void *)&np->desc.units, 0, sizeof(np->desc.units)); + np->desc.units.dimCount = 1; + np->desc.units.scaleCount = PM_COUNT_ONE; + } + else { + /* others inherit, but need arithmetic operand */ + switch (np->left->desc.type) { + case PM_TYPE_32: + case PM_TYPE_U32: + case PM_TYPE_64: + case PM_TYPE_U64: + case PM_TYPE_FLOAT: + case PM_TYPE_DOUBLE: + break; + default: + PM_TPD(derive_errmsg) = "Non-arithmetic operand for function"; + report_sem_error(registered.mlist[n].name, np); + return -1; + } + } + np->desc.sem = PM_SEM_INSTANT; + if (np->type == L_DELTA || np->type == L_RATE) { + /* inherit indom */ + if (np->type == L_RATE) { + /* + * further restriction for rate() that dimension + * for time must be 0 (->counter/sec) or 1 + * (->time utilization) + */ + if (np->left->desc.units.dimTime != 0 && np->left->desc.units.dimTime != 1) { + PM_TPD(derive_errmsg) = "Incorrect time dimension for operand"; + report_sem_error(registered.mlist[n].name, np); + return -1; + } + } + } + else { + /* all the others are aggregate funcs with a singular value */ + np->desc.indom = PM_INDOM_NULL; + } + if (np->type == L_AVG) { + /* avg() returns float result */ + np->desc.type = PM_TYPE_FLOAT; + } + if (np->type == L_RATE) { + /* rate() returns double result and time dimension is + * reduced by one ... if time dimension is then 0, set + * the scale to be none (this is time utilization) + */ + np->desc.type = PM_TYPE_DOUBLE; + np->desc.units.dimTime--; + if (np->desc.units.dimTime == 0) + np->desc.units.scaleTime = 0; + else + np->desc.units.scaleTime = PM_TIME_SEC; + } + } + else if (np->type == L_ANON) { + /* do nothing, pmDesc inherited "as is" from left node */ + ; + } + } + return 0; +} + +static void +dump_value(int type, pmAtomValue *avp) +{ + switch (type) { + case PM_TYPE_32: + fprintf(stderr, "%i", avp->l); + break; + + case PM_TYPE_U32: + fprintf(stderr, "%u", avp->ul); + break; + + case PM_TYPE_64: + fprintf(stderr, "%" PRId64, avp->ll); + break; + + case PM_TYPE_U64: + fprintf(stderr, "%" PRIu64, avp->ull); + break; + + case PM_TYPE_FLOAT: + fprintf(stderr, "%g", (double)avp->f); + break; + + case PM_TYPE_DOUBLE: + fprintf(stderr, "%g", avp->d); + break; + + case PM_TYPE_STRING: + fprintf(stderr, "%s", avp->cp); + break; + + case PM_TYPE_AGGREGATE: + case PM_TYPE_AGGREGATE_STATIC: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + case PM_TYPE_UNKNOWN: + fprintf(stderr, "[blob]"); + break; + + case PM_TYPE_NOSUPPORT: + fprintf(stderr, "dump_value: bogus value, metric Not Supported\n"); + break; + + default: + fprintf(stderr, "dump_value: unknown value type=%d\n", type); + } +} + +void +__dmdumpexpr(node_t *np, int level) +{ + char strbuf[20]; + + if (level == 0) fprintf(stderr, "Derived metric expr dump from " PRINTF_P_PFX "%p...\n", np); + if (np == NULL) return; + fprintf(stderr, "expr node " PRINTF_P_PFX "%p type=%s left=" PRINTF_P_PFX "%p right=" PRINTF_P_PFX "%p save_last=%d", np, type_dbg[np->type+2], np->left, np->right, np->save_last); + if (np->type == L_NAME || np->type == L_NUMBER) + fprintf(stderr, " [%s] master=%d", np->value, np->info == NULL ? 1 : 0); + fputc('\n', stderr); + if (np->info) { + fprintf(stderr, " PMID: %s ", pmIDStr_r(np->info->pmid, strbuf, sizeof(strbuf))); + fprintf(stderr, "(%s from pmDesc) numval: %d", pmIDStr_r(np->desc.pmid, strbuf, sizeof(strbuf)), np->info->numval); + if (np->info->div_scale != 1) + fprintf(stderr, " div_scale: %d", np->info->div_scale); + if (np->info->mul_scale != 1) + fprintf(stderr, " mul_scale: %d", np->info->mul_scale); + fputc('\n', stderr); + __pmPrintDesc(stderr, &np->desc); + if (np->info->ivlist) { + int j; + int max; + + max = np->info->numval > np->info->last_numval ? np->info->numval : np->info->last_numval; + + for (j = 0; j < max; j++) { + fprintf(stderr, "[%d]", j); + if (j < np->info->numval) { + fprintf(stderr, " inst=%d, val=", np->info->ivlist[j].inst); + dump_value(np->desc.type, &np->info->ivlist[j].value); + } + if (j < np->info->last_numval) { + fprintf(stderr, " (last inst=%d, val=", np->info->last_ivlist[j].inst); + dump_value(np->desc.type, &np->info->last_ivlist[j].value); + fputc(')', stderr); + } + fputc('\n', stderr); + } + } + } + if (np->left != NULL) __dmdumpexpr(np->left, level+1); + if (np->right != NULL) __dmdumpexpr(np->right, level+1); +} + +/* + * Parser FSA + * state lex new state + * P_INIT L_NAME or P_LEAF + * L_NUMBER + * P_INIT L_<func> P_FUNC_OP + * P_INIT L_LPAREN if parse() != NULL then P_LEAF + * P_LEAF L_PLUS or P_BINOP + * L_MINUS or + * L_STAR or + * L_SLASH + * P_BINOP L_NAME or P_LEAF + * L_NUMBER + * P_BINOP L_LPAREN if parse() != NULL then P_LEAF + * P_BINOP L_<func> P_FUNC_OP + * P_LEAF_PAREN same as P_LEAF, but no precedence rules at next operator + * P_FUNC_OP L_NAME P_FUNC_END + * P_FUNC_END L_RPAREN P_LEAF + */ +static node_t * +parse(int level) +{ + int state = P_INIT; + int type; + node_t *expr = NULL; + node_t *curr = NULL; + node_t *np; + + for ( ; ; ) { + type = lex(); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL0)) { + fprintf(stderr, "parse(%d) state=P_%s type=L_%s \"%s\"\n", level, state_dbg[state], type_dbg[type+2], type == L_EOF ? "" : tokbuf); + } +#endif + /* handle lexicons that terminate the parsing */ + switch (type) { + case L_ERROR: + PM_TPD(derive_errmsg) = "Illegal character"; + free_expr(expr); + return NULL; + break; + case L_EOF: + if (level == 1 && (state == P_LEAF || state == P_LEAF_PAREN)) + return expr; + PM_TPD(derive_errmsg) = "End of input"; + free_expr(expr); + return NULL; + break; + case L_RPAREN: + if (state == P_FUNC_END) { + state = P_LEAF; + continue; + } + if ((level > 1 && state == P_LEAF_PAREN) || state == P_LEAF) + return expr; + PM_TPD(derive_errmsg) = "Unexpected ')'"; + free_expr(expr); + return NULL; + break; + } + + switch (state) { + case P_INIT: + /* + * Only come here at the start of parsing an expression. + * The assert() is designed to stop Coverity flagging a + * memory leak if we should come here after expr and/or + * curr have already been assigned values either directly + * from calling newnode() or via an assignment to np that + * was previously assigned a value from newnode() + */ + assert(expr == NULL && curr == NULL); + + if (type == L_NAME || type == L_NUMBER) { + expr = curr = newnode(type); + if ((curr->value = strdup(tokbuf)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: leaf node", strlen(tokbuf)+1, PM_FATAL_ERR); + /*NOTREACHED*/ + } + if (type == L_NUMBER) { + char *endptr; + __uint64_t check; + check = strtoull(tokbuf, &endptr, 10); + if (*endptr != '\0' || check > 0xffffffffUL) { + PM_TPD(derive_errmsg) = "Constant value too large"; + free_expr(expr); + return NULL; + } + curr->desc.pmid = PM_ID_NULL; + curr->desc.type = PM_TYPE_U32; + curr->desc.indom = PM_INDOM_NULL; + curr->desc.sem = PM_SEM_DISCRETE; + memset(&curr->desc.units, 0, sizeof(pmUnits)); + } + state = P_LEAF; + } + else if (type == L_LPAREN) { + expr = curr = parse(level+1); + if (expr == NULL) + return NULL; + state = P_LEAF_PAREN; + } + else if (type == L_AVG || type == L_COUNT + || type == L_DELTA || type == L_RATE + || type == L_MAX || type == L_MIN || type == L_SUM + || type == L_ANON) { + expr = curr = newnode(type); + state = P_FUNC_OP; + } + else { + free_expr(expr); + return NULL; + } + break; + + case P_LEAF_PAREN: /* fall through */ + case P_LEAF: + if (type == L_PLUS || type == L_MINUS || type == L_STAR || type == L_SLASH) { + np = newnode(type); + if (state == P_LEAF_PAREN || + curr->type == L_NAME || curr->type == L_NUMBER || + curr->type == L_AVG || curr->type == L_COUNT || + curr->type == L_DELTA || curr->type == L_RATE || + curr->type == L_MAX || curr->type == L_MIN || + curr->type == L_SUM || curr->type == L_ANON || + type == L_PLUS || type == L_MINUS) { + /* + * first operator or equal or lower precedence + * make new root of tree and push previous + * expr down left descendent branch + */ + np->left = curr; + expr = curr = np; + } + else { + /* + * push previous right branch down one level + */ + np->left = curr->right; + curr->right = np; + curr = np; + } + state = P_BINOP; + } + else { + free_expr(expr); + return NULL; + } + break; + + case P_BINOP: + if (type == L_NAME || type == L_NUMBER) { + np = newnode(type); + if ((np->value = strdup(tokbuf)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: leaf node", strlen(tokbuf)+1, PM_FATAL_ERR); + /*NOTREACHED*/ + } + if (type == L_NUMBER) { + np->desc.pmid = PM_ID_NULL; + np->desc.type = PM_TYPE_U32; + np->desc.indom = PM_INDOM_NULL; + np->desc.sem = PM_SEM_DISCRETE; + memset(&np->desc.units, 0, sizeof(pmUnits)); + } + curr->right = np; + curr = expr; + state = P_LEAF; + } + else if (type == L_LPAREN) { + np = parse(level+1); + if (np == NULL) + return NULL; + curr->right = np; + state = P_LEAF_PAREN; + } + else if (type == L_AVG || type == L_COUNT + || type == L_DELTA || type == L_RATE + || type == L_MAX || type == L_MIN || type == L_SUM + || type == L_ANON) { + np = newnode(type); + curr->right = np; + curr = np; + state = P_FUNC_OP; + } + else { + free_expr(expr); + return NULL; + } + break; + + case P_FUNC_OP: + if (type == L_NAME) { + np = newnode(type); + if ((np->value = strdup(tokbuf)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: func op node", strlen(tokbuf)+1, PM_FATAL_ERR); + /*NOTREACHED*/ + } + np->save_last = 1; + if (curr->type == L_ANON) { + /* + * anon(PM_TYPE_...) is a special case ... the + * argument defines the metric type, the remainder + * of the metadata is fixed and there are never any + * values available. So we build the pmDesc and then + * clobber the "left" node and prevent any attempt to + * contact a PMDA for metadata or values + */ + if (strcmp(np->value, "PM_TYPE_32") == 0) + np->desc.type = PM_TYPE_32; + else if (strcmp(np->value, "PM_TYPE_U32") == 0) + np->desc.type = PM_TYPE_U32; + else if (strcmp(np->value, "PM_TYPE_64") == 0) + np->desc.type = PM_TYPE_64; + else if (strcmp(np->value, "PM_TYPE_U64") == 0) + np->desc.type = PM_TYPE_U64; + else if (strcmp(np->value, "PM_TYPE_FLOAT") == 0) + np->desc.type = PM_TYPE_FLOAT; + else if (strcmp(np->value, "PM_TYPE_DOUBLE") == 0) + np->desc.type = PM_TYPE_DOUBLE; + else { + fprintf(stderr, "Error: type=%s not allowed for anon()\n", np->value); + free_expr(np); + return NULL; + } + np->desc.pmid = PM_ID_NULL; + np->desc.indom = PM_INDOM_NULL; + np->desc.sem = PM_SEM_DISCRETE; + memset((void *)&np->desc.units, 0, sizeof(np->desc.units)); + np->type = L_NUMBER; + } + curr->left = np; + curr = expr; + state = P_FUNC_END; + } + else { + free_expr(expr); + return NULL; + } + break; + + default: + free_expr(expr); + return NULL; + } + } +} + +static int +checkname(char *p) +{ + int firstch = 1; + + for ( ; *p; p++) { + if (firstch) { + firstch = 0; + if (isalpha((int)*p)) continue; + return -1; + } + else { + if (isalpha((int)*p) || isdigit((int)*p) || *p == '_') continue; + if (*p == '.') { + firstch = 1; + continue; + } + return -1; + } + } + return 0; +} + +char * +pmRegisterDerived(const char *name, const char *expr) +{ + node_t *np; + static __pmID_int pmid; + int i; + + PM_INIT_LOCKS(); +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL0)) { + fprintf(stderr, "pmRegisterDerived: name=\"%s\" expr=\"%s\"\n", name, expr); + } +#endif + + for (i = 0; i < registered.nmetric; i++) { + if (strcmp(name, registered.mlist[i].name) == 0) { + /* oops, duplicate name ... */ + PM_TPD(derive_errmsg) = "Duplicate derived metric name"; + PM_UNLOCK(registered.mutex); + return (char *)expr; + } + } + + PM_TPD(derive_errmsg) = NULL; + string = expr; + np = parse(1); + if (np == NULL) { + /* parser error */ + char *sts = (char *)this; + PM_UNLOCK(registered.mutex); + return sts; + } + + registered.nmetric++; + registered.mlist = (dm_t *)realloc(registered.mlist, registered.nmetric*sizeof(dm_t)); + if (registered.mlist == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmRegisterDerived: registered mlist", registered.nmetric*sizeof(dm_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + if (registered.nmetric == 1) { + pmid.flag = 0; + pmid.domain = DYNAMIC_PMID; + pmid.cluster = 0; + } + registered.mlist[registered.nmetric-1].name = strdup(name); + pmid.item = registered.nmetric; + registered.mlist[registered.nmetric-1].pmid = *((pmID *)&pmid); + registered.mlist[registered.nmetric-1].expr = np; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + fprintf(stderr, "pmRegisterDerived: register metric[%d] %s = %s\n", registered.nmetric-1, name, expr); + if (pmDebug & DBG_TRACE_APPL0) + __dmdumpexpr(np, 0); + } +#endif + + PM_UNLOCK(registered.mutex); + return NULL; +} + +int +pmLoadDerivedConfig(const char *fname) +{ + FILE *fp; + int buflen; + char *buf; + char *p; + int c; + int sts = 0; + int eq = -1; + int lineno = 1; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + fprintf(stderr, "pmLoadDerivedConfig(\"%s\")\n", fname); + } +#endif + + if ((fp = fopen(fname, "r")) == NULL) { + return -oserror(); + } + buflen = 128; + if ((buf = (char *)malloc(buflen)) == NULL) { + /* registered.mutex not locked in this case */ + __pmNoMem("pmLoadDerivedConfig: alloc buf", buflen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + p = buf; + while ((c = fgetc(fp)) != EOF) { + if (p == &buf[buflen]) { + if ((buf = (char *)realloc(buf, 2*buflen)) == NULL) { + /* registered.mutex not locked in this case */ + __pmNoMem("pmLoadDerivedConfig: expand buf", 2*buflen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + p = &buf[buflen]; + buflen *= 2; + } + if (c == '=' && eq == -1) { + /* + * mark first = in line ... metric name to the left and + * expression to the right + */ + eq = p - buf; + } + if (c == '\n') { + if (p == buf || buf[0] == '#') { + /* comment or empty line, skip it ... */ + goto next_line; + } + *p = '\0'; + if (eq != -1) { + char *np; /* copy of name */ + char *ep; /* start of expression */ + char *q; + char *errp; + buf[eq] = '\0'; + if ((np = strdup(buf)) == NULL) { + /* registered.mutex not locked in this case */ + __pmNoMem("pmLoadDerivedConfig: dupname", strlen(buf), PM_FATAL_ERR); + /*NOTREACHED*/ + } + /* trim white space from tail of metric name */ + q = &np[eq-1]; + while (q >= np && isspace((int)*q)) + *q-- = '\0'; + /* trim white space from head of metric name */ + q = np; + while (*q && isspace((int)*q)) + q++; + if (*q == '\0') { + buf[eq] = '='; + pmprintf("[%s:%d] Error: pmLoadDerivedConfig: derived metric name missing\n%s\n", fname, lineno, buf); + pmflush(); + free(np); + goto next_line; + } + if (checkname(q) < 0) { + pmprintf("[%s:%d] Error: pmLoadDerivedConfig: illegal derived metric name (%s)\n", fname, lineno, q); + pmflush(); + free(np); + goto next_line; + } + ep = &buf[eq+1]; + while (*ep != '\0' && isspace((int)*ep)) + ep++; + if (*ep == '\0') { + buf[eq] = '='; + pmprintf("[%s:%d] Error: pmLoadDerivedConfig: expression missing\n%s\n", fname, lineno, buf); + pmflush(); + free(np); + goto next_line; + } + errp = pmRegisterDerived(q, ep); + if (errp != NULL) { + pmprintf("[%s:%d] Error: pmRegisterDerived(%s, ...) syntax error\n", fname, lineno, q); + pmprintf("%s\n", &buf[eq+1]); + for (q = &buf[eq+1]; *q; q++) { + if (q == errp) *q = '^'; + else if (!isspace((int)*q)) *q = ' '; + } + pmprintf("%s\n", &buf[eq+1]); + q = pmDerivedErrStr(); + if (q != NULL) pmprintf("%s\n", q); + pmflush(); + } + else + sts++; + free(np); + } + else { + /* + * error ... no = in the line, so no derived metric name + */ + pmprintf("[%s:%d] Error: pmLoadDerivedConfig: missing ``='' after derived metric name\n%s\n", fname, lineno, buf); + pmflush(); + } +next_line: + lineno++; + p = buf; + eq = -1; + } + else + *p++ = c; + } + fclose(fp); + free(buf); + return sts; +} + +char * +pmDerivedErrStr(void) +{ + PM_INIT_LOCKS(); + return PM_TPD(derive_errmsg); +} + +/* + * callbacks + */ + +int +__dmtraverse(const char *name, char ***namelist) +{ + int sts = 0; + int i; + char **list = NULL; + int matchlen = strlen(name); + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + init(); + + for (i = 0; i < registered.nmetric; i++) { + /* + * prefix match ... if name is "", then all names match + */ + if (matchlen == 0 || + (strncmp(name, registered.mlist[i].name, matchlen) == 0 && + (registered.mlist[i].name[matchlen] == '.' || + registered.mlist[i].name[matchlen] == '\0'))) { + sts++; + if ((list = (char **)realloc(list, sts*sizeof(list[0]))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("__dmtraverse: list", sts*sizeof(list[0]), PM_FATAL_ERR); + /*NOTREACHED*/ + } + list[sts-1] = registered.mlist[i].name; + } + } + *namelist = list; + + PM_UNLOCK(registered.mutex); + return sts; +} + +int +__dmchildren(const char *name, char ***offspring, int **statuslist) +{ + int sts = 0; + int i; + int j; + char **children = NULL; + int *status = NULL; + int matchlen = strlen(name); + int start; + int len; + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + init(); + + for (i = 0; i < registered.nmetric; i++) { + /* + * prefix match ... pick off the unique next level names on match + */ + if (name[0] == '\0' || + (strncmp(name, registered.mlist[i].name, matchlen) == 0 && + (registered.mlist[i].name[matchlen] == '.' || + registered.mlist[i].name[matchlen] == '\0'))) { + if (registered.mlist[i].name[matchlen] == '\0') { + /* + * leaf node + * assert is for coverity, name uniqueness means we + * should only ever come here after zero passes through + * the block below where sts is incremented and children[] + * and status[] are realloc'd + */ + assert(sts == 0 && children == NULL && status == NULL); + PM_UNLOCK(registered.mutex); + return 0; + } + start = matchlen > 0 ? matchlen + 1 : 0; + for (j = 0; j < sts; j++) { + len = strlen(children[j]); + if (strncmp(®istered.mlist[i].name[start], children[j], len) == 0 && + registered.mlist[i].name[start+len] == '.') + break; + } + if (j == sts) { + /* first time for this one */ + sts++; + if ((children = (char **)realloc(children, sts*sizeof(children[0]))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("__dmchildren: children", sts*sizeof(children[0]), PM_FATAL_ERR); + /*NOTREACHED*/ + } + for (len = 0; registered.mlist[i].name[start+len] != '\0' && registered.mlist[i].name[start+len] != '.'; len++) + ; + if ((children[sts-1] = (char *)malloc(len+1)) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("__dmchildren: name", len+1, PM_FATAL_ERR); + /*NOTREACHED*/ + } + strncpy(children[sts-1], ®istered.mlist[i].name[start], len); + children[sts-1][len] = '\0'; +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) { + fprintf(stderr, "__dmchildren: offspring[%d] %s", sts-1, children[sts-1]); + } +#endif + + if (statuslist != NULL) { + if ((status = (int *)realloc(status, sts*sizeof(status[0]))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("__dmchildren: statrus", sts*sizeof(status[0]), PM_FATAL_ERR); + /*NOTREACHED*/ + } + status[sts-1] = registered.mlist[i].name[start+len] == '\0' ? PMNS_LEAF_STATUS : PMNS_NONLEAF_STATUS; +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) { + fprintf(stderr, " (status=%d)", status[sts-1]); + } +#endif + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) { + fputc('\n', stderr); + } +#endif + } + } + } + + if (sts == 0) { + PM_UNLOCK(registered.mutex); + return PM_ERR_NAME; + } + + *offspring = children; + if (statuslist != NULL) + *statuslist = status; + + PM_UNLOCK(registered.mutex); + return sts; +} + +int +__dmgetpmid(const char *name, pmID *dp) +{ + int i; + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + init(); + + for (i = 0; i < registered.nmetric; i++) { + if (strcmp(name, registered.mlist[i].name) == 0) { + *dp = registered.mlist[i].pmid; + PM_UNLOCK(registered.mutex); + return 0; + } + } + PM_UNLOCK(registered.mutex); + return PM_ERR_NAME; +} + +int +__dmgetname(pmID pmid, char ** name) +{ + int i; + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + init(); + + for (i = 0; i < registered.nmetric; i++) { + if (pmid == registered.mlist[i].pmid) { + *name = strdup(registered.mlist[i].name); + if (*name == NULL) { + PM_UNLOCK(registered.mutex); + return -oserror(); + } + else { + PM_UNLOCK(registered.mutex); + return 0; + } + } + } + PM_UNLOCK(registered.mutex); + return PM_ERR_PMID; +} + +void +__dmopencontext(__pmContext *ctxp) +{ + int i; + int sts; + ctl_t *cp; + +#ifdef PM_MULTI_THREAD +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + initialize_mutex(); +#endif +#endif + PM_LOCK(registered.mutex); + init(); + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) { + fprintf(stderr, "__dmopencontext(->ctx %d) called\n", __pmPtrToHandle(ctxp)); + } +#endif + if (registered.nmetric == 0) { + ctxp->c_dm = NULL; + PM_UNLOCK(registered.mutex); + return; + } + if ((cp = (void *)malloc(sizeof(ctl_t))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmNewContext: derived metrics (ctl)", sizeof(ctl_t), PM_FATAL_ERR); + /* NOTREACHED */ + } + ctxp->c_dm = (void *)cp; + cp->nmetric = registered.nmetric; + if ((cp->mlist = (dm_t *)malloc(cp->nmetric*sizeof(dm_t))) == NULL) { + PM_UNLOCK(registered.mutex); + __pmNoMem("pmNewContext: derived metrics (mlist)", cp->nmetric*sizeof(dm_t), PM_FATAL_ERR); + /* NOTREACHED */ + } + for (i = 0; i < cp->nmetric; i++) { + cp->mlist[i].name = registered.mlist[i].name; + cp->mlist[i].pmid = registered.mlist[i].pmid; + assert(registered.mlist[i].expr != NULL); + /* failures must be reported in bind_expr() or below */ + cp->mlist[i].expr = bind_expr(i, registered.mlist[i].expr); + if (cp->mlist[i].expr != NULL) { + /* failures must be reported in check_expr() or below */ + sts = check_expr(i, cp->mlist[i].expr); + if (sts < 0) { + free_expr(cp->mlist[i].expr); + cp->mlist[i].expr = NULL; + } + else { + /* set correct PMID in pmDesc at the top level */ + cp->mlist[i].expr->desc.pmid = cp->mlist[i].pmid; + } + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && cp->mlist[i].expr != NULL) { + fprintf(stderr, "__dmopencontext: bind metric[%d] %s\n", i, registered.mlist[i].name); + if (pmDebug & DBG_TRACE_APPL1) + __dmdumpexpr(cp->mlist[i].expr, 0); + } +#endif + } + PM_UNLOCK(registered.mutex); +} + +void +__dmclosecontext(__pmContext *ctxp) +{ + int i; + ctl_t *cp = (ctl_t *)ctxp->c_dm; + + /* if needed, init() called in __dmopencontext beforehand */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + fprintf(stderr, "__dmclosecontext(->ctx %d) called dm->" PRINTF_P_PFX "%p %d metrics\n", __pmPtrToHandle(ctxp), cp, cp == NULL ? -1 : cp->nmetric); + } +#endif + if (cp == NULL) return; + for (i = 0; i < cp->nmetric; i++) { + free_expr(cp->mlist[i].expr); + } + free(cp->mlist); + free(cp); + ctxp->c_dm = NULL; +} + +int +__dmdesc(__pmContext *ctxp, pmID pmid, pmDesc *desc) +{ + int i; + ctl_t *cp = (ctl_t *)ctxp->c_dm; + + /* if needed, init() called in __dmopencontext beforehand */ + + if (cp == NULL) return PM_ERR_PMID; + + for (i = 0; i < cp->nmetric; i++) { + if (cp->mlist[i].pmid == pmid) { + if (cp->mlist[i].expr == NULL) + /* bind failed for some reason, reported earlier */ + return PM_ERR_NAME; + *desc = cp->mlist[i].expr->desc; + return 0; + } + } + return PM_ERR_PMID; +} + +#ifdef PM_MULTI_THREAD +#ifdef PM_MULTI_THREAD_DEBUG +/* + * return true if lock == registered.mutex ... no locking here to avoid + * recursion ad nauseum + */ +int +__pmIsDeriveLock(void *lock) +{ + return lock == (void *)®istered.mutex; +} +#endif +#endif diff --git a/src/libpcp/src/derive.h b/src/libpcp/src/derive.h new file mode 100644 index 0000000..56a0196 --- /dev/null +++ b/src/libpcp/src/derive.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef _DERIVE_H +#define _DERIVE_H + +/* + * Derived Metrics support + */ + +typedef struct { /* one value in the expression tree */ + int inst; + pmAtomValue value; + int vlen; /* from vlen of pmValueBlock for string and aggregates */ +} val_t; + +typedef struct { /* dynamic information for an expression node */ + pmID pmid; + int numval; /* length of ivlist[] */ + int mul_scale; /* scale multiplier */ + int div_scale; /* scale divisor */ + val_t *ivlist; /* instance-value pairs */ + struct timeval stamp; /* timestamp from current fetch */ + double time_scale; /* time utilization scaling for rate() */ + int last_numval; /* length of last_ivlist[] */ + val_t *last_ivlist; /* values from previous fetch for delta() or rate() */ + struct timeval last_stamp; /* timestamp from previous fetch for rate() */ +} info_t; + +typedef struct node { /* expression tree node */ + int type; + pmDesc desc; + int save_last; + struct node *left; + struct node *right; + char *value; + info_t *info; +} node_t; + +typedef struct { /* one derived metric */ + char *name; + pmID pmid; + node_t *expr; +} dm_t; + +/* + * Control structure for a set of derived metrics. + * This is used for the static definitions (registered) and the dynamic + * tree of expressions maintained per context. + */ +typedef struct { + __pmMutex mutex; + int nmetric; /* derived metrics */ + dm_t *mlist; + int fetch_has_dm; /* ==1 if pmResult rewrite needed */ + int numpmid; /* from pmFetch before rewrite */ +} ctl_t; + +/* lexical types */ +#define L_ERROR -2 +#define L_EOF -1 +#define L_UNDEF 0 +#define L_NUMBER 1 +#define L_NAME 2 +#define L_PLUS 3 +#define L_MINUS 4 +#define L_STAR 5 +#define L_SLASH 6 +#define L_LPAREN 7 +#define L_RPAREN 8 +#define L_AVG 9 +#define L_COUNT 10 +#define L_DELTA 11 +#define L_MAX 12 +#define L_MIN 13 +#define L_SUM 14 +#define L_ANON 15 +#define L_RATE 16 + +extern int __dmtraverse(const char *, char ***) _PCP_HIDDEN; +extern int __dmchildren(const char *, char ***, int **) _PCP_HIDDEN; +extern int __dmgetpmid(const char *, pmID *) _PCP_HIDDEN; +extern int __dmgetname(pmID, char **) _PCP_HIDDEN; +extern void __dmopencontext(__pmContext *) _PCP_HIDDEN; +extern void __dmclosecontext(__pmContext *) _PCP_HIDDEN; +extern int __dmdesc(__pmContext *, pmID, pmDesc *) _PCP_HIDDEN; +extern int __dmprefetch(__pmContext *, int, const pmID *, pmID **) _PCP_HIDDEN; +extern void __dmpostfetch(__pmContext *, pmResult **) _PCP_HIDDEN; +extern void __dmdumpexpr(node_t *, int) _PCP_HIDDEN; + +#endif /* _DERIVE_H */ diff --git a/src/libpcp/src/derive_fetch.c b/src/libpcp/src/derive_fetch.c new file mode 100644 index 0000000..061c67a --- /dev/null +++ b/src/libpcp/src/derive_fetch.c @@ -0,0 +1,1317 @@ +/* + * Copyright (c) 2009,2014 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Debug Flags + * DERIVE - high-level diagnostics + * DERIVE & APPL0 - configuration and static syntax analysis + * DERIVE & APPL1 - expression binding and semantic analysis + * DERIVE & APPL2 - fetch handling + */ + +#include <inttypes.h> +#include <assert.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +static void +get_pmids(node_t *np, int *cnt, pmID **list) +{ + assert(np != NULL); + if (np->left != NULL) get_pmids(np->left, cnt, list); + if (np->right != NULL) get_pmids(np->right, cnt, list); + if (np->type == L_NAME) { + (*cnt)++; + if ((*list = (pmID *)realloc(*list, (*cnt)*sizeof(pmID))) == NULL) { + __pmNoMem("__dmprefetch: realloc xtralist", (*cnt)*sizeof(pmID), PM_FATAL_ERR); + /*NOTREACHED*/ + } + (*list)[*cnt-1] = np->info->pmid; + } +} + +/* + * Walk the pmidlist[] from pmFetch. + * For each derived metric found in the list add all the operand metrics, + * and build a combined pmID list (newlist). + * + * Return 0 if no derived metrics in the list, else the number of pmIDs + * in the combined list. + * + * The derived metric pmIDs are left in the combined list (they will + * return PM_ERR_NOAGENT from the fetch) to simplify the post-processing + * of the pmResult in __dmpostfetch() + */ +int +__dmprefetch(__pmContext *ctxp, int numpmid, const pmID *pmidlist, pmID **newlist) +{ + int i; + int j; + int m; + int xtracnt = 0; + pmID *xtralist = NULL; + pmID *list; + ctl_t *cp = (ctl_t *)ctxp->c_dm; + + /* if needed, init() called in __dmopencontext beforehand */ + + if (cp == NULL) return 0; + + /* + * save numpmid to be used in __dmpostfetch() ... works because calls + * to pmFetch cannot be nested (at all, but certainly for the same + * context). + * Ditto for the fast path flag (fetch_has_dm). + */ + cp->numpmid = numpmid; + cp->fetch_has_dm = 0; + + for (m = 0; m < numpmid; m++) { + if (pmid_domain(pmidlist[m]) != DYNAMIC_PMID || + pmid_item(pmidlist[m]) == 0) + continue; + for (i = 0; i < cp->nmetric; i++) { + if (pmidlist[m] == cp->mlist[i].pmid) { + if (cp->mlist[i].expr != NULL) { + get_pmids(cp->mlist[i].expr, &xtracnt, &xtralist); + cp->fetch_has_dm = 1; + } + break; + } + } + } + if (xtracnt == 0) { + if (cp->fetch_has_dm) + return numpmid; + else + return 0; + } + + /* + * Some of the "extra" ones, may already be in the caller's pmFetch + * list, or repeated in xtralist[] (if the same metric operand appears + * more than once as a leaf node in the expression tree. + * Remove these duplicates + */ + j = 0; + for (i = 0; i < xtracnt; i++) { + for (m = 0; m < numpmid; m++) { + if (xtralist[i] == pmidlist[m]) + /* already in pmFetch list */ + break; + } + if (m < numpmid) continue; + for (m = 0; m < j; m++) { + if (xtralist[i] == xtralist[m]) + /* already in xtralist[] */ + break; + } + if (m == j) + xtralist[j++] = xtralist[i]; + } + xtracnt = j; + if (xtracnt == 0) { + free(xtralist); + return numpmid; + } + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + char strbuf[20]; + fprintf(stderr, "derived metrics prefetch added %d metrics:", xtracnt); + for (i = 0; i < xtracnt; i++) + fprintf(stderr, " %s", pmIDStr_r(xtralist[i], strbuf, sizeof(strbuf))); + fputc('\n', stderr); + } +#endif + if ((list = (pmID *)malloc((numpmid+xtracnt)*sizeof(pmID))) == NULL) { + __pmNoMem("__dmprefetch: alloc list", (numpmid+xtracnt)*sizeof(pmID), PM_FATAL_ERR); + /*NOTREACHED*/ + } + for (m = 0; m < numpmid; m++) { + list[m] = pmidlist[m]; + } + for (i = 0; i < xtracnt; i++) { + list[m++] = xtralist[i]; + } + free(xtralist); + *newlist = list; + + return m; +} + +/* + * Free the old ivlist[] (if any) ... may need to walk the list because + * the pmAtomValues may have buffers attached in the type STRING, + * type AGGREGATE* and type EVENT cases. + * Includes logic to save one history sample (for delta() and rate()). + */ +static void +free_ivlist(node_t *np) +{ + int i; + + assert(np->info != NULL); + + if (np->save_last) { + /* + * saving history for delta() or rate() ... release previous + * sample, and save this sample + */ + if (np->info->last_ivlist != NULL) { + /* + * no STRING, AGGREGATE or EVENT types for delta() or rate() + * so simple free() + */ + free(np->info->last_ivlist); + } + np->info->last_numval = np->info->numval; + np->info->last_ivlist = np->info->ivlist; + np->info->ivlist = NULL; + } + else { + /* no history */ + if (np->info->ivlist != NULL) { + if (np->desc.type == PM_TYPE_STRING) { + for (i = 0; i < np->info->numval; i++) { + if (np->info->ivlist[i].value.cp != NULL) + free(np->info->ivlist[i].value.cp); + } + } + else if (np->desc.type == PM_TYPE_AGGREGATE || + np->desc.type == PM_TYPE_AGGREGATE_STATIC || + np->desc.type == PM_TYPE_EVENT || + np->desc.type == PM_TYPE_HIGHRES_EVENT) { + for (i = 0; i < np->info->numval; i++) { + if (np->info->ivlist[i].value.vbp != NULL) + free(np->info->ivlist[i].value.vbp); + } + } + } + free(np->info->ivlist); + np->info->numval = 0; + np->info->ivlist = NULL; + } +} + +/* + * Binary arithmetic. + * + * result = <a> <op> <b> + * ltype, rtype and type are the types of <a>, <b> and the result + * respectively + * + * If type is PM_TYPE_DOUBLE then lmul, ldiv, rmul and rdiv are + * the scale factors for units scale conversion of <a> and <b> + * respectively, so lmul*<a>/ldiv ... all are 1 in the common cases. + */ +static pmAtomValue +bin_op(int type, int op, pmAtomValue a, int ltype, int lmul, int ldiv, pmAtomValue b, int rtype, int rmul, int rdiv) +{ + pmAtomValue res; + pmAtomValue l; + pmAtomValue r; + + l = a; /* struct assignments */ + r = b; + + /* + * Promote each operand to the type of the result ... there are limited + * cases to be considered here, see promote[][] and map_desc(). + */ + switch (type) { + case PM_TYPE_32: + case PM_TYPE_U32: + /* do nothing */ + break; + case PM_TYPE_64: + switch (ltype) { + case PM_TYPE_32: + l.ll = a.l; + break; + case PM_TYPE_U32: + l.ll = a.ul; + break; + case PM_TYPE_64: + case PM_TYPE_U64: + /* do nothing */ + break; + } + switch (rtype) { + case PM_TYPE_32: + r.ll = b.l; + break; + case PM_TYPE_U32: + r.ll = b.ul; + break; + case PM_TYPE_64: + case PM_TYPE_U64: + /* do nothing */ + break; + } + break; + case PM_TYPE_U64: + switch (ltype) { + case PM_TYPE_32: + l.ull = a.l; + break; + case PM_TYPE_U32: + l.ull = a.ul; + break; + case PM_TYPE_64: + case PM_TYPE_U64: + /* do nothing */ + break; + } + switch (rtype) { + case PM_TYPE_32: + r.ull = b.l; + break; + case PM_TYPE_U32: + r.ull = b.ul; + break; + case PM_TYPE_64: + case PM_TYPE_U64: + /* do nothing */ + break; + } + break; + case PM_TYPE_FLOAT: + switch (ltype) { + case PM_TYPE_32: + l.f = a.l; + break; + case PM_TYPE_U32: + l.f = a.ul; + break; + case PM_TYPE_64: + l.f = a.ll; + break; + case PM_TYPE_U64: + l.f = a.ull; + break; + case PM_TYPE_FLOAT: + /* do nothing */ + break; + } + switch (rtype) { + case PM_TYPE_32: + r.f = b.l; + break; + case PM_TYPE_U32: + r.f = b.ul; + break; + case PM_TYPE_64: + r.f = b.ll; + break; + case PM_TYPE_U64: + r.f = b.ull; + break; + case PM_TYPE_FLOAT: + /* do nothing */ + break; + } + break; + case PM_TYPE_DOUBLE: + switch (ltype) { + case PM_TYPE_32: + l.d = a.l; + break; + case PM_TYPE_U32: + l.d = a.ul; + break; + case PM_TYPE_64: + l.d = a.ll; + break; + case PM_TYPE_U64: + l.d = a.ull; + break; + case PM_TYPE_FLOAT: + l.d = a.f; + break; + case PM_TYPE_DOUBLE: + /* do nothing */ + break; + } + l.d = (l.d / ldiv) * lmul; + switch (rtype) { + case PM_TYPE_32: + r.d = b.l; + break; + case PM_TYPE_U32: + r.d = b.ul; + break; + case PM_TYPE_64: + r.d = b.ll; + break; + case PM_TYPE_U64: + r.d = b.ull; + break; + case PM_TYPE_FLOAT: + r.d = b.f; + break; + case PM_TYPE_DOUBLE: + /* do nothing */ + break; + } + r.d = (r.d / rdiv) * rmul; + break; + } + + /* + * Do the aritmetic ... messy! + */ + switch (type) { + case PM_TYPE_32: + switch (op) { + case L_PLUS: + res.l = l.l + r.l; + break; + case L_MINUS: + res.l = l.l - r.l; + break; + case L_STAR: + res.l = l.l * r.l; + break; + /* semantics enforce no L_SLASH for integer results */ + } + break; + case PM_TYPE_U32: + switch (op) { + case L_PLUS: + res.ul = l.ul + r.ul; + break; + case L_MINUS: + res.ul = l.ul - r.ul; + break; + case L_STAR: + res.ul = l.ul * r.ul; + break; + /* semantics enforce no L_SLASH for integer results */ + } + break; + case PM_TYPE_64: + switch (op) { + case L_PLUS: + res.ll = l.ll + r.ll; + break; + case L_MINUS: + res.ll = l.ll - r.ll; + break; + case L_STAR: + res.ll = l.ll * r.ll; + break; + /* semantics enforce no L_SLASH for integer results */ + } + break; + case PM_TYPE_U64: + switch (op) { + case L_PLUS: + res.ull = l.ull + r.ull; + break; + case L_MINUS: + res.ull = l.ull - r.ull; + break; + case L_STAR: + res.ull = l.ull * r.ull; + break; + /* semantics enforce no L_SLASH for integer results */ + } + break; + case PM_TYPE_FLOAT: + switch (op) { + case L_PLUS: + res.f = l.f + r.f; + break; + case L_MINUS: + res.f = l.f - r.f; + break; + case L_STAR: + res.f = l.f * r.f; + break; + /* semantics enforce no L_SLASH for float results */ + } + break; + case PM_TYPE_DOUBLE: + switch (op) { + case L_PLUS: + res.d = l.d + r.d; + break; + case L_MINUS: + res.d = l.d - r.d; + break; + case L_STAR: + res.d = l.d * r.d; + break; + case L_SLASH: + if (l.d == 0) + res.d = 0; + else + res.d = l.d / r.d; + break; + } + break; + } + + return res; +} + + +/* + * Walk an expression tree, filling in operand values from the + * pmResult at the leaf nodes and propagating the computed values + * towards the root node of the tree. + */ +static int +eval_expr(node_t *np, pmResult *rp, int level) +{ + int sts; + int i; + int j; + int k; + size_t need; + + assert(np != NULL); + if (np->left != NULL) { + sts = eval_expr(np->left, rp, level+1); + if (sts < 0) return sts; + } + if (np->right != NULL) { + sts = eval_expr(np->right, rp, level+1); + if (sts < 0) return sts; + } + + /* mostly, np->left is not NULL ... */ + assert (np->type == L_NUMBER || np->type == L_NAME || np->left != NULL); + + switch (np->type) { + + case L_NUMBER: + if (np->info->numval == 0) { + /* initialize ivlist[] for singular instance first time through */ + np->info->numval = 1; + if ((np->info->ivlist = (val_t *)malloc(sizeof(val_t))) == NULL) { + __pmNoMem("eval_expr: number ivlist", sizeof(val_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + np->info->ivlist[0].inst = PM_INDOM_NULL; + /* don't need error checking, done in the lexical scanner */ + np->info->ivlist[0].value.l = atoi(np->value); + } + return 1; + break; + + case L_DELTA: + case L_RATE: + /* + * this and the last values are in the left expr + */ + np->info->last_stamp = np->info->stamp; + np->info->stamp = rp->timestamp; + free_ivlist(np); + np->info->numval = np->left->info->numval <= np->left->info->last_numval ? np->left->info->numval : np->left->info->last_numval; + if (np->info->numval <= 0) + return np->info->numval; + if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) { + __pmNoMem("eval_expr: delta()/rate() ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + /* + * delta() + * ivlist[k] = left->ivlist[i] - left->last_ivlist[j] + * rate() + * ivlist[k] = (left->ivlist[i] - left->last_ivlist[j]) / + * (timestamp - left->last_stamp) + */ + for (i = k = 0; i < np->left->info->numval; i++) { + j = i; + if (j >= np->left->info->last_numval) + j = 0; + if (np->left->info->ivlist[i].inst != np->left->info->last_ivlist[j].inst) { + /* current ith inst != last jth inst ... search in last */ +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + fprintf(stderr, "eval_expr: inst[%d] mismatch left [%d]=%d last [%d]=%d\n", k, i, np->left->info->ivlist[i].inst, j, np->left->info->last_ivlist[j].inst); + } +#endif + for (j = 0; j < np->left->info->last_numval; j++) { + if (np->left->info->ivlist[i].inst == np->left->info->last_ivlist[j].inst) + break; + } + if (j == np->left->info->last_numval) { + /* no match, skip this instance from this result */ + continue; + } +#ifdef PCP_DEBUG + else { + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + fprintf(stderr, "eval_expr: recover @ last [%d]=%d\n", j, np->left->info->last_ivlist[j].inst); + } + } +#endif + } + np->info->ivlist[k].inst = np->left->info->ivlist[i].inst; + if (np->type == L_DELTA) { + /* for delta() result type == operand type */ + switch (np->left->desc.type) { + case PM_TYPE_32: + np->info->ivlist[k].value.l = np->left->info->ivlist[i].value.l - np->left->info->last_ivlist[j].value.l; + break; + case PM_TYPE_U32: + np->info->ivlist[k].value.ul = np->left->info->ivlist[i].value.ul - np->left->info->last_ivlist[j].value.ul; + break; + case PM_TYPE_64: + np->info->ivlist[k].value.ll = np->left->info->ivlist[i].value.ll - np->left->info->last_ivlist[j].value.ll; + break; + case PM_TYPE_U64: + np->info->ivlist[k].value.ull = np->left->info->ivlist[i].value.ull - np->left->info->last_ivlist[j].value.ull; + break; + case PM_TYPE_FLOAT: + np->info->ivlist[k].value.f = np->left->info->ivlist[i].value.f - np->left->info->last_ivlist[j].value.f; + break; + case PM_TYPE_DOUBLE: + np->info->ivlist[k].value.d = np->left->info->ivlist[i].value.d - np->left->info->last_ivlist[j].value.d; + break; + default: + /* + * Nothing should end up here as check_expr() checks + * for numeric data type at bind time + */ + return PM_ERR_CONV; + } + } + else { + /* rate() conversion, type will be DOUBLE */ + struct timeval stampdiff; + stampdiff.tv_sec = np->info->stamp.tv_sec - np->info->last_stamp.tv_sec; + stampdiff.tv_usec = np->info->stamp.tv_usec - np->info->last_stamp.tv_usec; + if (stampdiff.tv_usec < 0) { + stampdiff.tv_usec += 1000000; + stampdiff.tv_sec--; + } + switch (np->left->desc.type) { + case PM_TYPE_32: + np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.l - np->left->info->last_ivlist[j].value.l); + break; + case PM_TYPE_U32: + np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ul - np->left->info->last_ivlist[j].value.ul); + break; + case PM_TYPE_64: + np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ll - np->left->info->last_ivlist[j].value.ll); + break; + case PM_TYPE_U64: + np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ull - np->left->info->last_ivlist[j].value.ull); + break; + case PM_TYPE_FLOAT: + np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.f - np->left->info->last_ivlist[j].value.f); + break; + case PM_TYPE_DOUBLE: + np->info->ivlist[k].value.d = np->left->info->ivlist[i].value.d - np->left->info->last_ivlist[j].value.d; + break; + default: + /* + * Nothing should end up here as check_expr() checks + * for numeric data type at bind time + */ + return PM_ERR_CONV; + } + np->info->ivlist[k].value.d /= stampdiff.tv_sec + (double)stampdiff.tv_usec/1000000; + /* + * check_expr() ensures dimTime is 0 or 1 at bind time + */ + if (np->left->desc.units.dimTime == 1) { + /* scale rate(time counter) -> time utilization */ + if (np->info->time_scale < 0) { + /* + * one trip initialization for time utilization + * scaling factor (to scale metric from counter + * units into seconds) + */ + int i; + np->info->time_scale = 1; + if (np->left->desc.units.scaleTime > PM_TIME_SEC) { + + for (i = PM_TIME_SEC; i < np->left->desc.units.scaleTime; i++) + + np->info->time_scale *= 60; + } + else { + for (i = np->left->desc.units.scaleTime; i < PM_TIME_SEC; i++) + np->info->time_scale /= 1000; + } + } + np->info->ivlist[k].value.d *= np->info->time_scale; + } + } + k++; + } + np->info->numval = k; + return np->info->numval; + break; + + case L_AVG: + case L_COUNT: + case L_SUM: + case L_MAX: + case L_MIN: + if (np->info->ivlist == NULL) { + /* initialize ivlist[] for singular instance first time through */ + if ((np->info->ivlist = (val_t *)malloc(sizeof(val_t))) == NULL) { + __pmNoMem("eval_expr: aggr ivlist", sizeof(val_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + np->info->ivlist[0].inst = PM_IN_NULL; + } + /* + * values are in the left expr + */ + if (np->type == L_COUNT) { + np->info->numval = 1; + np->info->ivlist[0].value.l = np->left->info->numval; + } + else { + np->info->numval = 1; + if (np->type == L_AVG) + np->info->ivlist[0].value.f = 0; + else if (np->type == L_SUM) { + switch (np->desc.type) { + case PM_TYPE_32: + np->info->ivlist[0].value.l = 0; + break; + case PM_TYPE_U32: + np->info->ivlist[0].value.ul = 0; + break; + case PM_TYPE_64: + np->info->ivlist[0].value.ll = 0; + break; + case PM_TYPE_U64: + np->info->ivlist[0].value.ull = 0; + break; + case PM_TYPE_FLOAT: + np->info->ivlist[0].value.f = 0; + break; + case PM_TYPE_DOUBLE: + np->info->ivlist[0].value.d = 0; + break; + } + } + for (i = 0; i < np->left->info->numval; i++) { + switch (np->type) { + + case L_AVG: + switch (np->left->desc.type) { + case PM_TYPE_32: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.l / np->left->info->numval; + break; + case PM_TYPE_U32: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ul / np->left->info->numval; + break; + case PM_TYPE_64: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ll / np->left->info->numval; + break; + case PM_TYPE_U64: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ull / np->left->info->numval; + break; + case PM_TYPE_FLOAT: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.f / np->left->info->numval; + break; + case PM_TYPE_DOUBLE: + np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.d / np->left->info->numval; + break; + default: + /* + * check_expr() checks for numeric data + * type at bind time ... if here, botch! + */ + return PM_ERR_CONV; + } + break; + + case L_MAX: + switch (np->desc.type) { + case PM_TYPE_32: + if (i == 0 || + np->info->ivlist[0].value.l < np->left->info->ivlist[i].value.l) + np->info->ivlist[0].value.l = np->left->info->ivlist[i].value.l; + break; + case PM_TYPE_U32: + if (i == 0 || + np->info->ivlist[0].value.ul < np->left->info->ivlist[i].value.ul) + np->info->ivlist[0].value.ul = np->left->info->ivlist[i].value.ul; + break; + case PM_TYPE_64: + if (i == 0 || + np->info->ivlist[0].value.ll < np->left->info->ivlist[i].value.ll) + np->info->ivlist[0].value.ll = np->left->info->ivlist[i].value.ll; + break; + case PM_TYPE_U64: + if (i == 0 || + np->info->ivlist[0].value.ull < np->left->info->ivlist[i].value.ull) + np->info->ivlist[0].value.ull = np->left->info->ivlist[i].value.ull; + break; + case PM_TYPE_FLOAT: + if (i == 0 || + np->info->ivlist[0].value.f < np->left->info->ivlist[i].value.f) + np->info->ivlist[0].value.f = np->left->info->ivlist[i].value.f; + break; + case PM_TYPE_DOUBLE: + if (i == 0 || + np->info->ivlist[0].value.d < np->left->info->ivlist[i].value.d) + np->info->ivlist[0].value.d = np->left->info->ivlist[i].value.d; + break; + default: + /* + * check_expr() checks for numeric data + * type at bind time ... if here, botch! + */ + return PM_ERR_CONV; + } + break; + + case L_MIN: + switch (np->desc.type) { + case PM_TYPE_32: + if (i == 0 || + np->info->ivlist[0].value.l > np->left->info->ivlist[i].value.l) + np->info->ivlist[0].value.l = np->left->info->ivlist[i].value.l; + break; + case PM_TYPE_U32: + if (i == 0 || + np->info->ivlist[0].value.ul > np->left->info->ivlist[i].value.ul) + np->info->ivlist[0].value.ul = np->left->info->ivlist[i].value.ul; + break; + case PM_TYPE_64: + if (i == 0 || + np->info->ivlist[0].value.ll > np->left->info->ivlist[i].value.ll) + np->info->ivlist[0].value.ll = np->left->info->ivlist[i].value.ll; + break; + case PM_TYPE_U64: + if (i == 0 || + np->info->ivlist[0].value.ull > np->left->info->ivlist[i].value.ull) + np->info->ivlist[0].value.ull = np->left->info->ivlist[i].value.ull; + break; + case PM_TYPE_FLOAT: + if (i == 0 || + np->info->ivlist[0].value.f > np->left->info->ivlist[i].value.f) + np->info->ivlist[0].value.f = np->left->info->ivlist[i].value.f; + break; + case PM_TYPE_DOUBLE: + if (i == 0 || + np->info->ivlist[0].value.d > np->left->info->ivlist[i].value.d) + np->info->ivlist[0].value.d = np->left->info->ivlist[i].value.d; + break; + default: + /* + * check_expr() checks for numeric data + * type at bind time ... if here, botch! + */ + return PM_ERR_CONV; + } + break; + + case L_SUM: + switch (np->desc.type) { + case PM_TYPE_32: + np->info->ivlist[0].value.l += np->left->info->ivlist[i].value.l; + break; + case PM_TYPE_U32: + np->info->ivlist[0].value.ul += np->left->info->ivlist[i].value.ul; + break; + case PM_TYPE_64: + np->info->ivlist[0].value.ll += np->left->info->ivlist[i].value.ll; + break; + case PM_TYPE_U64: + np->info->ivlist[0].value.ull += np->left->info->ivlist[i].value.ull; + break; + case PM_TYPE_FLOAT: + np->info->ivlist[0].value.f += np->left->info->ivlist[i].value.f; + break; + case PM_TYPE_DOUBLE: + np->info->ivlist[0].value.d += np->left->info->ivlist[i].value.d; + break; + default: + /* + * check_expr() checks for numeric data + * type at bind time ... if here, botch! + */ + return PM_ERR_CONV; + } + break; + + } + } + } + return np->info->numval; + break; + + case L_NAME: + /* + * Extract instance-values from pmResult and store them in + * ivlist[] as <int, pmAtomValue> pairs + */ + for (j = 0; j < rp->numpmid; j++) { + if (np->info->pmid == rp->vset[j]->pmid) { + free_ivlist(np); + np->info->numval = rp->vset[j]->numval; + if (np->info->numval <= 0) + return np->info->numval; + if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) { + __pmNoMem("eval_expr: metric ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + for (i = 0; i < np->info->numval; i++) { + np->info->ivlist[i].inst = rp->vset[j]->vlist[i].inst; + switch (np->desc.type) { + case PM_TYPE_32: + case PM_TYPE_U32: + np->info->ivlist[i].value.l = rp->vset[j]->vlist[i].value.lval; + break; + + case PM_TYPE_64: + case PM_TYPE_U64: + memcpy((void *)&np->info->ivlist[i].value.ll, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(__int64_t)); + break; + + case PM_TYPE_FLOAT: + if (rp->vset[j]->valfmt == PM_VAL_INSITU) { + /* old style insitu float */ + np->info->ivlist[i].value.l = rp->vset[j]->vlist[i].value.lval; + } + else { + assert(rp->vset[j]->vlist[i].value.pval->vtype == PM_TYPE_FLOAT); + memcpy((void *)&np->info->ivlist[i].value.f, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(float)); + } + break; + + case PM_TYPE_DOUBLE: + memcpy((void *)&np->info->ivlist[i].value.d, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(double)); + break; + + case PM_TYPE_STRING: + need = rp->vset[j]->vlist[i].value.pval->vlen-PM_VAL_HDR_SIZE; + if ((np->info->ivlist[i].value.cp = (char *)malloc(need)) == NULL) { + __pmNoMem("eval_expr: string value", rp->vset[j]->vlist[i].value.pval->vlen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + memcpy((void *)np->info->ivlist[i].value.cp, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, need); + np->info->ivlist[i].vlen = need; + break; + + case PM_TYPE_AGGREGATE: + case PM_TYPE_AGGREGATE_STATIC: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + if ((np->info->ivlist[i].value.vbp = (pmValueBlock *)malloc(rp->vset[j]->vlist[i].value.pval->vlen)) == NULL) { + __pmNoMem("eval_expr: aggregate value", rp->vset[j]->vlist[i].value.pval->vlen, PM_FATAL_ERR); + /*NOTREACHED*/ + } + memcpy(np->info->ivlist[i].value.vbp, (void *)rp->vset[j]->vlist[i].value.pval, rp->vset[j]->vlist[i].value.pval->vlen); + np->info->ivlist[i].vlen = rp->vset[j]->vlist[i].value.pval->vlen; + break; + + default: + /* + * really only PM_TYPE_NOSUPPORT should + * end up here + */ + return PM_ERR_TYPE; + } + } + return np->info->numval; + } + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char strbuf[20]; + fprintf(stderr, "eval_expr: botch: operand %s not in the extended pmResult\n", pmIDStr_r(np->info->pmid, strbuf, sizeof(strbuf))); + __pmDumpResult(stderr, rp); + } +#endif + return PM_ERR_PMID; + + case L_ANON: + /* no values available for anonymous metrics */ + return 0; + + default: + /* + * binary operator cases ... always have a left and right + * operand and no errors (these are caught earlier when the + * recursive call on each of the operands would may have + * returned an error + */ + assert(np->left != NULL); + assert(np->right != NULL); + + free_ivlist(np); + /* + * empty result cases first + */ + if (np->left->info->numval == 0) { + np->info->numval = 0; + return np->info->numval; + } + if (np->right->info->numval == 0) { + np->info->numval = 0; + return np->info->numval; + } + /* + * really got some work to do ... + */ + if (np->left->desc.indom == PM_INDOM_NULL) + np->info->numval = np->right->info->numval; + else if (np->right->desc.indom == PM_INDOM_NULL) + np->info->numval = np->left->info->numval; + else { + /* + * Generally have the same number of instances because + * both operands are over the same instance domain, + * fetched with the same profile. When not the case, + * the result can contain no more instances than in + * the smaller of the operands. + */ + if (np->left->info->numval <= np->right->info->numval) + np->info->numval = np->left->info->numval; + else + np->info->numval = np->right->info->numval; + } + if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) { + __pmNoMem("eval_expr: expr ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + /* + * ivlist[k] = left-ivlist[i] <op> right-ivlist[j] + */ + for (i = j = k = 0; k < np->info->numval; ) { + if (i >= np->left->info->numval || j >= np->right->info->numval) { + /* run out of operand instances, quit */ + np->info->numval = k; + break; + } + if (np->left->desc.indom != PM_INDOM_NULL && + np->right->desc.indom != PM_INDOM_NULL) { + if (np->left->info->ivlist[i].inst != np->right->info->ivlist[j].inst) { + /* left ith inst != right jth inst ... search in right */ +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + fprintf(stderr, "eval_expr: inst[%d] mismatch left [%d]=%d right [%d]=%d\n", k, i, np->left->info->ivlist[i].inst, j, np->right->info->ivlist[j].inst); + } +#endif + for (j = 0; j < np->right->info->numval; j++) { + if (np->left->info->ivlist[i].inst == np->right->info->ivlist[j].inst) + break; + } + if (j == np->right->info->numval) { + /* + * no match, so next instance on left operand, + * and reset to start from first instance of + * right operand + */ + i++; + j = 0; + continue; + } +#ifdef PCP_DEBUG + else { + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + fprintf(stderr, "eval_expr: recover @ right [%d]=%d\n", j, np->right->info->ivlist[j].inst); + } + } +#endif + } + } + np->info->ivlist[k].value = + bin_op(np->desc.type, np->type, + np->left->info->ivlist[i].value, np->left->desc.type, np->left->info->mul_scale, np->left->info->div_scale, + np->right->info->ivlist[j].value, np->right->desc.type, np->right->info->mul_scale, np->right->info->div_scale); + if (np->left->desc.indom != PM_INDOM_NULL) + np->info->ivlist[k].inst = np->left->info->ivlist[i].inst; + else + np->info->ivlist[k].inst = np->right->info->ivlist[j].inst; + k++; + if (np->left->desc.indom != PM_INDOM_NULL) { + i++; + if (np->right->desc.indom != PM_INDOM_NULL) { + j++; + if (j >= np->right->info->numval) { + /* rescan if need be */ + j = 0; + } + } + } + else if (np->right->desc.indom != PM_INDOM_NULL) { + j++; + } + } + return np->info->numval; + } + /*NOTREACHED*/ +} + +/* + * Algorithm here is complicated by trying to re-write the pmResult. + * + * On entry the pmResult is likely to be built over a pinned PDU buffer, + * which means individual pmValueSets cannot be selectively replaced + * (this would come to tears badly in pmFreeResult() where as soon as + * one pmValueSet is found to be in a pinned PDU buffer it is assumed + * they are all so ... leaving a memory leak for any ones we'd modified + * here). + * + * So the only option is to COPY the pmResult, selectively replacing + * the pmValueSets for the derived metrics, and then calling + * pmFreeResult() to free the input structure and return the new one. + * + * In making the COPY it is critical that we reverse the algorithm + * used in pmFreeResult() so that a later call to pmFreeResult() will + * not cause a memory leak. + * This means ... + * - malloc() the pmResult (padded out to the right number of vset[] + * entries) + * - if valfmt is not PM_VAL_INSITU use PM_VAL_DPTR (not PM_VAL_SPTR), + * so anything we point to is going to be released when our caller + * calls pmFreeResult() + * - use one malloc() for each pmValueSet with vlist[] sized to be 0 + * if numval < 0 else numval + * - pmValueBlocks are from malloc() + * + * For reference, the same logic appears in __pmLogFetchInterp() to + * sythesize a pmResult there. + */ +void +__dmpostfetch(__pmContext *ctxp, pmResult **result) +{ + int i; + int j; + int m; + int numval; + int valfmt; + size_t need; + int rewrite; + ctl_t *cp = (ctl_t *)ctxp->c_dm; + pmResult *rp = *result; + pmResult *newrp; + + /* if needed, init() called in __dmopencontext beforehand */ + + if (cp == NULL || cp->fetch_has_dm == 0) return; + + newrp = (pmResult *)malloc(sizeof(pmResult)+(cp->numpmid-1)*sizeof(pmValueSet *)); + if (newrp == NULL) { + __pmNoMem("__dmpostfetch: newrp", sizeof(pmResult)+(cp->numpmid-1)*sizeof(pmValueSet *), PM_FATAL_ERR); + /*NOTREACHED*/ + } + newrp->timestamp = rp->timestamp; + newrp->numpmid = cp->numpmid; + + for (j = 0; j < newrp->numpmid; j++) { + numval = rp->vset[j]->numval; + valfmt = rp->vset[j]->valfmt; + rewrite = 0; + /* + * pandering to gcc ... m is not used unless rewrite == 1 in + * which case m is well-defined + */ + m = 0; + if (pmid_domain(rp->vset[j]->pmid) == DYNAMIC_PMID && + pmid_item(rp->vset[j]->pmid) != 0) { + for (m = 0; m < cp->nmetric; m++) { + if (rp->vset[j]->pmid == cp->mlist[m].pmid) { + if (cp->mlist[m].expr == NULL) { + numval = PM_ERR_PMID; + } + else { + rewrite = 1; + if (cp->mlist[m].expr->desc.type == PM_TYPE_32 || + cp->mlist[m].expr->desc.type == PM_TYPE_U32) + valfmt = PM_VAL_INSITU; + else + valfmt = PM_VAL_DPTR; + numval = eval_expr(cp->mlist[m].expr, rp, 1); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) { + int k; + char strbuf[20]; + + fprintf(stderr, "__dmpostfetch: [%d] root node %s: numval=%d", j, pmIDStr_r(rp->vset[j]->pmid, strbuf, sizeof(strbuf)), numval); + for (k = 0; k < numval; k++) { + fprintf(stderr, " vset[%d]: inst=%d", k, cp->mlist[m].expr->info->ivlist[k].inst); + if (cp->mlist[m].expr->desc.type == PM_TYPE_32) + fprintf(stderr, " l=%d", cp->mlist[m].expr->info->ivlist[k].value.l); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_U32) + fprintf(stderr, " u=%u", cp->mlist[m].expr->info->ivlist[k].value.ul); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_64) + fprintf(stderr, " ll=%"PRIi64, cp->mlist[m].expr->info->ivlist[k].value.ll); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_U64) + fprintf(stderr, " ul=%"PRIu64, cp->mlist[m].expr->info->ivlist[k].value.ull); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_FLOAT) + fprintf(stderr, " f=%f", (double)cp->mlist[m].expr->info->ivlist[k].value.f); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_DOUBLE) + fprintf(stderr, " d=%f", cp->mlist[m].expr->info->ivlist[k].value.d); + else if (cp->mlist[m].expr->desc.type == PM_TYPE_STRING) { + fprintf(stderr, " cp=%s (len=%d)", cp->mlist[m].expr->info->ivlist[k].value.cp, cp->mlist[m].expr->info->ivlist[k].vlen); + } + else { + fprintf(stderr, " vbp=" PRINTF_P_PFX "%p (len=%d)", cp->mlist[m].expr->info->ivlist[k].value.vbp, cp->mlist[m].expr->info->ivlist[k].vlen); + } + } + fputc('\n', stderr); + if (cp->mlist[m].expr->info != NULL) + __dmdumpexpr(cp->mlist[m].expr, 1); + } +#endif + } + break; + } + } + } + + if (numval <= 0) { + /* only need pmid and numval */ + need = sizeof(pmValueSet) - sizeof(pmValue); + } + else { + /* already one pmValue in a pmValueSet */ + need = sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue); + } + if (need > 0) { + if ((newrp->vset[j] = (pmValueSet *)malloc(need)) == NULL) { + __pmNoMem("__dmpostfetch: vset", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + } + newrp->vset[j]->pmid = rp->vset[j]->pmid; + newrp->vset[j]->numval = numval; + newrp->vset[j]->valfmt = valfmt; + if (numval < 0) + continue; + + for (i = 0; i < numval; i++) { + pmValueBlock *vp; + + if (!rewrite) { + newrp->vset[j]->vlist[i].inst = rp->vset[j]->vlist[i].inst; + if (newrp->vset[j]->valfmt == PM_VAL_INSITU) { + newrp->vset[j]->vlist[i].value.lval = rp->vset[j]->vlist[i].value.lval; + } + else { + need = rp->vset[j]->vlist[i].value.pval->vlen; + vp = (pmValueBlock *)malloc(need); + if (vp == NULL) { + __pmNoMem("__dmpostfetch: copy value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + memcpy((void *)vp, (void *)rp->vset[j]->vlist[i].value.pval, need); + newrp->vset[j]->vlist[i].value.pval = vp; + } + continue; + } + + /* + * the rewrite case ... + */ + newrp->vset[j]->vlist[i].inst = cp->mlist[m].expr->info->ivlist[i].inst; + switch (cp->mlist[m].expr->desc.type) { + case PM_TYPE_32: + case PM_TYPE_U32: + newrp->vset[j]->vlist[i].value.lval = cp->mlist[m].expr->info->ivlist[i].value.l; + break; + + case PM_TYPE_64: + case PM_TYPE_U64: + need = PM_VAL_HDR_SIZE + sizeof(__int64_t); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + __pmNoMem("__dmpostfetch: 64-bit int value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + vp->vlen = need; + vp->vtype = cp->mlist[m].expr->desc.type; + memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.ll, sizeof(__int64_t)); + newrp->vset[j]->vlist[i].value.pval = vp; + break; + + case PM_TYPE_FLOAT: + need = PM_VAL_HDR_SIZE + sizeof(float); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + __pmNoMem("__dmpostfetch: float value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + vp->vlen = need; + vp->vtype = PM_TYPE_FLOAT; + memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.f, sizeof(float)); + newrp->vset[j]->vlist[i].value.pval = vp; + break; + + case PM_TYPE_DOUBLE: + need = PM_VAL_HDR_SIZE + sizeof(double); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + __pmNoMem("__dmpostfetch: double value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + vp->vlen = need; + vp->vtype = PM_TYPE_DOUBLE; + memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.f, sizeof(double)); + newrp->vset[j]->vlist[i].value.pval = vp; + break; + + case PM_TYPE_STRING: + need = PM_VAL_HDR_SIZE + cp->mlist[m].expr->info->ivlist[i].vlen; + vp = (pmValueBlock *)malloc(need); + if (vp == NULL) { + __pmNoMem("__dmpostfetch: string value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + vp->vlen = need; + vp->vtype = cp->mlist[m].expr->desc.type; + memcpy((void *)vp->vbuf, cp->mlist[m].expr->info->ivlist[i].value.cp, cp->mlist[m].expr->info->ivlist[i].vlen); + newrp->vset[j]->vlist[i].value.pval = vp; + break; + + case PM_TYPE_AGGREGATE: + case PM_TYPE_AGGREGATE_STATIC: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + need = cp->mlist[m].expr->info->ivlist[i].vlen; + vp = (pmValueBlock *)malloc(need); + if (vp == NULL) { + __pmNoMem("__dmpostfetch: aggregate or event value", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + memcpy((void *)vp, cp->mlist[m].expr->info->ivlist[i].value.vbp, cp->mlist[m].expr->info->ivlist[i].vlen); + newrp->vset[j]->vlist[i].value.pval = vp; + break; + + default: + /* + * really nothing should end up here ... + * do nothing as numval should have been < 0 + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char strbuf[20]; + fprintf(stderr, "__dmpostfetch: botch: drived metric[%d]: operand %s has odd type (%d)\n", m, pmIDStr_r(rp->vset[j]->pmid, strbuf, sizeof(strbuf)), cp->mlist[m].expr->desc.type); + } +#endif + break; + } + } + } + + /* + * cull the original pmResult and return the rewritten one + */ + pmFreeResult(rp); + *result = newrp; + + return; +} diff --git a/src/libpcp/src/desc.c b/src/libpcp/src/desc.c new file mode 100644 index 0000000..d1dd624 --- /dev/null +++ b/src/libpcp/src/desc.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" + +int +pmLookupDesc(pmID pmid, pmDesc *desc) +{ + int n; + __pmContext *ctxp; + __pmPDU *pb; + + if ((n = pmWhichContext()) < 0) + goto done; + if ((ctxp = __pmHandleToPtr(n)) == NULL) { + n = PM_ERR_NOCONTEXT; + goto done; + } + + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_LOCK(ctxp->c_pmcd->pc_lock); + if ((n = __pmSendDescReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), pmid)) < 0) + n = __pmMapErrno(n); + else { + int pinpdu; + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_DESC) + n = __pmDecodeDesc(pb, desc); + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + int ctx = n; + __pmDSO *dp; + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + else if ((dp = __pmLookupDSO(((__pmID_int *)&pmid)->domain)) == NULL) + n = PM_ERR_NOAGENT; + else { + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + n = dp->dispatch.version.any.desc(pmid, desc, dp->dispatch.version.any.ext); + } + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + n = __pmLogLookupDesc(ctxp->c_archctl->ac_log, pmid, desc); + } + + if (n == PM_ERR_PMID || n == PM_ERR_PMID_LOG || n == PM_ERR_NOAGENT) { + int sts; + /* + * check for derived metric ... keep error status from above + * unless we have success with the derived metrics + */ + sts = __dmdesc(ctxp, pmid, desc); + if (sts >= 0) + n = sts; + } + PM_UNLOCK(ctxp->c_lock); + +done: + return n; +} + diff --git a/src/libpcp/src/discovery.c b/src/libpcp/src/discovery.c new file mode 100644 index 0000000..ec8c359 --- /dev/null +++ b/src/libpcp/src/discovery.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * + * 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. + */ +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include "avahi.h" +#include "probe.h" + +/* + * Advertise the given service using all available means. The implementation + * must support adding and removing individual service specs on the fly. + * e.g. "pmcd" on port 1234 + */ +__pmServerPresence * +__pmServerAdvertisePresence(const char *serviceSpec, int port) +{ + __pmServerPresence *s; + + /* Allocate a server presence and copy the given data. */ + if ((s = malloc(sizeof(*s))) == NULL) { + __pmNoMem("__pmServerAdvertisePresence: can't allocate __pmServerPresence", + sizeof(*s), PM_FATAL_ERR); + } + s->serviceSpec = strdup(serviceSpec); + if (s->serviceSpec == NULL) { + __pmNoMem("__pmServerAdvertisePresence: can't allocate service spec", + strlen(serviceSpec) + 1, PM_FATAL_ERR); + } + s->port = port; + + /* Now advertise our presence using all available means. If a particular + * method is not available or not configured, then the respective call + * will have no effect. Currently, only Avahi is supported. + */ + __pmServerAvahiAdvertisePresence(s); + return s; +} + +/* + * Unadvertise the given service using all available means. The implementation + * must support removing individual service specs on the fly. + * e.g. "pmcd" on port 1234 + */ +void +__pmServerUnadvertisePresence(__pmServerPresence *s) +{ + /* Unadvertise our presence for all available means. If a particular + * method is not active, then the respective call will have no effect. + */ + __pmServerAvahiUnadvertisePresence(s); + free(s->serviceSpec); + free(s); +} + +/* + * Service discovery API entry points. + */ +char * +__pmServiceDiscoveryParseTimeout (const char *s, struct timeval *timeout) +{ + double seconds; + char *end; + + /* + * The string is a floating point number representing the number of seconds + * to wait. Possibly followed by a comma, to separate the next option. + */ + seconds = strtod(s, &end); + if (*end != '\0' && *end != ',') { + __pmNotifyErr(LOG_ERR, "the timeout argument '%s' is not valid", s); + return strchrnul(s, ','); + } + + /* Set the specified timeout. */ + timeout->tv_sec = (long)seconds; + timeout->tv_usec = (long)((seconds - timeout->tv_sec) * 1000000); + + return end; +} + +static int +parseOptions(const char *optionsString, __pmServiceDiscoveryOptions *options) +{ + if (optionsString == NULL) + return 0; /* no options to parse */ + + /* Now interpret the options string. */ + while (*optionsString != '\0') { + if (strncmp(optionsString, "resolve", sizeof("resolve") - 1) == 0) + options->resolve = 1; + else if (strncmp(optionsString, "timeout=", sizeof("timeout=") - 1) == 0) { +#if ! PM_MULTI_THREAD + __pmNotifyErr(LOG_ERR, "__pmDiscoverServicesWithOptions: Service discovery global timeout is not supported"); + return -EOPNOTSUPP; +#else + optionsString += sizeof("timeout=") - 1; + optionsString = __pmServiceDiscoveryParseTimeout(optionsString, + &options->timeout); +#endif + } + else { + __pmNotifyErr(LOG_ERR, "__pmDiscoverServicesWithOptions: unrecognized option at '%s'", optionsString); + return -EINVAL; + } + /* Locate the start of the next option. */ + optionsString = strchrnul(optionsString, ','); + } + + return 0; /* ok */ +} + +#if PM_MULTI_THREAD +static void * +timeoutSleep(void *arg) +{ + __pmServiceDiscoveryOptions *options = arg; + int old; + + /* + * Make sure that this thread is cancellable. + * We don't need the previous state, but pthread_setcancelstate(3) says that + * passing in NULL as the second argument is not portable. + */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old); + + /* + * Sleep for the specified amount of time. Our thread will either be + * cancelled by the calling thread or we will wake up on our own. + */ + __pmtimevalSleep(options->timeout); + + /* + * Service discovery has timed out. It's ok to set this unconditionally + * since the object exists in the calling thread's memory space and it + * waits to join with our thread before finishing. + */ + options->timedOut = 1; + return NULL; +} +#endif + +int +pmDiscoverServices(const char *service, + const char *mechanism, + char ***urls) +{ + return __pmDiscoverServicesWithOptions(service, mechanism, NULL, NULL, urls); +} + +int +__pmDiscoverServicesWithOptions(const char *service, + const char *mechanism, + const char *optionsString, + const volatile unsigned *flags, + char ***urls) +{ + __pmServiceDiscoveryOptions options; + int numUrls; + int sts; +#if PM_MULTI_THREAD + pthread_t timeoutThread; + pthread_attr_t threadAttr; + int timeoutSet = 0; +#endif + + /* Interpret the options string. Initialize first. */ + memset(&options, 0, sizeof(options)); + sts = parseOptions(optionsString, &options); + if (sts < 0) + return sts; + options.flags = flags; + +#if PM_MULTI_THREAD + /* + * If a global timeout has been specified, then start a thread which will + * sleep for the specified length of time. When it wakes up, it will + * interrupt the discovery process. If discovery finishes before the + * timeout period, then the thread will be cancelled. + * We want the thread to be joinable. + */ + if (options.timeout.tv_sec || options.timeout.tv_usec) { + pthread_attr_init(&threadAttr); + pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_JOINABLE); + sts = pthread_create(&timeoutThread, &threadAttr, + timeoutSleep, &options); + pthread_attr_destroy(&threadAttr); + if (sts != 0) { + sts = oserror(); + __pmNotifyErr(LOG_ERR, "Service discovery global timeout could not be set: %s", + strerror(sts)); + return -sts; + } + timeoutSet = 1; + } +#endif + + /* + * Attempt to discover the requested service(s) using the requested or + * all available means. + * If a particular method is not available or not configured, then the + * respective call will have no effect. + */ + *urls = NULL; + numUrls = 0; + if (mechanism == NULL) { + /* + * Accumulate discovered services using all available mechanisms. + * Ensure that the return value from each mechanism is not an error + * code before adding it to numUrls. + */ + sts = __pmAvahiDiscoverServices(service, mechanism, &options, + numUrls, urls); + if (sts < 0) { + numUrls = sts; + goto done; + } + numUrls += sts; + if (! flags || (*flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0) { + sts = __pmProbeDiscoverServices(service, mechanism, &options, + numUrls, urls); + if (sts < 0) { + numUrls = sts; + goto done; + } + numUrls += sts; + } + } + else if (strncmp(mechanism, "avahi", 5) == 0) { + numUrls = __pmAvahiDiscoverServices(service, mechanism, &options, + numUrls, urls); + } + else if (strncmp(mechanism, "probe", 5) == 0) { + numUrls = __pmProbeDiscoverServices(service, mechanism, &options, + numUrls, urls); + } + else + numUrls = -EOPNOTSUPP; + + done: +#if PM_MULTI_THREAD + if (timeoutSet) { + /* Cancel the timeout thread and then wait for it to join. */ + pthread_cancel(timeoutThread); + pthread_join(timeoutThread, NULL); + } +#endif + + return numUrls; +} + +/* For manually adding a service. Also used by pmDiscoverServices(). */ +int +__pmAddDiscoveredService(__pmServiceInfo *info, + const __pmServiceDiscoveryOptions *options, + int numUrls, + char ***urls) +{ + const char *protocol = info->protocol; + char *host = NULL; + char *url; + size_t size; + int isIPv6; + int port; + + /* If address resolution was requested, then do attempt it. */ + if (options->resolve || + (options->flags && (*options->flags & PM_SERVICE_DISCOVERY_RESOLVE) != 0)) + host = __pmGetNameInfo(info->address); + + /* + * If address resolution was not requested, or if it failed, then + * just use the address. + */ + if (host == NULL) { + host = __pmSockAddrToString(info->address); + if (host == NULL) { + __pmNoMem("__pmAddDiscoveredService: can't allocate host buffer", + 0, PM_FATAL_ERR); + } + } + + /* + * Allocate the new entry. We need room for the URL prefix, the + * address/host and the port. IPv6 addresses require a set of [] + * surrounding the address in order to distinguish the port. + */ + port = __pmSockAddrGetPort(info->address); + size = strlen(protocol) + sizeof("://"); + size += strlen(host) + sizeof(":65535"); + if ((isIPv6 = (strchr(host, ':') != NULL))) + size += 2; + url = malloc(size); + if (url == NULL) { + __pmNoMem("__pmAddDiscoveredService: can't allocate new entry", + size, PM_FATAL_ERR); + } + if (isIPv6) + snprintf(url, size, "%s://[%s]:%u", protocol, host, port); + else + snprintf(url, size, "%s://%s:%u", protocol, host, port); + free(host); + + /* + * Now search the current list for the new entry. + * Add it if not found. We don't want any duplicates. + */ + if (__pmStringListFind(url, numUrls, *urls) == NULL) + numUrls = __pmStringListAdd(url, numUrls, urls); + + free(url); + return numUrls; +} diff --git a/src/libpcp/src/endian.c b/src/libpcp/src/endian.c new file mode 100644 index 0000000..a5782b6 --- /dev/null +++ b/src/libpcp/src/endian.c @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* + * Bit field typedefs for endian translations to support little endian + * hosts. + * + * For a typedef __X_foo, the big endian version will be "foo" or + * The only structures that appear here are ones that + * (a) may be encoded within a PDU, and/or + * (b) may appear in a PCP archive + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +#ifndef __htonpmUnits +pmUnits +__htonpmUnits(pmUnits units) +{ + unsigned int x; + + x = htonl(*(unsigned int *)&units); + units = *(pmUnits *)&x; + + return units; +} +#endif + +#ifndef __ntohpmUnits +pmUnits +__ntohpmUnits(pmUnits units) +{ + unsigned int x; + + x = ntohl(*(unsigned int *)&units); + units = *(pmUnits *)&x; + + return units; +} +#endif + +#ifndef __htonpmValueBlock +static void +htonEventArray(pmValueBlock * const vb, int highres) +{ + size_t size; + char *base; + int r; /* records */ + int p; /* parameters in a record ... */ + int nrecords; + int nparams; + int vtype; + int vlen; + __uint32_t *tp; /* points to int holding vtype/vlen */ + + /* ea_type and ea_len handled via *ip below */ + if (highres) { + pmHighResEventArray *hreap = (pmHighResEventArray *)vb; + base = (char *)&hreap->ea_record[0]; + nrecords = hreap->ea_nrecords; + hreap->ea_nrecords = htonl(nrecords); + } + else { + pmEventArray *eap = (pmEventArray *)vb; + base = (char *)&eap->ea_record[0]; + nrecords = eap->ea_nrecords; + eap->ea_nrecords = htonl(nrecords); + } + + /* walk packed event record array */ + for (r = 0; r < nrecords; r++) { + if (highres) { + pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base; + size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) + + sizeof(hrerp->er_nparams); + if (hrerp->er_flags & PM_EVENT_FLAG_MISSED) + nparams = 0; + else + nparams = hrerp->er_nparams; + hrerp->er_nparams = htonl(nparams); + hrerp->er_flags = htonl(hrerp->er_flags); + hrerp->er_timestamp.tv_sec = htonl(hrerp->er_timestamp.tv_sec); + hrerp->er_timestamp.tv_nsec = htonl(hrerp->er_timestamp.tv_nsec); + } + else { + pmEventRecord *erp = (pmEventRecord *)base; + size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + + sizeof(erp->er_nparams); + if (erp->er_flags & PM_EVENT_FLAG_MISSED) + nparams = 0; + else + nparams = erp->er_nparams; + erp->er_nparams = htonl(erp->er_nparams); + erp->er_flags = htonl(erp->er_flags); + erp->er_timestamp.tv_sec = htonl(erp->er_timestamp.tv_sec); + erp->er_timestamp.tv_usec = htonl(erp->er_timestamp.tv_usec); + } + base += size; + + for (p = 0; p < nparams; p++) { + pmEventParameter *epp = (pmEventParameter *)base; + + epp->ep_pmid = __htonpmID(epp->ep_pmid); + vtype = epp->ep_type; + vlen = epp->ep_len; + tp = (__uint32_t *)&epp->ep_pmid; + tp++; /* now points to ep_type/ep_len */ + *tp = htonl(*tp); + tp++; /* now points to vbuf */ + /* convert the types we're able to ... */ + switch (vtype) { + case PM_TYPE_32: + case PM_TYPE_U32: + *tp = htonl(*tp); + break; + case PM_TYPE_64: + case PM_TYPE_U64: + __htonll((void *)tp); + break; + case PM_TYPE_DOUBLE: + __htond((void *)tp); + break; + case PM_TYPE_FLOAT: + __htonf((void *)tp); + break; + } + base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(vlen); + } + } +} + +void +__htonpmValueBlock(pmValueBlock * const vb) +{ + unsigned int *ip = (unsigned int *)vb; + + if (vb->vtype == PM_TYPE_U64 || vb->vtype == PM_TYPE_64) + __htonll(vb->vbuf); + else if (vb->vtype == PM_TYPE_DOUBLE) + __htond(vb->vbuf); + else if (vb->vtype == PM_TYPE_FLOAT) + __htonf(vb->vbuf); + else if (vb->vtype == PM_TYPE_EVENT) + htonEventArray(vb, 0); + else if (vb->vtype == PM_TYPE_HIGHRES_EVENT) + htonEventArray(vb, 1); + + *ip = htonl(*ip); /* vtype/vlen */ +} +#endif + +#ifndef __ntohpmValueBlock +static void +ntohEventArray(pmValueBlock * const vb, int highres) +{ + char *base; + int r; /* records */ + int p; /* parameters in a record ... */ + int nrecords; + int nparams; + + /* ea_type and ea_len handled via *ip above */ + if (highres) { + pmHighResEventArray *hreap = (pmHighResEventArray *)vb; + base = (char *)&hreap->ea_record[0]; + nrecords = hreap->ea_nrecords = ntohl(hreap->ea_nrecords); + } + else { + pmEventArray *eap = (pmEventArray *)vb; + base = (char *)&eap->ea_record[0]; + nrecords = eap->ea_nrecords = ntohl(eap->ea_nrecords); + } + + /* walk packed event record array */ + for (r = 0; r < nrecords; r++) { + unsigned int flags; + size_t size; + + if (highres) { + pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base; + size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) + + sizeof(hrerp->er_nparams); + hrerp->er_timestamp.tv_sec = ntohl(hrerp->er_timestamp.tv_sec); + hrerp->er_timestamp.tv_nsec = ntohl(hrerp->er_timestamp.tv_nsec); + nparams = hrerp->er_nparams = ntohl(hrerp->er_nparams); + flags = hrerp->er_flags = ntohl(hrerp->er_flags); + } + else { + pmEventRecord *erp = (pmEventRecord *)base; + size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + + sizeof(erp->er_nparams); + erp->er_timestamp.tv_sec = ntohl(erp->er_timestamp.tv_sec); + erp->er_timestamp.tv_usec = ntohl(erp->er_timestamp.tv_usec); + nparams = erp->er_nparams = ntohl(erp->er_nparams); + flags = erp->er_flags = ntohl(erp->er_flags); + } + + if (flags & PM_EVENT_FLAG_MISSED) + nparams = 0; + + base += size; + for (p = 0; p < nparams; p++) { + __uint32_t *tp; /* points to int holding vtype/vlen */ + pmEventParameter *epp = (pmEventParameter *)base; + + epp->ep_pmid = __ntohpmID(epp->ep_pmid); + tp = (__uint32_t *)&epp->ep_pmid; + tp++; /* now points to ep_type/ep_len */ + *tp = ntohl(*tp); + tp++; /* now points to vbuf */ + /* convert the types we're able to ... */ + switch (epp->ep_type) { + case PM_TYPE_32: + case PM_TYPE_U32: + *tp = ntohl(*tp); + break; + case PM_TYPE_64: + case PM_TYPE_U64: + __ntohll((void *)tp); + break; + case PM_TYPE_DOUBLE: + __ntohd((void *)tp); + break; + case PM_TYPE_FLOAT: + __ntohf((void *)tp); + break; + } + base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + } + } +} + +void +__ntohpmValueBlock(pmValueBlock * const vb) +{ + unsigned int *ip = (unsigned int *)vb; + + /* Swab the first word, which contain vtype and vlen */ + *ip = ntohl(*ip); + + switch (vb->vtype) { + case PM_TYPE_U64: + case PM_TYPE_64: + __ntohll(vb->vbuf); + break; + + case PM_TYPE_DOUBLE: + __ntohd(vb->vbuf); + break; + + case PM_TYPE_FLOAT: + __ntohf(vb->vbuf); + break; + + case PM_TYPE_EVENT: + ntohEventArray(vb, 0); + break; + + case PM_TYPE_HIGHRES_EVENT: + ntohEventArray(vb, 1); + break; + } +} +#endif + +#ifndef __htonpmPDUInfo +__pmPDUInfo +__htonpmPDUInfo(__pmPDUInfo info) +{ + unsigned int x; + + x = htonl(*(unsigned int *)&info); + info = *(__pmPDUInfo *)&x; + + return info; +} +#endif + +#ifndef __ntohpmPDUInfo +__pmPDUInfo +__ntohpmPDUInfo(__pmPDUInfo info) +{ + unsigned int x; + + x = ntohl(*(unsigned int *)&info); + info = *(__pmPDUInfo *)&x; + + return info; +} +#endif + +#ifndef __htonpmCred +__pmCred +__htonpmCred(__pmCred cred) +{ + unsigned int x; + + x = htonl(*(unsigned int *)&cred); + cred = *(__pmCred *)&x; + + return cred; +} +#endif + +#ifndef __ntohpmCred +__pmCred +__ntohpmCred(__pmCred cred) +{ + unsigned int x; + + x = ntohl(*(unsigned int *)&cred); + cred = *(__pmCred *)&x; + + return cred; +} +#endif + + +#ifndef __htonf +void +__htonf(char *p) +{ + char c; + int i; + + for (i = 0; i < 2; i++) { + c = p[i]; + p[i] = p[3-i]; + p[3-i] = c; + } +} +#endif + +#ifndef __htonll +void +__htonll(char *p) +{ + char c; + int i; + + for (i = 0; i < 4; i++) { + c = p[i]; + p[i] = p[7-i]; + p[7-i] = c; + } +} +#endif diff --git a/src/libpcp/src/err.c b/src/libpcp/src/err.c new file mode 100644 index 0000000..614837d --- /dev/null +++ b/src/libpcp/src/err.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "fault.h" +#include <ctype.h> +#ifdef HAVE_SECURE_SOCKETS +#include <prerror.h> +#include <secerr.h> +#include <sslerr.h> +#include <sasl.h> +#endif +#ifdef IS_MINGW +extern const char *strerror_r(int, char *, size_t); +#endif + +/* + * if you modify this table at all, be sure to remake qa/006 + */ +static const struct { + int err; + char *symb; + char *errmess; +} errtab[] = { + { PM_ERR_GENERIC, "PM_ERR_GENERIC", + "Generic error, already reported above" }, + { PM_ERR_PMNS, "PM_ERR_PMNS", + "Problems parsing PMNS definitions" }, + { PM_ERR_NOPMNS, "PM_ERR_NOPMNS", + "PMNS not accessible" }, + { PM_ERR_DUPPMNS, "PM_ERR_DUPPMNS", + "Attempt to reload the PMNS" }, + { PM_ERR_TEXT, "PM_ERR_TEXT", + "One-line or help text is not available" }, + { PM_ERR_APPVERSION, "PM_ERR_APPVERSION", + "Metric not supported by this version of monitored application" }, + { PM_ERR_VALUE, "PM_ERR_VALUE", + "Missing metric value(s)" }, + { PM_ERR_TIMEOUT, "PM_ERR_TIMEOUT", + "Timeout waiting for a response from PMCD" }, + { PM_ERR_NODATA, "PM_ERR_NODATA", + "Empty archive log file" }, + { PM_ERR_RESET, "PM_ERR_RESET", + "PMCD reset or configuration change" }, + { PM_ERR_NAME, "PM_ERR_NAME", + "Unknown metric name" }, + { PM_ERR_PMID, "PM_ERR_PMID", + "Unknown or illegal metric identifier" }, + { PM_ERR_INDOM, "PM_ERR_INDOM", + "Unknown or illegal instance domain identifier" }, + { PM_ERR_INST, "PM_ERR_INST", + "Unknown or illegal instance identifier" }, + { PM_ERR_TYPE, "PM_ERR_TYPE", + "Unknown or illegal metric type" }, + { PM_ERR_UNIT, "PM_ERR_UNIT", + "Illegal pmUnits specification" }, + { PM_ERR_CONV, "PM_ERR_CONV", + "Impossible value or scale conversion" }, + { PM_ERR_TRUNC, "PM_ERR_TRUNC", + "Truncation in value conversion" }, + { PM_ERR_SIGN, "PM_ERR_SIGN", + "Negative value in conversion to unsigned" }, + { PM_ERR_PROFILE, "PM_ERR_PROFILE", + "Explicit instance identifier(s) required" }, + { PM_ERR_IPC, "PM_ERR_IPC", + "IPC protocol failure" }, + { PM_ERR_EOF, "PM_ERR_EOF", + "IPC channel closed" }, + { PM_ERR_NOTHOST, "PM_ERR_NOTHOST", + "Operation requires context with host source of metrics" }, + { PM_ERR_EOL, "PM_ERR_EOL", + "End of PCP archive log" }, + { PM_ERR_MODE, "PM_ERR_MODE", + "Illegal mode specification" }, + { PM_ERR_LABEL, "PM_ERR_LABEL", + "Illegal label record at start of a PCP archive log file" }, + { PM_ERR_LOGREC, "PM_ERR_LOGREC", + "Corrupted record in a PCP archive log" }, + { PM_ERR_LOGFILE, "PM_ERR_LOGFILE", + "Missing PCP archive log file" }, + { PM_ERR_NOTARCHIVE, "PM_ERR_NOTARCHIVE", + "Operation requires context with archive source of metrics" }, + { PM_ERR_NOCONTEXT, "PM_ERR_NOCONTEXT", + "Attempt to use an illegal context" }, + { PM_ERR_PROFILESPEC, "PM_ERR_PROFILESPEC", + "NULL pmInDom with non-NULL instlist" }, + { PM_ERR_PMID_LOG, "PM_ERR_PMID_LOG", + "Metric not defined in the PCP archive log" }, + { PM_ERR_INDOM_LOG, "PM_ERR_INDOM_LOG", + "Instance domain identifier not defined in the PCP archive log" }, + { PM_ERR_INST_LOG, "PM_ERR_INST_LOG", + "Instance identifier not defined in the PCP archive log" }, + { PM_ERR_NOPROFILE, "PM_ERR_NOPROFILE", + "Missing profile - protocol botch" }, + { PM_ERR_NOAGENT, "PM_ERR_NOAGENT", + "No PMCD agent for domain of request" }, + { PM_ERR_PERMISSION, "PM_ERR_PERMISSION", + "No permission to perform requested operation" }, + { PM_ERR_CONNLIMIT, "PM_ERR_CONNLIMIT", + "PMCD connection limit for this host exceeded" }, + { PM_ERR_AGAIN, "PM_ERR_AGAIN", + "Try again. Information not currently available" }, + { PM_ERR_ISCONN, "PM_ERR_ISCONN", + "Already Connected" }, + { PM_ERR_NOTCONN, "PM_ERR_NOTCONN", + "Not Connected" }, + { PM_ERR_NEEDPORT, "PM_ERR_NEEDPORT", + "A non-null port name is required" }, + { PM_ERR_NONLEAF, "PM_ERR_NONLEAF", + "Metric name is not a leaf in PMNS" }, + { PM_ERR_PMDANOTREADY, "PM_ERR_PMDANOTREADY", + "PMDA is not yet ready to respond to requests" }, + { PM_ERR_PMDAREADY, "PM_ERR_PMDAREADY", + "PMDA is now responsive to requests" }, + { PM_ERR_TOOSMALL, "PM_ERR_TOOSMALL", + "Insufficient elements in list" }, + { PM_ERR_TOOBIG, "PM_ERR_TOOBIG", + "Result size exceeded" }, + { PM_ERR_FAULT, "PM_ERR_FAULT", + "QA fault injected" }, + { PM_ERR_THREAD, "PM_ERR_THREAD", + "Operation not supported for multi-threaded applications" }, + /* insert new libpcp error codes here */ + { PM_ERR_NYI, "PM_ERR_NYI", + "Functionality not yet implemented" }, + /* do not use values smaller than NYI */ + { 0, "", + "" } +}; + +#define BADCODE "No such PMAPI error code (%d)" + +#ifndef IS_MINGW +/* + * handle non-determinism in the GNU implementation of strerror_r() + */ +static void +strerror_x(int code, char *buf, int buflen) +{ +#ifdef HAVE_STRERROR_R_PTR + char *p; + p = strerror_r(code, buf, buflen); + if (p != buf) + strncpy(buf, p, buflen); +#else + /* + * the more normal POSIX and XSI compliant variants always fill buf[] + */ + strerror_r(code, buf, buflen); +#endif +} +#endif + +char * +pmErrStr_r(int code, char *buf, int buflen) +{ + int i; +#ifndef IS_MINGW + static int first = 1; + static char *unknown = NULL; +#else + static char unknown[] = "Unknown error"; +#endif + + if (code == 0) { + strncpy(buf, "No error", buflen); + return buf; + } + + /* + * Is the code from a library wrapped by libpcp? (e.g. NSS/SSL/SASL) + * By good fortune, these libraries are using error codes that do not + * overlap - by design for NSS/SSL/NSPR, and by sheer luck with SASL. + */ + if (code < PM_ERR_NYI) { +#ifdef HAVE_SECURE_SOCKETS +#define DECODE_SECURE_SOCKETS_ERROR(c) ((c) - PM_ERR_NYI) /* negative */ +#define DECODE_SASL_SPECIFIC_ERROR(c) ((c) < -1000 ? 0 : (c)) + + int error = DECODE_SECURE_SOCKETS_ERROR(code); + if (DECODE_SASL_SPECIFIC_ERROR(error)) + snprintf(buf, buflen, "Authentication - %s", sasl_errstring(error, NULL, NULL)); + else + strncpy(buf, PR_ErrorToString(error, PR_LANGUAGE_EN), buflen); + buf[buflen-1] = '\0'; + return buf; +#endif + } + +#ifndef IS_MINGW + if (first) { + /* + * reference message for an unrecognized error code. + * For IRIX, strerror() returns NULL in this case. + */ + strerror_x(-1, buf, buflen); + if (buf[0] != '\0') { + /* + * For Linux et al, strip the last word, expected to be the + * error number as in ... + * Unknown error -1 + * or + * Unknown error 4294967295 + */ + char *sp = strrchr(buf, ' '); + char *p; + + if (sp != NULL) { + sp++; + if (*sp == '-') sp++; + for (p = sp; *p != '\0'; p++) { + if (!isdigit((int)*p)) break; + } + + if (*p == '\0') { +PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC); + *sp = '\0'; + if ((unknown = strdup(buf)) != NULL) + unknown[sp - buf] = '\0'; + } + } + } + first = 0; + } + if (code < 0 && code > -PM_ERR_BASE) { + /* intro(2) / errno(3) errors, maybe */ + strerror_x(-code, buf, buflen); + if (unknown == NULL) { + if (buf[0] != '\0') + return buf; + } + else { + /* The intention here is to catch variants of "Unknown + * error XXX" - in this case we're going to fail the + * stncmp() below, fall through and return a pcp error + * message, otherwise return the system error message + */ + if (strncmp(buf, unknown, strlen(unknown)) != 0) + return buf; + } + } +#else /* WIN32 */ + if (code > -PM_ERR_BASE || code < -PM_ERR_NYI) { + const char *bp; + if ((bp = wsastrerror(-code)) != NULL) + strncpy(buf, bp, buflen); + else { + /* No strerror_r in MinGW, so need to lock */ + char *tbp; + PM_LOCK(__pmLock_libpcp); + tbp = strerror(-code); + strncpy(buf, tbp, buflen); + PM_UNLOCK(__pmLock_libpcp); + } + + if (strncmp(buf, unknown, strlen(unknown)) != 0) + return buf; + } +#endif + + for (i = 0; errtab[i].err; i++) { + if (errtab[i].err == code) { + strncpy(buf, errtab[i].errmess, buflen); + return buf; + } + } + + /* failure */ + snprintf(buf, buflen, BADCODE, code); + return buf; +} + +char * +pmErrStr(int code) +{ + static char errmsg[PM_MAXERRMSGLEN]; + pmErrStr_r(code, errmsg, sizeof(errmsg)); + return errmsg; +} + +void +__pmDumpErrTab(FILE *f) +{ + int i; + + fprintf(f, " Code Symbolic Name Message\n"); + for (i = 0; errtab[i].err; i++) + fprintf(f, "%6d %-20s %s\n", + errtab[i].err, errtab[i].symb, errtab[i].errmess); +} diff --git a/src/libpcp/src/events.c b/src/libpcp/src/events.c new file mode 100644 index 0000000..c1049f8 --- /dev/null +++ b/src/libpcp/src/events.c @@ -0,0 +1,735 @@ +/* + * Unpack an array of event records + * Free space from unpack + * + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2010 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * The initialization of pmid_flags and pmid_missed both have a potential + * race, but there are no side-effects and the end result will be the + * same, so no locking is required. + * + */ +#include <inttypes.h> +#include "pmapi.h" +#include "impl.h" +#include "fault.h" + +static void +dump_count(FILE *f, size_t length, int nrecords) +{ + if (length < PM_VAL_HDR_SIZE + sizeof(int)) { + fprintf(f, "Error: bad len (smaller than minimum size %lu)\n", + (unsigned long)PM_VAL_HDR_SIZE + sizeof(int)); + return; + } + fprintf(f, "nrecords: %d\n", nrecords); + if (nrecords < 0) { + fprintf(f, "Error: bad nrecords\n"); + return; + } + if (nrecords == 0) { + fprintf(f, "Warning: no event records\n"); + return; + } +} + +static int +dump_flags(FILE *f, unsigned int flags, int nparams) +{ + if (flags != 0) + fprintf(f, " flags=%x", flags); + if (flags & PM_EVENT_FLAG_MISSED) { + fprintf(f, "\n ==> %d missed records", nparams); + if (flags != PM_EVENT_FLAG_MISSED) + fprintf(f, " (Warning: extra flags %x ignored)", + flags & (~PM_EVENT_FLAG_MISSED)); + fputc('\n', f); + return 1; + } + return 0; +} + +static void +dump_parameter(FILE *f, pmEventParameter *epp) +{ + pmAtomValue atom; + char strbuf[20]; + char *vbuf; + char *name; + + if (pmNameID(epp->ep_pmid, &name) == 0) { + fprintf(f, " %s", name); + free(name); + } else { + fprintf(f, " %s", pmIDStr_r(epp->ep_pmid, strbuf, sizeof(strbuf))); + } + + vbuf = (char *)epp + sizeof(epp->ep_pmid) + sizeof(int); + switch (epp->ep_type) { + case PM_TYPE_32: + fprintf(f, " = %i", *((__int32_t *)vbuf)); + break; + case PM_TYPE_U32: + fprintf(f, " = %u", *((__uint32_t *)vbuf)); + break; + case PM_TYPE_64: + memcpy((void *)&atom.ll, (void *)vbuf, sizeof(atom.ll)); + fprintf(f, " = %"PRIi64, atom.ll); + break; + case PM_TYPE_U64: + memcpy((void *)&atom.ull, (void *)vbuf, sizeof(atom.ull)); + fprintf(f, " = %"PRIu64, atom.ull); + break; + case PM_TYPE_FLOAT: + memcpy((void *)&atom.f, (void *)vbuf, sizeof(atom.f)); + fprintf(f, " = %.8g", (double)atom.f); + break; + case PM_TYPE_DOUBLE: + memcpy((void *)&atom.d, (void *)vbuf, sizeof(atom.d)); + fprintf(f, " = %.16g", atom.d); + break; + case PM_TYPE_STRING: + fprintf(f, " = \"%*.*s\"", epp->ep_len-PM_VAL_HDR_SIZE, + epp->ep_len-PM_VAL_HDR_SIZE, vbuf); + break; + case PM_TYPE_AGGREGATE: + case PM_TYPE_AGGREGATE_STATIC: + fprintf(f, " = [%08x...]", ((__uint32_t *)vbuf)[0]); + break; + default: + fprintf(f, " : bad type %s", + pmTypeStr_r(epp->ep_type, strbuf, sizeof(strbuf))); + } + fputc('\n', f); +} + +/* + * Dump a packed array of event records ... need to be paranoid + * with checking here, because typically called after + * __pmCheck[HighRes]EventRecords() finds an error. + * Process the idx'th instance. + */ +void +dump_event_records(FILE *f, pmValueSet *vsp, int idx, int highres) +{ + char *base; + char *valend; /* end of the value */ + char strbuf[20]; + size_t length; + int nrecords; + int nparams; + int r; /* records index */ + int p; /* parameters in a record ... */ + + fprintf(f, "Event Records Dump ...\n"); + fprintf(f, "PMID: %s numval: %d", + pmIDStr_r(vsp->pmid, strbuf, sizeof(strbuf)), vsp->numval); + if (vsp->numval <= 0) { + fprintf(f, "\nError: bad numval\n"); + return; + } + fprintf(f, " valfmt: %d", vsp->valfmt); + if (vsp->valfmt != PM_VAL_DPTR && vsp->valfmt != PM_VAL_SPTR) { + fprintf(f, "\nError: bad valfmt\n"); + return; + } + if (vsp->vlist[idx].inst != PM_IN_NULL) + fprintf(f, " inst: %d", vsp->vlist[idx].inst); + + if (highres) { + pmHighResEventArray *hreap; + + hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval; + fprintf(f, " vtype: %s vlen: %d\n", + pmTypeStr_r(hreap->ea_type, strbuf, sizeof(strbuf)), + hreap->ea_len); + if (hreap->ea_type != PM_TYPE_HIGHRES_EVENT) { + fprintf(f, "Error: bad highres vtype\n"); + return; + } + length = hreap->ea_len; + nrecords = hreap->ea_nrecords; + valend = &((char *)hreap)[length]; + base = (char *)&hreap->ea_record[0]; + } + else { + pmEventArray *eap; + + eap = (pmEventArray *)vsp->vlist[idx].value.pval; + fprintf(f, " vtype: %s vlen: %d\n", + pmTypeStr_r(eap->ea_type, strbuf, sizeof(strbuf)), eap->ea_len); + if (eap->ea_type != PM_TYPE_EVENT) { + fprintf(f, "Error: bad vtype\n"); + return; + } + length = eap->ea_len; + nrecords = eap->ea_nrecords; + valend = &((char *)eap)[length]; + base = (char *)&eap->ea_record[0]; + } + dump_count(f, length, nrecords); + + for (r = 0; r < nrecords; r++) { + pmEventParameter *epp; + unsigned int flags; + size_t size; + + fprintf(f, "Event Record [%d]", r); + + if (highres) { + pmHighResEventRecord *herp = (pmHighResEventRecord *)base; + + size = sizeof(herp->er_timestamp) + sizeof(herp->er_flags) + + sizeof(herp->er_nparams); + if (base + size > valend) { + fprintf(f, " Error: buffer overflow\n"); + return; + } + flags = herp->er_flags; + nparams = herp->er_nparams; + } else { + pmEventRecord *erp = (pmEventRecord *)base; + + size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + + sizeof(erp->er_nparams); + if (base + size > valend) { + fprintf(f, " Error: buffer overflow\n"); + return; + } + flags = erp->er_flags; + nparams = erp->er_nparams; + } + base += size; + + if (dump_flags(f, flags, nparams) != 0) + continue; + + fprintf(f, " with %d parameters\n", nparams); + for (p = 0; p < nparams; p++) { + fprintf(f, " Parameter [%d]:", p); + if (base + sizeof(pmEventParameter) > valend) { + fprintf(f, " Error: buffer overflow\n"); + return; + } + epp = (pmEventParameter *)base; + size = sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + if (base + size > valend) { + fprintf(f, " Error: buffer overflow\n"); + return; + } + dump_parameter(f, epp); + base += size; + } + } +} + +void +__pmDumpEventRecords(FILE *f, pmValueSet *vsp, int idx) +{ + dump_event_records(f, vsp, idx, 0); +} + +void +__pmDumpHighResEventRecords(FILE *f, pmValueSet *vsp, int idx) +{ + dump_event_records(f, vsp, idx, 1); +} + +/* + * Integrity checker for a packed array of event records, check + * the idx'th instance. + */ +int +check_event_records(pmValueSet *vsp, int idx, int highres) +{ + char *base; + char *valend; /* end of the value */ + pmEventParameter *epp; + int nrecords; + int nparams; + int r; /* records */ + int p; /* parameters in a record ... */ + + if (vsp->numval < 1) + return vsp->numval; + if (vsp->valfmt != PM_VAL_DPTR && vsp->valfmt != PM_VAL_SPTR) + return PM_ERR_CONV; + + if (highres) { + pmHighResEventArray *hreap; + + hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval; + if (hreap->ea_type != PM_TYPE_HIGHRES_EVENT) + return PM_ERR_TYPE; + if (hreap->ea_len < PM_VAL_HDR_SIZE + sizeof(int)) + return PM_ERR_TOOSMALL; + nrecords = hreap->ea_nrecords; + base = (char *)&hreap->ea_record[0]; + valend = &((char *)hreap)[hreap->ea_len]; + } + else { + pmEventArray *eap; + + eap = (pmEventArray *)vsp->vlist[idx].value.pval; + if (eap->ea_type != PM_TYPE_EVENT) + return PM_ERR_TYPE; + if (eap->ea_len < PM_VAL_HDR_SIZE + sizeof(eap->ea_nrecords)) + return PM_ERR_TOOSMALL; + nrecords = eap->ea_nrecords; + base = (char *)&eap->ea_record[0]; + valend = &((char *)eap)[eap->ea_len]; + } + if (nrecords < 0) + return PM_ERR_TOOSMALL; + + /* header seems OK, onto each event record */ + for (r = 0; r < nrecords; r++) { + unsigned int flags; + size_t size; + + if (highres) { + pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base; + + size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) + + sizeof(hrerp->er_nparams); + if (base + size > valend) + return PM_ERR_TOOBIG; + flags = hrerp->er_flags; + nparams = hrerp->er_nparams; + } + else { + pmEventRecord *erp = (pmEventRecord *)base; + + size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + + sizeof(erp->er_nparams); + if (base + size > valend) + return PM_ERR_TOOBIG; + flags = erp->er_flags; + nparams = erp->er_nparams; + } + base += size; + + if (flags & PM_EVENT_FLAG_MISSED) { + if (flags == PM_EVENT_FLAG_MISSED) + nparams = 0; + else { + /* + * not legal to have other flag bits set when + * PM_EVENT_FLAG_MISSED is set + */ + return PM_ERR_CONV; + } + } + + for (p = 0; p < nparams; p++) { + if (base + sizeof(pmEventParameter) > valend) + return PM_ERR_TOOBIG; + epp = (pmEventParameter *)base; + size = sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + if (base + size > valend) + return PM_ERR_TOOBIG; + base += size; + } + } + return 0; +} + +int +__pmCheckEventRecords(pmValueSet *vsp, int idx) +{ + return check_event_records(vsp, idx, 0); +} + +int +__pmCheckHighResEventRecords(pmValueSet *vsp, int idx) +{ + return check_event_records(vsp, idx, 1); +} + + static char *name_flags = "event.flags"; + static char *name_missed = "event.missed"; + +static int +register_event_metrics(const char *caller) +{ + static int first = 1; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (first) { + int sts; + +PM_FAULT_POINT("libpcp/" __FILE__ ":5", PM_FAULT_PMAPI); + if (first == 1) { + sts = __pmRegisterAnon(name_flags, PM_TYPE_U32); + if (sts < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s: Warning: failed to register %s: %s\n", + caller, name_flags, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + first = 2; + } + +PM_FAULT_POINT("libpcp/" __FILE__ ":6", PM_FAULT_PMAPI); + sts = __pmRegisterAnon(name_missed, PM_TYPE_U32); + if (sts < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s: Warning: failed to register %s: %s\n", + caller, name_missed, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + first = 0; + } + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +/* + * flags is optionally unpacked into an extra anon events.flags metric + * before all the event record parameters, and for PM_EVENT_FLAG_MISSED + * nparams is a count of the missed records. + */ +static int +count_event_parameters(unsigned int flags, int nparams) +{ + if (flags == 0) + return nparams; + else if (flags & PM_EVENT_FLAG_MISSED) + return 2; + return nparams + 1; +} + +static int +add_event_parameter(const char *caller, pmEventParameter *epp, int idx, + unsigned int flags, int nparams, pmValueSet **vsetp) +{ + pmValueSet *vset; + char *vbuf; + char errmsg[PM_MAXERRMSGLEN]; + int sts; + int need; + int want; + int vsize; + + /* always have numval == 1 */ +PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC); + if ((vset = (pmValueSet *)malloc(sizeof(pmValueSet))) == NULL) + return -oserror(); + + if (idx == 0 && flags != 0) { + /* rewrite non-zero er_flags as the anon event.flags metric */ + static pmID pmid_flags = 0; + + if (pmid_flags == 0) { + if ((sts = pmLookupName(1, &name_flags, &pmid_flags)) < 0) { + fprintf(stderr, "%s: Warning: failed to get PMID for %s: %s\n", + caller, name_flags, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + __pmid_int(&pmid_flags)->item = 1; + } + } + vset->pmid = pmid_flags; + vset->numval = 1; + vset->vlist[0].inst = PM_IN_NULL; + vset->valfmt = PM_VAL_INSITU; + vset->vlist[0].value.lval = flags; + *vsetp = vset; + return 1; + } + if (idx == 1 && flags & PM_EVENT_FLAG_MISSED) { + /* rewrite missed count as the anon event.missed metric */ + static pmID pmid_missed = 0; + + if (pmid_missed == 0) { + if ((sts = pmLookupName(1, &name_missed, &pmid_missed)) < 0) { + fprintf(stderr, "%s: Warning: failed to get PMID for %s: %s\n", + caller, name_missed, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + __pmid_int(&pmid_missed)->item = 1; + } + } + vset->pmid = pmid_missed; + vset->numval = 1; + vset->vlist[0].inst = PM_IN_NULL; + vset->valfmt = PM_VAL_INSITU; + vset->vlist[0].value.lval = nparams; + *vsetp = vset; + return 2; + } + + vset->pmid = epp->ep_pmid; + vset->numval = 1; + vset->vlist[0].inst = PM_IN_NULL; + vbuf = (char *)epp + sizeof(epp->ep_pmid) + sizeof(int); + switch (epp->ep_type) { + case PM_TYPE_32: + case PM_TYPE_U32: + vset->valfmt = PM_VAL_INSITU; + memcpy((void *)&vset->vlist[0].value.lval, (void *)vbuf, sizeof(__int32_t)); + *vsetp = vset; + return 0; + + case PM_TYPE_64: + case PM_TYPE_U64: + vsize = sizeof(__int64_t); + break; + case PM_TYPE_FLOAT: + vsize = sizeof(float); + break; + case PM_TYPE_DOUBLE: + vsize = sizeof(double); + break; + case PM_TYPE_AGGREGATE: + case PM_TYPE_STRING: + case PM_TYPE_AGGREGATE_STATIC: + vsize = epp->ep_len - PM_VAL_HDR_SIZE; + break; + case PM_TYPE_EVENT: /* no nesting! */ + case PM_TYPE_HIGHRES_EVENT: + default: + free(vset); + return PM_ERR_TYPE; + } + need = vsize + PM_VAL_HDR_SIZE; + want = need; + if (want < sizeof(pmValueBlock)) + want = sizeof(pmValueBlock); +PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC); + vset->vlist[0].value.pval = (pmValueBlock *)malloc(want); + if (vset->vlist[0].value.pval == NULL) { + vset->valfmt = PM_VAL_INSITU; + return -oserror(); + } + vset->vlist[0].value.pval->vlen = need; + vset->vlist[0].value.pval->vtype = epp->ep_type; + memcpy((void *)vset->vlist[0].value.pval->vbuf, (void *)vbuf, vsize); + vset->valfmt = PM_VAL_DPTR; + *vsetp = vset; + return 0; +} + +/* + * Process the idx'th instance of an event record metric value + * and unpack the array of event records into a pmResult. + */ +int +pmUnpackEventRecords(pmValueSet *vsp, int idx, pmResult ***rap) +{ + pmEventArray *eap; + const char caller[] = "pmUnpackEventRecords"; + char *base; + size_t need; + int r; /* records */ + int p; /* parameters in a record ... */ + int numpmid; /* metrics in a pmResult */ + int sts; + + if ((sts = register_event_metrics(caller)) < 0) + return sts; + + if ((sts = __pmCheckEventRecords(vsp, idx)) < 0) { + __pmDumpEventRecords(stderr, vsp, idx); + return sts; + } + + eap = (pmEventArray *)vsp->vlist[idx].value.pval; + if (eap->ea_nrecords == 0) { + *rap = NULL; + return 0; + } + + /* + * allocate one more than needed as a NULL sentinel to be used + * in pmFreeEventResult + */ +PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC); + need = (eap->ea_nrecords + 1) * sizeof(pmResult *); + if ((*rap = (pmResult **)malloc(need)) == NULL) + return -oserror(); + + base = (char *)&eap->ea_record[0]; + /* walk packed event record array */ + for (r = 0; r < eap->ea_nrecords; r++) { + pmEventRecord *erp = (pmEventRecord *)base; + pmResult *rp; + + numpmid = count_event_parameters(erp->er_flags, erp->er_nparams); + need = sizeof(pmResult) + (numpmid-1)*sizeof(pmValueSet *); +PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC); + if ((rp = (pmResult *)malloc(need)) == NULL) { + sts = -oserror(); + r--; + goto bail; + } + (*rap)[r] = rp; + rp->timestamp.tv_sec = erp->er_timestamp.tv_sec; + rp->timestamp.tv_usec = erp->er_timestamp.tv_usec; + rp->numpmid = numpmid; + base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams); + for (p = 0; p < numpmid; p++) { + pmEventParameter *epp = (pmEventParameter *)base; + + if ((sts = add_event_parameter(caller, epp, p, + erp->er_flags, erp->er_nparams, + &rp->vset[p])) < 0) { + rp->numpmid = p; + goto bail; + } + if (sts == 0) + base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + } + } + (*rap)[r] = NULL; /* sentinel */ + + if (pmDebug & DBG_TRACE_FETCH) { + fprintf(stderr, "pmUnpackEventRecords returns ...\n"); + for (r = 0; r < eap->ea_nrecords; r++) { + fprintf(stderr, "pmResult[%d]\n", r); + __pmDumpResult(stderr, (*rap)[r]); + } + } + + return eap->ea_nrecords; + +bail: + while (r >= 0) { + if ((*rap)[r] != NULL) + pmFreeResult((*rap)[r]); + r--; + } + free(*rap); + *rap = NULL; + return sts; +} + +/* + * Process the idx'th instance of a highres event record metric value + * and unpack the array of event records into a pmHighResResult. + */ +int +pmUnpackHighResEventRecords(pmValueSet *vsp, int idx, pmHighResResult ***rap) +{ + pmHighResEventArray *hreap; + const char caller[] = "pmUnpackHighResEventRecords"; + char *base; + size_t need; + int r; /* records */ + int p; /* parameters in a record ... */ + int numpmid; /* metrics in a pmResult */ + int sts; + + if ((sts = register_event_metrics(caller)) < 0) + return sts; + + if ((sts = __pmCheckHighResEventRecords(vsp, idx)) < 0) { + __pmDumpHighResEventRecords(stderr, vsp, idx); + return sts; + } + + hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval; + if (hreap->ea_nrecords == 0) { + *rap = NULL; + return 0; + } + + /* + * allocate one more than needed as a NULL sentinel to be used + * in pmFreeHighResEventResult + */ +PM_FAULT_POINT("libpcp/" __FILE__ ":7", PM_FAULT_ALLOC); + need = (hreap->ea_nrecords + 1) * sizeof(pmHighResResult *); + if ((*rap = (pmHighResResult **)malloc(need)) == NULL) + return -oserror(); + + base = (char *)&hreap->ea_record[0]; + /* walk packed event record array */ + for (r = 0; r < hreap->ea_nrecords; r++) { + pmHighResEventRecord *erp = (pmHighResEventRecord *)base; + pmHighResResult *rp; + + numpmid = count_event_parameters(erp->er_flags, erp->er_nparams); + need = sizeof(pmHighResResult) + (numpmid-1)*sizeof(pmValueSet *); +PM_FAULT_POINT("libpcp/" __FILE__ ":8", PM_FAULT_ALLOC); + if ((rp = (pmHighResResult *)malloc(need)) == NULL) { + sts = -oserror(); + r--; + goto bail; + } + (*rap)[r] = rp; + rp->timestamp.tv_sec = erp->er_timestamp.tv_sec; + rp->timestamp.tv_nsec = erp->er_timestamp.tv_nsec; + rp->numpmid = numpmid; + base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams); + for (p = 0; p < numpmid; p++) { + pmEventParameter *epp = (pmEventParameter *)base; + + if ((sts = add_event_parameter(caller, epp, p, + erp->er_flags, erp->er_nparams, + &rp->vset[p])) < 0) { + rp->numpmid = p; + goto bail; + } + if (sts == 0) + base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + } + } + (*rap)[r] = NULL; /* sentinel */ + + if (pmDebug & DBG_TRACE_FETCH) { + fprintf(stderr, "%s returns ...\n", caller); + for (r = 0; r < hreap->ea_nrecords; r++) { + fprintf(stderr, "pmHighResResult[%d]\n", r); + __pmDumpHighResResult(stderr, (*rap)[r]); + } + } + + return hreap->ea_nrecords; + +bail: + while (r >= 0) { + if ((*rap)[r] != NULL) + pmFreeHighResResult((*rap)[r]); + r--; + } + free(*rap); + *rap = NULL; + return sts; +} + +void +pmFreeEventResult(pmResult **rset) +{ + int r; + + if (rset == NULL) + return; + for (r = 0; rset[r] != NULL; r++) + pmFreeResult(rset[r]); + free(rset); +} + +void +pmFreeHighResEventResult(pmHighResResult **rset) +{ + int r; + + if (rset == NULL) + return; + for (r = 0; rset[r] != NULL; r++) + pmFreeHighResResult(rset[r]); + free(rset); +} diff --git a/src/libpcp/src/exports b/src/libpcp/src/exports new file mode 100644 index 0000000..545a9b3 --- /dev/null +++ b/src/libpcp/src/exports @@ -0,0 +1,472 @@ +PCP_3.0 { + global: + pmAddProfile; + pmAtomStr; + pmAtomStr_r; + pmConvScale; + pmCtime; + pmDebug; + pmDelProfile; + pmDerivedErrStr; + pmDestroyContext; + pmDupContext; + pmErrStr; + pmErrStr_r; + pmEventFlagsStr; + pmEventFlagsStr_r; + pmExtractValue; + pmFetch; + pmFetchArchive; + pmflush; + pmFreeEventResult; + pmFreeMetricSpec; + pmFreeResult; + pmGetArchiveEnd; + pmGetArchiveLabel; + pmGetChildren; + pmGetChildrenStatus; + pmGetConfig; + pmGetContextHostName; + pmGetInDom; + pmGetInDomArchive; + pmGetPMNSLocation; + pmIDStr; + pmIDStr_r; + pmInDomStr; + pmInDomStr_r; + pmLoadASCIINameSpace; + pmLoadDerivedConfig; + pmLoadNameSpace; + pmLocaltime; + pmLookupDesc; + pmLookupInDom; + pmLookupInDomArchive; + pmLookupInDomText; + pmLookupName; + pmLookupText; + pmNameAll; + pmNameID; + pmNameInDom; + pmNameInDomArchive; + pmNewContext; + pmNewContextZone; + pmNewZone; + pmNumberStr; + pmNumberStr_r; + pmParseInterval; + pmParseMetricSpec; + pmParseTimeWindow; + pmprintf; + pmPrintValue; + pmProgname; + pmReconnectContext; + pmRegisterDerived; + pmSetMode; + pmSortInstances; + pmStore; + pmTraversePMNS; + pmTraversePMNS_r; + pmTrimNameSpace; + pmTypeStr; + pmTypeStr_r; + pmUnitsStr; + pmUnitsStr_r; + pmUnloadNameSpace; + pmUnpackEventRecords; + pmUseContext; + pmUseZone; + pmWhichContext; + pmWhichZone; + + __pmAbsolutePath; + __pmAccAddAccount; + __pmAccAddClient; + __pmAccAddGroup; + __pmAccAddHost; + __pmAccAddOp; + __pmAccAddUser; + __pmAccDelAccount; + __pmAccDelClient; + __pmAccDumpGroups; + __pmAccDumpHosts; + __pmAccDumpLists; + __pmAccDumpUsers; + __pmAccept; + __pmAccFreeSavedGroups; + __pmAccFreeSavedHosts; + __pmAccFreeSavedLists; + __pmAccFreeSavedUsers; + __pmAccRestoreGroups; + __pmAccRestoreHosts; + __pmAccRestoreLists; + __pmAccRestoreUsers; + __pmAccSaveGroups; + __pmAccSaveHosts; + __pmAccSaveLists; + __pmAccSaveUsers; + __pmAddHostPorts; + __pmAddPMNSNode; + __pmAFblock; + __pmAFisempty; + __pmAFregister; + __pmAFunblock; + __pmAFunregister; + __pmAPIConfig; + __pmAttrKeyStr_r; + __pmAttrStr_r; + __pmAuxConnectPMCD; + __pmAuxConnectPMCDPort; + __pmAuxConnectPMCDUnixSocket; + __pmBind; + __pmCheckEventRecords; + __pmCheckSum; + __pmCloseSocket; + __pmConfig; + __pmConnect; + __pmConnectGetPorts; + __pmConnectLocal; + __pmConnectLogger; + __pmConnectPMCD; + __pmConnectTo; + __pmControlLog; + __pmConvertTime; + __pmCountPDUBuf; + __pmCreateIPv6Socket; + __pmCreateSocket; + __pmCreateUnixSocket; + __pmDataIPC; + __pmDataIPCSize; + __pmDecodeAuth; + __pmDecodeChildReq; + __pmDecodeCreds; + __pmDecodeDesc; + __pmDecodeDescReq; + __pmDecodeError; + __pmDecodeFetch; + __pmDecodeIDList; + __pmDecodeInstance; + __pmDecodeInstanceReq; + __pmDecodeLogControl; + __pmDecodeLogRequest; + __pmDecodeLogStatus; + __pmDecodeNameList; + __pmDecodeProfile; + __pmDecodeResult; + __pmDecodeText; + __pmDecodeTextReq; + __pmDecodeTraversePMNSReq; + __pmDecodeXtendError; + __pmDropHostPort; + __pmDumpContext; + __pmDumpErrTab; + __pmDumpEventRecords; + __pmDumpIDList; + __pmDumpInResult; + __pmDumpNameAndStatusList; + __pmDumpNameList; + __pmDumpNameSpace; + __pmDumpProfile; + __pmDumpResult; + __pmDumpStatusList; + __pmEncodeResult; + __pmEventTrace; + __pmEventTrace_r; + __pmExportPMNS; + __pmFaultInject; + __pmFaultSummary; + __pmFD; + __pmFD_CLR; + __pmFD_COPY; + __pmFD_ISSET; + __pmFD_SET; + __pmFD_ZERO; + __pmFindPDUBuf; + __pmFindPMDA; + __pmFindProfile; + __pmFinishResult; + __pmFixPMNSHashTab; + __pmFreeAttrsSpec; + __pmFreeHostAttrsSpec; + __pmFreeHostSpec; + __pmFreeInResult; + __pmFreePMNS; + __pmFreeProfile; + __pmFreeResultValues; + __pmGetAddrInfo; + __pmGetAPIConfig; + __pmGetArchiveEnd; + __pmGetClientId; + __pmGetInternalState; + __pmGetNameInfo; + __pmGetPDU; + __pmGetPDUCeiling; + __pmGetSockOpt; + __pmGetUsername; + __pmHandleToPtr; + __pmHashAdd; + __pmHashClear; + __pmHashDel; + __pmHashInit; + __pmHashSearch; + __pmHashWalk; + __pmHashWalkCB; + __pmHasPMNSFileChanged; + __pmHostEntAlloc; + __pmHostEntFree; + __pmHostEntGetName; + __pmHostEntGetSockAddr; + __pmInitLocks; + __pmInProfile; + __pmIsLocalhost; + __pmLastVersionIPC; + __pmListen; + __pmLocalPMDA; + __pmLock; + __pmLock_libpcp; + __pmLogCacheClear; + __pmLogChangeVol; + __pmLogChkLabel; + __pmLogClose; + __pmLogCreate; + __pmLogFetch; + __pmLogFetchInterp; + __pmLogFindLocalPorts; + __pmLogFindPort; + __pmLoggerTimeout; + __pmLogGetInDom; + __pmLogLoadIndex; + __pmLogLoadLabel; + __pmLogLoadMeta; + __pmLogLookupDesc; + __pmLogLookupInDom; + __pmLogName; + __pmLogNameInDom; + __pmLogName_r; + __pmLogNewFile; + __pmLogOpen; + __pmLogPutDesc; + __pmLogPutIndex; + __pmLogPutInDom; + __pmLogPutResult; + __pmLogRead; + __pmLogReads; + __pmLogResetInterp; + __pmLogSetTime; + __pmLogWriteLabel; + __pmLookupAttrKey; + __pmLookupDSO; + __pmLoopBackAddress; + __pmMapErrno; + __pmMemoryMap; + __pmMemoryUnmap; + __pmMktime; + __pmMultiThreaded; + __pmNativeConfig; + __pmNativePath; + __pmNewPMNS; + __pmNoMem; + __pmNotifyErr; + __pmOpenLog; + __pmOptFetchAdd; + __pmOptFetchDel; + __pmOptFetchDump; + __pmOptFetchGetParams; + __pmOptFetchPutParams; + __pmOptFetchRedo; + __pmOverrideLastFd; + __pmParseCtime; + __pmParseDebug; + __pmParseHostAttrsSpec; + __pmParseHostSpec; + __pmParseTime; + __pmPathSeparator; + __pmPDUCntIn; + __pmPDUCntOut; + __pmPDUTypeStr; + __pmPDUTypeStr_r; + __pmPinPDUBuf; + __pmPrepareFetch; + __pmPrintDesc; + __pmPrintIPC; + __pmPrintStamp; + __pmPrintTimeval; + __pmProcessCreate; + __pmProcessDataSize; + __pmProcessExists; + __pmProcessRunTimes; + __pmProcessTerminate; + __pmRead; + __pmRecv; + __pmRegisterAnon; + __pmResetIPC; + __pmRotateLog; + __pmSecureClientHandshake; + __pmSecureServerHandshake; + __pmSecureServerSetup; + __pmSecureServerShutdown; + __pmSelectRead; + __pmSelectWrite; + __pmSend; + __pmSendAuth; + __pmSendChildReq; + __pmSendCreds; + __pmSendDesc; + __pmSendDescReq; + __pmSendError; + __pmSendFetch; + __pmSendIDList; + __pmSendInstance; + __pmSendInstanceReq; + __pmSendLogControl; + __pmSendLogRequest; + __pmSendLogStatus; + __pmSendNameList; + __pmSendProfile; + __pmSendResult; + __pmSendText; + __pmSendTextReq; + __pmSendTraversePMNSReq; + __pmSendXtendError; + __pmServerAddInterface; + __pmServerAddNewClients; + __pmServerAddPorts; + __pmServerAdvertisePresence; + __pmServerClearFeature; + __pmServerCloseRequestPorts; + __pmServerDumpRequestPorts; + __pmServerHasFeature; + __pmServerOpenRequestPorts; + __pmServerRequestPortString; + __pmServerSetFeature; + __pmServerSetLocalCreds; + __pmServerSetLocalSocket; + __pmServerSetServiceSpec; + __pmServerUnadvertisePresence; + __pmSetClientId; + __pmSetClientIdArgv; + __pmSetDataIPC; + __pmSetInternalState; + __pmSetPDUCeiling; + __pmSetPDUCntBuf; + __pmSetProcessIdentity; + __pmSetProgname; + __pmSetSignalHandler; + __pmSetSocketIPC; + __pmSetSockOpt; + __pmSetVersionIPC; + __pmShutdown; + __pmSockAddrAlloc; + __pmSockAddrCompare; + __pmSockAddrDup; + __pmSockAddrFree; + __pmSockAddrGetFamily; + __pmSockAddrGetPort; + __pmSockAddrInit; + __pmSockAddrIsInet; + __pmSockAddrIsIPv6; + __pmSockAddrIsLoopBack; + __pmSockAddrIsUnix; + __pmSockAddrMask; + __pmSockAddrSetFamily; + __pmSockAddrSetPath; + __pmSockAddrSetPort; + __pmSockAddrSetScope; + __pmSockAddrSize; + __pmSockAddrToString; + __pmSocketIPC; + __pmSpecLocalPMDA; + __pmStringToSockAddr; + __pmStuffValue; + __pmSyslog; + __pmtimevalAdd; + __pmtimevalFromReal; + __pmtimevalNow; + __pmtimevalPause; + __pmtimevalSleep; + __pmtimevalSub; + __pmTimevalSub; + __pmtimevalToReal; + __pmTimezone; + __pmTimezone_r; + __pmUnlock; + __pmUnparseHostAttrsSpec; + __pmUnparseHostSpec; + __pmUnpinPDUBuf; + __pmUsePMNS; + __pmVersionIPC; + __pmWrite; + __pmXmitPDU; + + local: *; +}; + +PCP_3.1 { + global: + pmDiscoverServices; +} PCP_3.0; + +PCP_3.2 { + global: + pmGetContextHostName_r; + pmGetContextOptions; + pmGetOptions; + pmFreeOptions; + pmUsageMessage; + + __pmStartOptions; + __pmAddOptArchive; + __pmAddOptArchiveList; + __pmAddOptHost; + __pmAddOptHostList; + __pmEndOptions; +} PCP_3.1; + +PCP_3.3 { + global: + pmgetopt_r; + + __pmLogLocalSocketDefault; + __pmLogLocalSocketUser; + __pmMakePath; +} PCP_3.2; + +PCP_3.4 { + global: + __pmConnectCheckError; + __pmConnectRestoreFlags; + __pmFdOpen; + __pmGetFileStatusFlags; + __pmSetFileStatusFlags; + __pmGetFileDescriptorFlags; + __pmSetFileDescriptorFlags; + __pmSocketClosed; + __pmLogPutResult2; +} PCP_3.3; + +PCP_3.5 { + global: + __pmDumpStack; + __pmServerCreatePIDFile; +} PCP_3.4; + +PCP_3.6 { + global: + __pmDiscoverServicesWithOptions; + __pmDumpNameNode; + __pmFreeInterpData; + __pmAddOptArchiveFolio; +} PCP_3.5; + +PCP_3.7 { + global: + pmFreeHighResResult; + pmFreeHighResEventResult; + pmUnpackHighResEventRecords; + + __pmCheckHighResEventRecords; + __pmDumpHighResEventRecords; + __pmDumpHighResResult; + __pmPrintHighResStamp; + __pmPrintTimespec; + __pmGetTimespec; +} PCP_3.6; diff --git a/src/libpcp/src/fault.c b/src/libpcp/src/fault.c new file mode 100644 index 0000000..5ba8bf0 --- /dev/null +++ b/src/libpcp/src/fault.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2011 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "fault.h" +/* need pmda.h and libpcp_pmda for the pmdaCache* routines */ +#include "pmda.h" + +#include <ctype.h> + +/* + * Fault Injection - run-time control structure + */ +typedef struct { + int ntrip; + int op; + int thres; + int nfault; +} control_t; + +#define PM_FAULT_LT 0 +#define PM_FAULT_LE 1 +#define PM_FAULT_EQ 2 +#define PM_FAULT_GE 3 +#define PM_FAULT_GT 4 +#define PM_FAULT_NE 5 +#define PM_FAULT_MOD 6 + +#ifdef PM_FAULT_INJECTION + +int __pmFault_arm; + +#define FAULT_INDOM pmInDom_build(DYNAMIC_PMID, 1024) + +static void +__pmFaultAtExit(void) +{ + __pmFaultSummary(stderr); +} + +void +__pmFaultInject(const char *ident, int class) +{ + static int first = 1; + int sts; + control_t *cp; + + if (first) { + char *fname = getenv("PM_FAULT_CONTROL"); + if (fname != NULL) { + FILE *f; + if ((f = fopen(fname, "r")) == NULL) { + char msgbuf[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmFaultInject: cannot open \"%s\": %s\n", fname, pmErrStr_r(-errno, msgbuf, sizeof(msgbuf))); + } + else { + char line[128]; + int lineno = 0; + /* + * control line format + * ident - start of line to first white space + * guard - optional, consists of <op> and threshold + * <op> is one of <, <=, ==, >=, >=, != or % + * threshold is an integer value ... + * fault will be injected when + * tripcount <op> threshold == 1 + * default guard is ">0", i.e. fault on every trip + * leading # => comment + */ + pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_CULL); + while (fgets(line, sizeof(line), f) != NULL) { + char *lp = line; + char *sp; + char *ep; + int op; + int thres; + lineno++; + while (*lp) { + if (*lp == '\n') { + *lp = '\0'; + break; + } + lp++; + } + lp = line; + while (*lp && isspace((int)*lp)) lp++; + /* comment? */ + if (*lp == '#') + continue; + sp = lp; + while (*lp && !isspace((int)*lp)) lp++; + /* empty line? */ + if (lp == sp) + continue; + ep = lp; + while (*lp && isspace((int)*lp)) lp++; + if (*lp == '\0') { + op = PM_FAULT_GT; + thres = 0; + } + else { + if (strncmp(lp, "<=", 2) == 0) { + op = PM_FAULT_LE; + lp +=2; + } + else if (strncmp(lp, ">=", 2) == 0) { + op = PM_FAULT_GE; + lp +=2; + } + else if (strncmp(lp, "!=", 2) == 0) { + op = PM_FAULT_NE; + lp +=2; + } + else if (strncmp(lp, "==", 2) == 0) { + op = PM_FAULT_EQ; + lp +=2; + } + else if (*lp == '<') { + op = PM_FAULT_LT; + lp++; + } + else if (*lp == '>') { + op = PM_FAULT_GT; + lp++; + } + else if (*lp == '%') { + op = PM_FAULT_MOD; + lp++; + } + else { + fprintf(stderr, "Ignoring: %s[%d]: illegal operator: %s\n", fname, lineno, line); + continue; + } + } + while (*lp && isspace((int)*lp)) lp++; + thres = (int)strtol(lp, &lp, 10); + while (*lp && isspace((int)*lp)) lp++; + if (*lp != '\0') { + fprintf(stderr, "Ignoring: %s[%d]: non-numeric threshold: %s\n", fname, lineno, line); + continue; + } + cp = (control_t *)malloc(sizeof(control_t)); + if (cp == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmFaultInject: malloc failed: %s\n", pmErrStr_r(-errno, errmsg, sizeof(errmsg))); + break; + } + *ep = '\0'; + cp->ntrip = cp->nfault = 0; + cp->op = op; + cp->thres = thres; + sts = pmdaCacheStore(FAULT_INDOM, PMDA_CACHE_ADD, sp, cp); + if (sts < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s[%d]: %s\n", fname, lineno, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + } + fclose(f); + } + } +#ifdef HAVE_ATEXIT +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FAULT) + atexit(__pmFaultAtExit); +#endif +#endif + first = 0; + } + + sts = pmdaCacheLookupName(FAULT_INDOM, ident, NULL, (void **)&cp); + if (sts == PMDA_CACHE_ACTIVE) { + cp->ntrip++; + __pmFault_arm = 0; + switch (cp->op) { + case PM_FAULT_LT: + __pmFault_arm = (cp->ntrip < cp->thres) ? class : 0; + break; + case PM_FAULT_LE: + __pmFault_arm = (cp->ntrip <= cp->thres) ? class : 0; + break; + case PM_FAULT_EQ: + __pmFault_arm = (cp->ntrip == cp->thres) ? class : 0; + break; + case PM_FAULT_GE: + __pmFault_arm = (cp->ntrip >= cp->thres) ? class : 0; + break; + case PM_FAULT_GT: + __pmFault_arm = (cp->ntrip > cp->thres) ? class : 0; + break; + case PM_FAULT_NE: + __pmFault_arm = (cp->ntrip != cp->thres) ? class : 0; + break; + case PM_FAULT_MOD: + __pmFault_arm = ((cp->ntrip % cp->thres) == 1) ? class : 0; + break; + } + if (__pmFault_arm != 0) + cp->nfault++; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FAULT) + fprintf(stderr, "__pmFaultInject(%s) ntrip=%d %s\n", ident, cp->ntrip, __pmFault_arm == 0 ? "SKIP" : "INJECT"); +#endif + } + else if (sts == PM_ERR_INST) { + /* + * expected for injection points that are compiled in the code + * but not registered via the control file + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FAULT) + fprintf(stderr, "__pmFaultInject(%s) not registered\n", ident); +#endif + ; + } + else { + /* oops, this is serious */ + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmFaultInject(%s): %s\n", ident, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + +} + +void +__pmFaultSummary(FILE *f) +{ + int inst; + char *ident; + control_t *cp; + int sts; + static char *opstr[] = { "<", "<=", "==", ">=", ">", "!=", "%" }; + + pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_WALK_REWIND); + + fprintf(f, "=== Fault Injection Summary Report ===\n"); + while ((inst = pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_WALK_NEXT)) != -1) { + sts = pmdaCacheLookup(FAULT_INDOM, inst, &ident, (void **)&cp); + if (sts < 0) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(f, "pmdaCacheLookup(%s, %d, %s, ..): %s\n", pmInDomStr_r(FAULT_INDOM, strbuf, sizeof(strbuf)), inst, ident, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + else + fprintf(f, "%s: guard trip%s%d, %d trips, %d faults\n", ident, opstr[cp->op], cp->thres, cp->ntrip, cp->nfault); + + } +} + +void +*__pmFault_malloc(size_t size) +{ + if (__pmFault_arm == PM_FAULT_ALLOC) { + __pmFault_arm = 0; + errno = ENOMEM; + return NULL; + } + else +#undef malloc + return malloc(size); +} + +void +*__pmFault_realloc(void *ptr, size_t size) +{ + if (__pmFault_arm == PM_FAULT_ALLOC) { + __pmFault_arm = 0; + errno = ENOMEM; + return NULL; + } + else +#undef realloc + return realloc(ptr, size); +} + +char * +__pmFault_strdup(const char *s) +{ + if (__pmFault_arm == PM_FAULT_ALLOC) { + __pmFault_arm = 0; + errno = ENOMEM; + return NULL; + } + else +#undef strdup + return strdup(s); +} + +#else +void +__pmFaultInject(const char *ident, int class) +{ + fprintf(stderr, "__pmFaultInject() called but library not compiled with -DPM_FAULT_INJECTION\n"); + exit(1); +} + +void +__pmFaultSummary(FILE *f) +{ + fprintf(f, "__pmFaultSummary() called but library not compiled with -DPM_FAULT_INJECTION\n"); + exit(1); + +} +#endif diff --git a/src/libpcp/src/fetch.c b/src/libpcp/src/fetch.c new file mode 100644 index 0000000..de684af --- /dev/null +++ b/src/libpcp/src/fetch.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 1995-2006,2008 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +static int +request_fetch(int ctxid, __pmContext *ctxp, int numpmid, pmID pmidlist[]) +{ + int n; + + if (ctxp->c_sent == 0) { + /* + * current profile is _not_ already cached at other end of + * IPC, so send get current profile + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PROFILE) { + fprintf(stderr, "pmFetch: calling __pmSendProfile, context: %d\n", + ctxid); + __pmDumpProfile(stderr, PM_INDOM_NULL, ctxp->c_instprof); + } +#endif + if ((n = __pmSendProfile(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + ctxid, ctxp->c_instprof)) < 0) + return (__pmMapErrno(n)); + else + ctxp->c_sent = 1; + } + + n = __pmSendFetch(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), ctxid, + &ctxp->c_origin, numpmid, pmidlist); + if (n < 0) { + n = __pmMapErrno(n); + } + return n; +} + +int +__pmPrepareFetch(__pmContext *ctxp, int numpmid, const pmID *ids, pmID **newids) +{ + return __dmprefetch(ctxp, numpmid, ids, newids); +} + +int +__pmFinishResult(__pmContext *ctxp, int count, pmResult **resultp) +{ + if (count >= 0) + __dmpostfetch(ctxp, resultp); + return count; +} + +int +pmFetch(int numpmid, pmID pmidlist[], pmResult **result) +{ + int n; + + if (numpmid < 1) { + n = PM_ERR_TOOSMALL; + goto done; + } + + if ((n = pmWhichContext()) >= 0) { + __pmContext *ctxp = __pmHandleToPtr(n); + int newcnt; + pmID *newlist = NULL; + int have_dm; + + if (ctxp == NULL) { + n = PM_ERR_NOCONTEXT; + goto done; + } + if (ctxp->c_type == PM_CONTEXT_LOCAL && PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) { + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + PM_UNLOCK(ctxp->c_lock); + goto done; + } + + /* for derived metrics, may need to rewrite the pmidlist */ + have_dm = newcnt = __pmPrepareFetch(ctxp, numpmid, pmidlist, &newlist); + if (newcnt > numpmid) { + /* replace args passed into pmFetch */ + numpmid = newcnt; + pmidlist = newlist; + } + + if (ctxp->c_type == PM_CONTEXT_HOST) { + /* + * Thread-safe note + * + * Need to be careful here, because the PMCD changed protocol + * may mean several PDUs are returned, but __pmDecodeResult() + * may request more info from PMCD if pmDebug is set. + * + * So unlock ctxp->c_pmcd->pc_lock as soon as possible. + */ + PM_LOCK(ctxp->c_pmcd->pc_lock); + if ((n = request_fetch(n, ctxp, numpmid, pmidlist)) >= 0) { + int changed = 0; + do { + __pmPDU *pb; + int pinpdu; + + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_RESULT) { + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + n = __pmDecodeResult(pb, result); + } + else if (n == PDU_ERROR) { + __pmDecodeError(pb, &n); + if (n > 0) + /* PMCD state change protocol */ + changed = n; + else + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else { + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } while (n > 0); + + if (n == 0) + n |= changed; + } + else + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + n = __pmFetchLocal(ctxp, numpmid, pmidlist, result); + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + n = __pmLogFetch(ctxp, numpmid, pmidlist, result); + if (n >= 0 && (ctxp->c_mode & __PM_MODE_MASK) != PM_MODE_INTERP) { + ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + } + } + + /* process derived metrics, if any */ + if (have_dm) { + __pmFinishResult(ctxp, n, result); + if (newlist != NULL) + free(newlist); + } + PM_UNLOCK(ctxp->c_lock); + } + +done: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FETCH) { + fprintf(stderr, "pmFetch returns ...\n"); + if (n > 0) { + fprintf(stderr, "PMCD state changes: agent(s)"); + if (n & PMCD_ADD_AGENT) fprintf(stderr, " added"); + if (n & PMCD_RESTART_AGENT) fprintf(stderr, " restarted"); + if (n & PMCD_DROP_AGENT) fprintf(stderr, " dropped"); + fputc('\n', stderr); + } + if (n >= 0) + __pmDumpResult(stderr, *result); + else { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "Error: %s\n", pmErrStr_r(n, errmsg, sizeof(errmsg))); + } + } +#endif + + return n; +} + +int +pmFetchArchive(pmResult **result) +{ + int n; + __pmContext *ctxp; + int ctxp_mode; + + if ((n = pmWhichContext()) >= 0) { + ctxp = __pmHandleToPtr(n); + if (ctxp == NULL) + n = PM_ERR_NOCONTEXT; + else { + ctxp_mode = (ctxp->c_mode & __PM_MODE_MASK); + if (ctxp->c_type != PM_CONTEXT_ARCHIVE) + n = PM_ERR_NOTARCHIVE; + else if (ctxp_mode == PM_MODE_INTERP) + /* makes no sense! */ + n = PM_ERR_MODE; + else { + /* assume PM_CONTEXT_ARCHIVE and BACK or FORW */ + n = __pmLogFetch(ctxp, 0, NULL, result); + if (n >= 0) { + ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + } + } + PM_UNLOCK(ctxp->c_lock); + } + } + + return n; +} + +int +pmSetMode(int mode, const struct timeval *when, int delta) +{ + int n; + __pmContext *ctxp; + int l_mode = (mode & __PM_MODE_MASK); + + if ((n = pmWhichContext()) >= 0) { + ctxp = __pmHandleToPtr(n); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + if (l_mode != PM_MODE_LIVE) + n = PM_ERR_MODE; + else { + ctxp->c_origin.tv_sec = ctxp->c_origin.tv_usec = 0; + ctxp->c_mode = mode; + ctxp->c_delta = delta; + n = 0; + } + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + n = PM_ERR_MODE; + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + if (l_mode == PM_MODE_INTERP || + l_mode == PM_MODE_FORW || l_mode == PM_MODE_BACK) { + if (when != NULL) { + /* + * special case of NULL for timestamp + * => do not update notion of "current" time + */ + ctxp->c_origin.tv_sec = (__int32_t)when->tv_sec; + ctxp->c_origin.tv_usec = (__int32_t)when->tv_usec; + } + ctxp->c_mode = mode; + ctxp->c_delta = delta; + __pmLogSetTime(ctxp); + __pmLogResetInterp(ctxp); + n = 0; + } + else + n = PM_ERR_MODE; + } + PM_UNLOCK(ctxp->c_lock); + } + return n; +} diff --git a/src/libpcp/src/fetchlocal.c b/src/libpcp/src/fetchlocal.c new file mode 100644 index 0000000..d7944a6 --- /dev/null +++ b/src/libpcp/src/fetchlocal.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <stdio.h> +#include <sys/time.h> +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" + +/* + * Called with valid context locked ... + */ +int +__pmFetchLocal(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result) +{ + int sts; + int ctx; + int j; + int k; + int n; + pmResult *ans; + pmResult *tmp_ans; + __pmDSO *dp; + int need; + + static pmID * splitlist=NULL; + static int splitmax=0; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + return PM_ERR_THREAD; + if (numpmid < 1) + return PM_ERR_TOOSMALL; + + ctx = __pmPtrToHandle(ctxp); + + /* + * this is very ugly ... the DSOs have a high-water mark + * allocation algorithm for the result skeleton, but the + * code that calls us assumes it has freedom to retain + * this result structure for as long as it wishes, and + * then to call pmFreeResult + * + * we make another skeleton, selectively copy and return that + * + * (numpmid - 1) because there's room for one valueSet + * in a pmResult + */ + need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *); + if ((ans = (pmResult *)malloc(need)) == NULL) + return -oserror(); + + /* + * Check if we have enough space to accomodate "best" case scenario - + * all pmids are from the same domain + */ + if (splitmax < numpmid) { + splitmax = numpmid; + pmID *tmp_list = (pmID *)realloc(splitlist, sizeof(pmID)*splitmax); + if (tmp_list == NULL) { + free(splitlist); + splitmax = 0; + free(ans); + return -oserror(); + } + splitlist = tmp_list; + } + + ans->numpmid = numpmid; + __pmtimevalNow(&ans->timestamp); + for (j = 0; j < numpmid; j++) + ans->vset[j] = NULL; + + for (j = 0; j < numpmid; j++) { + int cnt; + + if (ans->vset[j] != NULL) + /* picked up in a previous fetch */ + continue; + + sts = 0; + if ((dp = __pmLookupDSO(((__pmID_int *)&pmidlist[j])->domain)) == NULL) + /* based on domain, unknown PMDA */ + sts = PM_ERR_NOAGENT; + else { + if (ctxp->c_sent != dp->domain) { + /* + * current profile is _not_ already cached at other end of + * IPC, so send get current profile ... + * Note: trickier than the non-local case, as no per-PMDA + * caching at the PMCD end, so need to remember the + * last domain to receive a profile + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FETCH) + fprintf(stderr, + "__pmFetchLocal: calling ???_profile(domain: %d), " + "context: %d\n", dp->domain, ctx); +#endif + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + sts = dp->dispatch.version.any.profile(ctxp->c_instprof, + dp->dispatch.version.any.ext); + if (sts >= 0) + ctxp->c_sent = dp->domain; + } + } + + /* Copy all pmID for the current domain into the temp. list */ + for (cnt=0, k=j; k < numpmid; k++ ) { + if (((__pmID_int*)(pmidlist+k))->domain == + ((__pmID_int*)(pmidlist+j))->domain) + splitlist[cnt++] = pmidlist[k]; + } + + if (sts >= 0) { + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + sts = dp->dispatch.version.any.fetch(cnt, splitlist, &tmp_ans, + dp->dispatch.version.any.ext); + } + + /* Copy results back + * + * Note: We DO NOT have to free tmp_ans since DSO PMDA would + * ALWAYS return a pointer to the static area. + */ + for (n = 0, k = j; k < numpmid && n < cnt; k++) { + if (pmidlist[k] == splitlist[n]) { + if (sts < 0) { + ans->vset[k] = (pmValueSet *)malloc(sizeof(pmValueSet)); + if (ans->vset[k] == NULL) { + /* cleanup all partial allocations for ans->vset[] */ + for (k--; k >=0; k--) + free(ans->vset[k]); + free(ans); + return -oserror(); + } + ans->vset[k]->numval = sts; + ans->vset[k]->pmid = pmidlist[k]; + } + else { + ans->vset[k] = tmp_ans->vset[n]; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_FETCH) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmFetchLocal: [%d] PMID=%s nval=", + k, pmIDStr_r(pmidlist[k], strbuf, sizeof(strbuf))); + if (ans->vset[k]->numval < 0) + fprintf(stderr, "%s\n", + pmErrStr_r(ans->vset[k]->numval, errmsg, sizeof(errmsg))); + else + fprintf(stderr, "%d\n", ans->vset[k]->numval); + } +#endif + n++; + } + } + } + *result = ans; + + return 0; +} diff --git a/src/libpcp/src/freeresult.c b/src/libpcp/src/freeresult.c new file mode 100644 index 0000000..b7e3d52 --- /dev/null +++ b/src/libpcp/src/freeresult.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" + +/* Free result buffer routines */ + +static void +__pmFreeResultValueSets(pmValueSet **ppvstart, pmValueSet **ppvsend) +{ + pmValueSet *pvs; + pmValueSet **ppvs; + char strbuf[20]; + int j; + + /* if _any_ vset[] -> an address within a pdubuf, we are done */ + for (ppvs = ppvstart; ppvs < ppvsend; ppvs++) { + if (__pmUnpinPDUBuf((void *)*ppvs)) + return; + } + + /* not created from a pdubuf, really free the memory */ + for (ppvs = ppvstart; ppvs < ppvsend; ppvs++) { + pvs = *ppvs; + if (pvs->numval > 0 && pvs->valfmt == PM_VAL_DPTR) { + /* pmValueBlocks may be malloc'd as well */ + for (j = 0; j < pvs->numval; j++) { + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "free" + "(" PRINTF_P_PFX "%p) pmValueBlock pmid=%s inst=%d\n", + pvs->vlist[j].value.pval, + pmIDStr_r(pvs->pmid, strbuf, sizeof(strbuf)), + pvs->vlist[j].inst); + free(pvs->vlist[j].value.pval); + } + } + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "free(" PRINTF_P_PFX "%p) vset pmid=%s\n", + pvs, pmIDStr_r(pvs->pmid, strbuf, sizeof(strbuf))); + free(pvs); + } +} + +void +__pmFreeResultValues(pmResult *result) +{ + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "__pmFreeResultValues(" PRINTF_P_PFX "%p) numpmid=%d\n", + result, result->numpmid); + if (result->numpmid) + __pmFreeResultValueSets(result->vset, &result->vset[result->numpmid]); +} + +void +pmFreeResult(pmResult *result) +{ + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "pmFreeResult(" PRINTF_P_PFX "%p)\n", result); + __pmFreeResultValues(result); + free(result); +} + +void +pmFreeHighResResult(pmHighResResult *result) +{ + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "pmFreeHighResResult(" PRINTF_P_PFX "%p)\n", result); + if (result->numpmid) + __pmFreeResultValueSets(result->vset, &result->vset[result->numpmid]); + free(result); +} diff --git a/src/libpcp/src/getdate.y b/src/libpcp/src/getdate.y new file mode 100644 index 0000000..b01d015 --- /dev/null +++ b/src/libpcp/src/getdate.y @@ -0,0 +1,1274 @@ + /* *INDENT-OFF* *//* indent -linux -nce -i4 */ +%{ +/* Parse a string into an internal time stamp. + * + * Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007 Free Software + * Foundation, Inc. + * + * 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, 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. + */ + +/* There's no need to extend the stack, so there's no need to involve + alloca. */ +#define YYSTACK_USE_ALLOCA 0 + +/* Tell Bison how much stack space is needed. 20 should be plenty for + this grammar, which is not right recursive. Beware setting it too + high, since that might cause problems on machines whose + implementations have lame stack-overflow checking. */ +#define YYMAXDEPTH 20 +#define YYINITDEPTH YYMAXDEPTH + +#include <ctype.h> +#include <limits.h> +#include <stdbool.h> +#include "pmapi.h" +#include "impl.h" + + +/* ISDIGIT differs from isdigit, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char + or EOF. + - It's typically faster. + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + isdigit unless it's important to use the locale's definition + of `digit' even when the host does not conform to POSIX. */ +#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9) + +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ +# define __attribute__(x) +# endif +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +/* Shift A right by B bits portably, by dividing A by 2**B and + truncating towards minus infinity. A and B should be free of side + effects, and B should be in the range 0 <= B <= INT_BITS - 2, where + INT_BITS is the number of useful bits in an int. GNU code can + assume that INT_BITS is at least 32. + + ISO C99 says that A >> B is implementation-defined if A < 0. Some + implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift + right in the usual way when A < 0, so SHR falls back on division if + ordinary A >> B doesn't seem to be the usual signed shift. */ +#define SHR(a, b) \ + (-1 >> 1 == -1 \ + ? (a) >> (b) \ + : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0)) + +#define EPOCH_YEAR 1970 +#define TM_YEAR_BASE 1900 + +#define HOUR(x) ((x) * 60) + +/* An integer value, and the number of digits in its textual + representation. */ +typedef struct +{ + bool negative; + long int value; + size_t digits; +} textint; + +/* An entry in the lexical lookup table. */ +typedef struct +{ + char const *name; + int type; + int value; +} table; + +/* Meridian: am, pm, or 24-hour style. */ +enum { MERam, MERpm, MER24 }; + +enum { BILLION = 1000000000, LOG10_BILLION = 9 }; + +/* Relative times. */ +typedef struct +{ + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + long int year; + long int month; + long int day; + long int hour; + long int minutes; + long int seconds; + long int ns; +} relative_time; + +# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 }) + +/* Information passed to and from the parser. */ +typedef struct +{ + /* The input string remaining to be parsed. */ + const char *input; + + /* N, if this is the Nth Tuesday. */ + long int day_ordinal; + + /* Day of week; Sunday is 0. */ + int day_number; + + /* tm_isdst flag for the local zone. */ + int local_isdst; + + /* Time zone, in minutes east of UTC. */ + long int time_zone; + + /* Style used for time. */ + int meridian; + + /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */ + textint year; + long int month; + long int day; + long int hour; + long int minutes; + struct timespec seconds; /* includes nanoseconds */ + + /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */ + relative_time rel; + + /* Presence or counts of nonterminals of various flavors parsed so far. */ + bool timespec_seen; + bool rels_seen; + size_t dates_seen; + size_t days_seen; + size_t local_zones_seen; + size_t dsts_seen; + size_t times_seen; + size_t zones_seen; + + /* Table of local time zone abbrevations, terminated by a null entry. */ + table local_time_zone_table[3]; +} parser_control; + +union YYSTYPE; +static int yylex (union YYSTYPE *, parser_control *); +static int yyerror (parser_control const *, char const *); +static long int time_zone_hhmm (textint, long int); + +%} + +/* We want a reentrant parser, even if the TZ manipulation and the calls to + localtime and gmtime are not reentrant. */ +%pure-parser +%parse-param { parser_control *pc } +%lex-param { parser_control *pc } + +/* This grammar has 20 shift/reduce conflicts. */ +%expect 20 + +%union +{ + long int intval; + textint textintval; + struct timespec timespec; + relative_time rel; +} + +%token tAGO tDST + +%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT +%token <intval> tDAY_UNIT + +%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN +%token <intval> tMONTH tORDINAL tZONE + +%token <textintval> tSNUMBER tUNUMBER +%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER + +%type <intval> o_colon_minutes o_merid +%type <timespec> seconds signed_seconds unsigned_seconds + +%type <rel> relunit relunit_snumber + +%% + +spec: + timespec + | items + ; + +timespec: + '@' seconds + { + pc->seconds = $2; + pc->timespec_seen = true; + } + ; + +items: + /* empty */ + | items item + ; + +item: + time + { pc->times_seen++; } + | local_zone + { pc->local_zones_seen++; } + | zone + { pc->zones_seen++; } + | date + { pc->dates_seen++; } + | day + { pc->days_seen++; } + | rel + { pc->rels_seen = true; } + | number + ; + +time: + tUNUMBER tMERIDIAN + { + pc->hour = $1.value; + pc->minutes = 0; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = $2; + } + | tUNUMBER ':' tUNUMBER o_merid + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = $4; + } + | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = time_zone_hhmm ($4, $5); + } + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5; + pc->meridian = $6; + } + | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes + { + pc->hour = $1.value; + pc->minutes = $3.value; + pc->seconds = $5; + pc->meridian = MER24; + pc->zones_seen++; + pc->time_zone = time_zone_hhmm ($6, $7); + } + ; + +local_zone: + tLOCAL_ZONE + { + pc->local_isdst = $1; + pc->dsts_seen += (0 < $1); + } + | tLOCAL_ZONE tDST + { + pc->local_isdst = 1; + pc->dsts_seen += (0 < $1) + 1; + } + ; + +zone: + tZONE + { pc->time_zone = $1; } + | tZONE relunit_snumber + { pc->time_zone = $1; + pc->rel.ns += $2.ns; + pc->rel.seconds += $2.seconds; + pc->rel.minutes += $2.minutes; + pc->rel.hour += $2.hour; + pc->rel.day += $2.day; + pc->rel.month += $2.month; + pc->rel.year += $2.year; + pc->rels_seen = true; } + | tZONE tSNUMBER o_colon_minutes + { pc->time_zone = $1 + time_zone_hhmm ($2, $3); } + | tDAYZONE + { pc->time_zone = $1 + 60; } + | tZONE tDST + { pc->time_zone = $1 + 60; } + ; + +day: + tDAY + { + pc->day_ordinal = 1; + pc->day_number = $1; + } + | tDAY ',' + { + pc->day_ordinal = 1; + pc->day_number = $1; + } + | tORDINAL tDAY + { + pc->day_ordinal = $1; + pc->day_number = $2; + } + | tUNUMBER tDAY + { + pc->day_ordinal = $1.value; + pc->day_number = $2; + } + ; + +date: + tUNUMBER '/' tUNUMBER + { + pc->month = $1.value; + pc->day = $3.value; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER +/* *INDENT-ON* */ +{ + /* Interpret as YYYY/MM/DD if the first value has 4 or more digits, + otherwise as MM/DD/YY. + The goal in recognizing YYYY/MM/DD is solely to support legacy + machine-generated dates like those in an RCS log listing. If + you want portability, use the ISO 8601 format. */ + if (4 <= $1.digits) { + pc->year = $1; + pc->month = $3.value; + pc->day = $5.value; + } + else { + pc->month = $1.value; + pc->day = $3.value; + pc->year = $5; + } +} +/* *INDENT-OFF* */ + | tUNUMBER tSNUMBER tSNUMBER + { + /* ISO 8601 format. YYYY-MM-DD. */ + pc->year = $1; + pc->month = -$2.value; + pc->day = -$3.value; + } + | tUNUMBER tMONTH tSNUMBER + { + /* e.g. 17-JUN-1992. */ + pc->day = $1.value; + pc->month = $2; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tSNUMBER tSNUMBER + { + /* e.g. JUN-17-1992. */ + pc->month = $1; + pc->day = -$2.value; + pc->year.value = -$3.value; + pc->year.digits = $3.digits; + } + | tMONTH tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + } + | tMONTH tUNUMBER ',' tUNUMBER + { + pc->month = $1; + pc->day = $2.value; + pc->year = $4; + } + | tUNUMBER tMONTH + { + pc->day = $1.value; + pc->month = $2; + } + | tUNUMBER tMONTH tUNUMBER + { + pc->day = $1.value; + pc->month = $2; + pc->year = $3; + } + ; + +rel: + relunit tAGO + { + pc->rel.ns -= $1.ns; + pc->rel.seconds -= $1.seconds; + pc->rel.minutes -= $1.minutes; + pc->rel.hour -= $1.hour; + pc->rel.day -= $1.day; + pc->rel.month -= $1.month; + pc->rel.year -= $1.year; + } + | relunit + { + pc->rel.ns += $1.ns; + pc->rel.seconds += $1.seconds; + pc->rel.minutes += $1.minutes; + pc->rel.hour += $1.hour; + pc->rel.day += $1.day; + pc->rel.month += $1.month; + pc->rel.year += $1.year; + } + ; + +relunit: + tORDINAL tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1; } + | tUNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } + | tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = 1; } + | tORDINAL tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1; } + | tUNUMBER tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } + | tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = 1; } + | tORDINAL tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; } + | tUNUMBER tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } + | tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1; } + | tORDINAL tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1; } + | tUNUMBER tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } + | tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = 1; } + | tORDINAL tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1; } + | tUNUMBER tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } + | tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = 1; } + | tORDINAL tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1; } + | tUNUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + | tSDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } + | tUDECIMAL_NUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; } + | tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = 1; } + | relunit_snumber + ; + +relunit_snumber: + tSNUMBER tYEAR_UNIT + { $$ = RELATIVE_TIME_0; $$.year = $1.value; } + | tSNUMBER tMONTH_UNIT + { $$ = RELATIVE_TIME_0; $$.month = $1.value; } + | tSNUMBER tDAY_UNIT + { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; } + | tSNUMBER tHOUR_UNIT + { $$ = RELATIVE_TIME_0; $$.hour = $1.value; } + | tSNUMBER tMINUTE_UNIT + { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; } + | tSNUMBER tSEC_UNIT + { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; } + ; + +seconds: signed_seconds | unsigned_seconds; + +signed_seconds: + tSDECIMAL_NUMBER + | tSNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } + ; + +unsigned_seconds: + tUDECIMAL_NUMBER + | tUNUMBER + { $$.tv_sec = $1.value; $$.tv_nsec = 0; } + ; + +number: + tUNUMBER +/* *INDENT-ON* */ + +{ + if (pc->dates_seen && !pc->year.digits + && !pc->rels_seen && (pc->times_seen || 2 < $1.digits)) + pc->year = $1; + else { + if (4 < $1.digits) { + pc->dates_seen++; + pc->day = $1.value % 100; + pc->month = ($1.value / 100) % 100; + pc->year.value = $1.value / 10000; + pc->year.digits = $1.digits - 4; + } + else { + pc->times_seen++; + if ($1.digits <= 2) { + pc->hour = $1.value; + pc->minutes = 0; + } + else { + pc->hour = $1.value / 100; + pc->minutes = $1.value % 100; + } + pc->seconds.tv_sec = 0; + pc->seconds.tv_nsec = 0; + pc->meridian = MER24; + } + } +} + +; +/* *INDENT-OFF* */ + +o_colon_minutes: + /* empty */ + { $$ = -1; } + | ':' tUNUMBER + { $$ = $2.value; } + ; + +o_merid: + /* empty */ + { $$ = MER24; } + | tMERIDIAN + { $$ = $1; } + ; + +%% + +static table const meridian_table[] = +{ + { "AM", tMERIDIAN, MERam }, + { "A.M.", tMERIDIAN, MERam }, + { "PM", tMERIDIAN, MERpm }, + { "P.M.", tMERIDIAN, MERpm }, + { NULL, 0, 0 } +}; + +static table const dst_table[] = +{ + { "DST", tDST, 0 } +}; + +static table const month_and_day_table[] = +{ + { "JANUARY", tMONTH, 1 }, + { "FEBRUARY", tMONTH, 2 }, + { "MARCH", tMONTH, 3 }, + { "APRIL", tMONTH, 4 }, + { "MAY", tMONTH, 5 }, + { "JUNE", tMONTH, 6 }, + { "JULY", tMONTH, 7 }, + { "AUGUST", tMONTH, 8 }, + { "SEPTEMBER",tMONTH, 9 }, + { "SEPT", tMONTH, 9 }, + { "OCTOBER", tMONTH, 10 }, + { "NOVEMBER", tMONTH, 11 }, + { "DECEMBER", tMONTH, 12 }, + { "SUNDAY", tDAY, 0 }, + { "MONDAY", tDAY, 1 }, + { "TUESDAY", tDAY, 2 }, + { "TUES", tDAY, 2 }, + { "WEDNESDAY",tDAY, 3 }, + { "WEDNES", tDAY, 3 }, + { "THURSDAY", tDAY, 4 }, + { "THUR", tDAY, 4 }, + { "THURS", tDAY, 4 }, + { "FRIDAY", tDAY, 5 }, + { "SATURDAY", tDAY, 6 }, + { NULL, 0, 0 } +}; + +static table const time_units_table[] = +{ + { "YEAR", tYEAR_UNIT, 1 }, + { "MONTH", tMONTH_UNIT, 1 }, + { "FORTNIGHT",tDAY_UNIT, 14 }, + { "WEEK", tDAY_UNIT, 7 }, + { "DAY", tDAY_UNIT, 1 }, + { "HOUR", tHOUR_UNIT, 1 }, + { "MINUTE", tMINUTE_UNIT, 1 }, + { "MIN", tMINUTE_UNIT, 1 }, + { "SECOND", tSEC_UNIT, 1 }, + { "SEC", tSEC_UNIT, 1 }, + { NULL, 0, 0 } +}; + +/* Assorted relative-time words. */ +static table const relative_time_table[] = +{ + { "TOMORROW", tDAY_UNIT, 1 }, + { "YESTERDAY",tDAY_UNIT, -1 }, + { "TODAY", tDAY_UNIT, 0 }, + { "NOW", tDAY_UNIT, 0 }, + { "LAST", tORDINAL, -1 }, + { "THIS", tORDINAL, 0 }, + { "NEXT", tORDINAL, 1 }, + { "FIRST", tORDINAL, 1 }, +/*{ "SECOND", tORDINAL, 2 }, */ + { "THIRD", tORDINAL, 3 }, + { "FOURTH", tORDINAL, 4 }, + { "FIFTH", tORDINAL, 5 }, + { "SIXTH", tORDINAL, 6 }, + { "SEVENTH", tORDINAL, 7 }, + { "EIGHTH", tORDINAL, 8 }, + { "NINTH", tORDINAL, 9 }, + { "TENTH", tORDINAL, 10 }, + { "ELEVENTH", tORDINAL, 11 }, + { "TWELFTH", tORDINAL, 12 }, + { "AGO", tAGO, 1 }, + { NULL, 0, 0 } +}; + +/* The universal time zone table. These labels can be used even for + time stamps that would not otherwise be valid, e.g., GMT time + stamps in London during summer. */ +static table const universal_time_zone_table[] = +{ + { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */ + { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */ + { "UTC", tZONE, HOUR ( 0) }, + { NULL, 0, 0 } +}; + +/* The time zone table. This table is necessarily incomplete, as time + zone abbreviations are ambiguous; e.g. Australians interpret "EST" + as Eastern time in Australia, not as US Eastern Standard Time. + You cannot rely on getdate to handle arbitrary time zone + abbreviations; use numeric abbreviations like `-0500' instead. */ +static table const time_zone_table[] = +{ + { "WET", tZONE, HOUR ( 0) }, /* Western European */ + { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */ + { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */ + { "ART", tZONE, -HOUR ( 3) }, /* Argentina */ + { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */ + { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */ + { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */ + { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */ + { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */ + { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */ + { "CLT", tZONE, -HOUR ( 4) }, /* Chile */ + { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */ + { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */ + { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */ + { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */ + { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */ + { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */ + { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */ + { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */ + { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */ + { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */ + { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */ + { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */ + { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */ + { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */ + { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */ + { "WAT", tZONE, HOUR ( 1) }, /* West Africa */ + { "CET", tZONE, HOUR ( 1) }, /* Central European */ + { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */ + { "MET", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */ + { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */ + { "EET", tZONE, HOUR ( 2) }, /* Eastern European */ + { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */ + { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */ + { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */ + { "EAT", tZONE, HOUR ( 3) }, /* East Africa */ + { "MSK", tZONE, HOUR ( 3) }, /* Moscow */ + { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */ + { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */ + { "SGT", tZONE, HOUR ( 8) }, /* Singapore */ + { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */ + { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */ + { "GST", tZONE, HOUR (10) }, /* Guam Standard */ + { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */ + { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */ + { NULL, 0, 0 } +}; + +/* Military time zone table. */ +static table const military_table[] = +{ + { "A", tZONE, -HOUR ( 1) }, + { "B", tZONE, -HOUR ( 2) }, + { "C", tZONE, -HOUR ( 3) }, + { "D", tZONE, -HOUR ( 4) }, + { "E", tZONE, -HOUR ( 5) }, + { "F", tZONE, -HOUR ( 6) }, + { "G", tZONE, -HOUR ( 7) }, + { "H", tZONE, -HOUR ( 8) }, + { "I", tZONE, -HOUR ( 9) }, + { "K", tZONE, -HOUR (10) }, + { "L", tZONE, -HOUR (11) }, + { "M", tZONE, -HOUR (12) }, + { "N", tZONE, HOUR ( 1) }, + { "O", tZONE, HOUR ( 2) }, + { "P", tZONE, HOUR ( 3) }, + { "Q", tZONE, HOUR ( 4) }, + { "R", tZONE, HOUR ( 5) }, + { "S", tZONE, HOUR ( 6) }, + { "T", tZONE, HOUR ( 7) }, + { "U", tZONE, HOUR ( 8) }, + { "V", tZONE, HOUR ( 9) }, + { "W", tZONE, HOUR (10) }, + { "X", tZONE, HOUR (11) }, + { "Y", tZONE, HOUR (12) }, + { "Z", tZONE, HOUR ( 0) }, + { NULL, 0, 0 } +}; + +/* *INDENT-ON* */ + +/* Convert a time zone expressed as HH:MM into an integer count of + minutes. If MM is negative, then S is of the form HHMM and needs + to be picked apart; otherwise, S is of the form HH. */ + +static long int time_zone_hhmm(textint s, long int mm) +{ + if (mm < 0) + return (s.value / 100) * 60 + s.value % 100; + else + return s.value * 60 + (s.negative ? -mm : mm); +} + +static int to_hour(long int hours, int meridian) +{ + switch (meridian) { + default: /* Pacify GCC. */ + case MER24: + return 0 <= hours && hours < 24 ? hours : -1; + case MERam: + return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1; + case MERpm: + return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1; + } +} + +static long int to_year(textint textyear) +{ + long int year = textyear.value; + + if (year < 0) + year = -year; + + /* XPG4 suggests that years 00-68 map to 2000-2068, and + years 69-99 map to 1969-1999. */ + else if (textyear.digits == 2) + year += year < 69 ? 2000 : 1900; + + return year; +} + +static table const *lookup_zone(parser_control const *pc, char const *name) +{ + table const *tp; + + for (tp = universal_time_zone_table; tp->name; tp++) + if (strcmp(name, tp->name) == 0) + return tp; + + /* Try local zone abbreviations before those in time_zone_table, as + the local ones are more likely to be right. */ + for (tp = pc->local_time_zone_table; tp->name; tp++) + if (strcmp(name, tp->name) == 0) + return tp; + + for (tp = time_zone_table; tp->name; tp++) + if (strcmp(name, tp->name) == 0) + return tp; + + return NULL; +} + +/* Yield the difference between *A and *B, + measured in seconds, ignoring leap seconds. + The body of this function is taken directly from the GNU C Library; + see src/strftime.c. */ +static long int tm_diff(struct tm const *a, struct tm const *b) +{ + /* Compute intervening leap days correctly even if year is negative. + Take care to avoid int overflow in leap day calculations. */ + int a4 = SHR(a->tm_year, 2) + SHR(TM_YEAR_BASE, 2) - !(a->tm_year & 3); + int b4 = SHR(b->tm_year, 2) + SHR(TM_YEAR_BASE, 2) - !(b->tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = SHR(a100, 2); + int b400 = SHR(b100, 2); + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + long int ayear = a->tm_year; + long int years = ayear - b->tm_year; + long int days = (365 * years + intervening_leap_days + + (a->tm_yday - b->tm_yday)); + return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} + +static table const *lookup_word(parser_control const *pc, char *word) +{ + char *p; + char *q; + size_t wordlen; + table const *tp; + bool period_found; + bool abbrev; + + /* Make it uppercase. */ + for (p = word; *p; p++) { + unsigned char ch = *p; + *p = toupper(ch); + } + + for (tp = meridian_table; tp->name; tp++) + if (strcmp(word, tp->name) == 0) + return tp; + + /* See if we have an abbreviation for a month. */ + wordlen = strlen(word); + abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.'); + + for (tp = month_and_day_table; tp->name; tp++) + if ((abbrev ? strncmp(word, tp->name, 3) : strcmp(word, tp->name)) == 0) + return tp; + + if ((tp = lookup_zone(pc, word))) + return tp; + + if (strcmp(word, dst_table[0].name) == 0) + return dst_table; + + for (tp = time_units_table; tp->name; tp++) + if (strcmp(word, tp->name) == 0) + return tp; + + /* Strip off any plural and try the units table again. */ + if (word[wordlen - 1] == 'S') { + word[wordlen - 1] = '\0'; + for (tp = time_units_table; tp->name; tp++) + if (strcmp(word, tp->name) == 0) + return tp; + word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */ + } + + for (tp = relative_time_table; tp->name; tp++) + if (strcmp(word, tp->name) == 0) + return tp; + + /* Military time zones. */ + if (wordlen == 1) + for (tp = military_table; tp->name; tp++) + if (word[0] == tp->name[0]) + return tp; + + /* Drop out any periods and try the time zone table again. */ + for (period_found = false, p = q = word; (*p = *q); q++) + if (*q == '.') + period_found = true; + else + p++; + if (period_found && (tp = lookup_zone(pc, word))) + return tp; + + return NULL; +} + +static int yylex(union YYSTYPE * lvalp, parser_control * pc) +{ + unsigned char c; + size_t count; + + for (;;) { + while (c = *pc->input, isspace(c)) + pc->input++; + + if (ISDIGIT(c) || c == '-' || c == '+') { + char const *p; + int sign; + unsigned long int value; + if (c == '-' || c == '+') { + sign = c == '-' ? -1 : 1; + while (c = *++pc->input, isspace(c)) + continue; + if (!ISDIGIT(c)) + /* skip the '-' sign */ + continue; + } + else + sign = 0; + p = pc->input; + for (value = 0;; value *= 10) { + unsigned long int value1 = value + (c - '0'); + if (value1 < value) + return '?'; + value = value1; + c = *++p; + if (!ISDIGIT(c)) + break; + if (ULONG_MAX / 10 < value) + return '?'; + } + if ((c == '.' || c == ',') && ISDIGIT(p[1])) { + time_t s; + int ns; + int digits; + unsigned long int value1; + + /* Check for overflow when converting value to time_t. */ + if (sign < 0) { + s = -value; + if (0 < s) + return '?'; + value1 = -s; + } + else { + s = value; + if (s < 0) + return '?'; + value1 = s; + } + if (value != value1) + return '?'; + + /* Accumulate fraction, to ns precision. */ + p++; + ns = *p++ - '0'; + for (digits = 2; digits <= LOG10_BILLION; digits++) { + ns *= 10; + if (ISDIGIT(*p)) + ns += *p++ - '0'; + } + + /* Skip excess digits, truncating toward -Infinity. */ + if (sign < 0) + for (; ISDIGIT(*p); p++) + if (*p != '0') { + ns++; + break; + } + while (ISDIGIT(*p)) + p++; + + /* Adjust to the timespec convention, which is that + tv_nsec is always a positive offset even if tv_sec is + negative. */ + if (sign < 0 && ns) { + s--; + if (!(s < 0)) + return '?'; + ns = BILLION - ns; + } + + lvalp->timespec.tv_sec = s; + lvalp->timespec.tv_nsec = ns; + pc->input = p; + return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER; + } + else { + lvalp->textintval.negative = sign < 0; + if (sign < 0) { + lvalp->textintval.value = -value; + if (0 < lvalp->textintval.value) + return '?'; + } + else { + lvalp->textintval.value = value; + if (lvalp->textintval.value < 0) + return '?'; + } + lvalp->textintval.digits = p - pc->input; + pc->input = p; + return sign ? tSNUMBER : tUNUMBER; + } + } + + if (isalpha(c)) { + char buff[20]; + char *p = buff; + table const *tp; + + do { + if (p < buff + sizeof buff - 1) + *p++ = c; + c = *++pc->input; + } + while (isalpha(c) || c == '.'); + + *p = '\0'; + tp = lookup_word(pc, buff); + if (!tp) + return '?'; + lvalp->intval = tp->value; + return tp->type; + } + + if (c != '(') + return *pc->input++; + count = 0; + do { + c = *pc->input++; + if (c == '\0') + return c; + if (c == '(') + count++; + else if (c == ')') + count--; + } + while (count != 0); + } +} + +/* Do nothing if the parser reports an error. */ +static int +yyerror(parser_control const *pc ATTRIBUTE_UNUSED, + char const *s ATTRIBUTE_UNUSED) +{ + return 0; +} + +/* If *TM0 is the old and *TM1 is the new value of a struct tm after + passing it to mktime, return true if it's OK that mktime returned T. + It's not OK if *TM0 has out-of-range members. */ + +static bool mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t) +{ + if (t == (time_t) - 1) { + /* Guard against falsely reporting an error when parsing a time + stamp that happens to equal (time_t) -1, on a host that + supports such a time stamp. */ + tm1 = pmLocaltime(&t, (struct tm *)&tm1); + if (!tm1) + return false; + } + + return !((tm0->tm_sec ^ tm1->tm_sec) + | (tm0->tm_min ^ tm1->tm_min) + | (tm0->tm_hour ^ tm1->tm_hour) + | (tm0->tm_mday ^ tm1->tm_mday) + | (tm0->tm_mon ^ tm1->tm_mon) + | (tm0->tm_year ^ tm1->tm_year)); +} + +/* A reasonable upper bound for the size of ordinary TZ strings. + Use heap allocation if TZ's length exceeds this. */ +enum { TZBUFSIZE = 100 }; + +/* Parse a date/time string, storing the resulting time value into *RESULT. + The string itself is pointed to by P. Return true if successful. + P can be an incomplete or relative time specification; if so, use + *NOW as the basis for the returned time. */ +int +__pmGlibGetDate(struct timespec *result, char const *p, + struct timespec const *now) +{ + time_t Start; + long int Start_ns; + struct tm tmpbuf; + struct tm const *tmp = &tmpbuf; + struct tm tm; + struct tm tm0; + parser_control pc; + struct timespec gettime_buffer; + unsigned char c; + int ok = 0; + + if (!now) { + __pmGetTimespec(&gettime_buffer); + now = &gettime_buffer; + } + + Start = now->tv_sec; + Start_ns = now->tv_nsec; + + tmp = pmLocaltime(&now->tv_sec, (struct tm *)tmp); + if (!tmp) + return -1; + + while (c = *p, isspace(c)) + p++; + + pc.input = p; + pc.year.value = tmp->tm_year; + pc.year.value += TM_YEAR_BASE; + pc.year.digits = 0; + pc.month = tmp->tm_mon + 1; + pc.day = tmp->tm_mday; + pc.hour = tmp->tm_hour; + pc.minutes = tmp->tm_min; + pc.seconds.tv_sec = tmp->tm_sec; + pc.seconds.tv_nsec = Start_ns; + tm.tm_isdst = tmp->tm_isdst; + + pc.meridian = MER24; + pc.rel = RELATIVE_TIME_0; + pc.timespec_seen = false; + pc.rels_seen = false; + pc.dates_seen = 0; + pc.days_seen = 0; + pc.times_seen = 0; + pc.local_zones_seen = 0; + pc.dsts_seen = 0; + pc.zones_seen = 0; + pc.local_time_zone_table[0].name = NULL; + + pc.local_time_zone_table[0].name = NULL; + + if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name + && !strcmp(pc.local_time_zone_table[0].name, + pc.local_time_zone_table[1].name)) { + /* This locale uses the same abbrevation for standard and + daylight times. So if we see that abbreviation, we don't + know whether it's daylight time. */ + pc.local_time_zone_table[0].value = -1; + pc.local_time_zone_table[1].name = NULL; + } + + if (yyparse(&pc) != 0) + goto fail; + + if (pc.timespec_seen) + *result = pc.seconds; + else { + if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen + | (pc.local_zones_seen + pc.zones_seen))) + goto fail; + + tm.tm_year = to_year(pc.year) - TM_YEAR_BASE; + tm.tm_mon = pc.month - 1; + tm.tm_mday = pc.day; + if (pc.times_seen || (pc.rels_seen && !pc.dates_seen && !pc.days_seen)) { + tm.tm_hour = to_hour(pc.hour, pc.meridian); + if (tm.tm_hour < 0) + goto fail; + tm.tm_min = pc.minutes; + tm.tm_sec = pc.seconds.tv_sec; + } + else { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + pc.seconds.tv_nsec = 0; + } + + /* Let mktime deduce tm_isdst if we have an absolute time stamp. */ + if (pc.dates_seen | pc.days_seen | pc.times_seen) + tm.tm_isdst = -1; + + /* But if the input explicitly specifies local time with or without + DST, give mktime that information. */ + if (pc.local_zones_seen) + tm.tm_isdst = pc.local_isdst; + + tm0 = tm; + + Start = __pmMktime(&tm); + + if (!mktime_ok(&tm0, &tm, Start)) + goto fail; + + if (pc.days_seen && !pc.dates_seen) { + tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + + 7 * (pc.day_ordinal - (0 < pc.day_ordinal))); + tm.tm_isdst = -1; + Start = __pmMktime(&tm); + if (Start == (time_t) - 1) + goto fail; + } + + if (pc.zones_seen) { + long int delta = pc.time_zone * 60; + time_t t1; + time_t t = Start; + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + struct tm *gmt = NULL; + gmt = gmtime(&t); + PM_UNLOCK(__pmLock_libpcp); + if (!gmt) + goto fail; + delta -= tm_diff(&tm, gmt); + t1 = Start - delta; + if ((Start < t1) != (delta < 0)) + goto fail; /* time_t overflow */ + Start = t1; + } + + /* Add relative date. */ + if (pc.rel.year | pc.rel.month | pc.rel.day) { + int year = tm.tm_year + pc.rel.year; + int month = tm.tm_mon + pc.rel.month; + int day = tm.tm_mday + pc.rel.day; + if (((year < tm.tm_year) ^ (pc.rel.year < 0)) + | ((month < tm.tm_mon) ^ (pc.rel.month < 0)) + | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) + goto fail; + tm.tm_year = year; + tm.tm_mon = month; + tm.tm_mday = day; + tm.tm_hour = tm0.tm_hour; + tm.tm_min = tm0.tm_min; + tm.tm_sec = tm0.tm_sec; + tm.tm_isdst = tm0.tm_isdst; + Start = __pmMktime(&tm); + if (Start == (time_t) - 1) + goto fail; + } + + /* Add relative hours, minutes, and seconds. On hosts that support + leap seconds, ignore the possibility of leap seconds; e.g., + "+ 10 minutes" adds 600 seconds, even if one of them is a + leap second. Typically this is not what the user wants, but it's + too hard to do it the other way, because the time zone indicator + must be applied before relative times, and if mktime is applied + again the time zone will be lost. */ + { + long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns; + long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION; + time_t t0 = Start; + long int d1 = 60 * 60 * pc.rel.hour; + time_t t1 = t0 + d1; + long int d2 = 60 * pc.rel.minutes; + time_t t2 = t1 + d2; + long int d3 = pc.rel.seconds; + time_t t3 = t2 + d3; + long int d4 = (sum_ns - normalized_ns) / BILLION; + time_t t4 = t3 + d4; + + if ((d1 / (60 * 60) ^ pc.rel.hour) + | (d2 / 60 ^ pc.rel.minutes) + | ((t1 < t0) ^ (d1 < 0)) + | ((t2 < t1) ^ (d2 < 0)) + | ((t3 < t2) ^ (d3 < 0)) + | ((t4 < t3) ^ (d4 < 0))) + goto fail; + + result->tv_sec = t4; + result->tv_nsec = normalized_ns; + } + } + + goto done; + + fail: + ok = -1; + done: + return ok; +} diff --git a/src/libpcp/src/getopt.c b/src/libpcp/src/getopt.c new file mode 100644 index 0000000..a7e9558 --- /dev/null +++ b/src/libpcp/src/getopt.c @@ -0,0 +1,1518 @@ +/* + * Common argument parsing for all PMAPI client tools. + * + * Copyright (c) 2014 Red Hat. + * Copyright (C) 1987-2014 Free Software Foundation, 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include <ctype.h> + +#if !defined(HAVE_UNDERBAR_ENVIRON) +#define _environ environ +#endif + +enum { + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +}; + +/* + * Using the current archive context, extract start and end + * times and adjust the time window boundaries accordingly. + */ +static int +__pmUpdateBounds(pmOptions *opts, int index, struct timeval *begin, struct timeval *end) +{ + struct timeval logend; + pmLogLabel label; + int sts; + + if ((sts = pmGetArchiveLabel(&label)) < 0) { + pmprintf("%s: Cannot get archive %s label record: %s\n", + pmProgname, opts->archives[index], pmErrStr(sts)); + return sts; + } + if ((sts = pmGetArchiveEnd(&logend)) < 0) { + logend.tv_sec = INT_MAX; + logend.tv_usec = 0; + fflush(stdout); + fprintf(stderr, "%s: Cannot locate end of archive %s: %s\n", + pmProgname, opts->archives[index], pmErrStr(sts)); + fprintf(stderr, "\nWARNING: " + "This archive is sufficiently damaged that it may not be possible to\n"); + fprintf(stderr, " " + "produce complete information. Continuing and hoping for the best.\n\n"); + fflush(stderr); + } + + if (index == 0) { + /* the first archive in the set forms the initial boundaries */ + *begin = label.ll_start; + *end = logend; + } else { + /* must now check if this archive pre- or post- dates others */ + if (__pmtimevalSub(begin, &label.ll_start) > 0.0) + *begin = label.ll_start; + if (__pmtimevalSub(end, &logend) < 0.0) + *end = logend; + } + return 0; +} + +/* + * Calculate time window boundaries depending on context type. + * In multi-archive context, this means opening all of them and + * defining the boundary as being from the start of the earliest + * through to the end of the last-written archive. + * + * Note - called with an active context via pmGetContextOptions. + */ +static int +__pmBoundaryOptions(pmOptions *opts, struct timeval *begin, struct timeval *end) +{ + int i, ctx, sts = 0; + + if (opts->context != PM_CONTEXT_ARCHIVE) { + /* live/local context, open ended - start now, never end */ + __pmtimevalNow(begin); + end->tv_sec = INT_MAX; + end->tv_usec = 0; + } else if (opts->narchives == 1) { + /* singular archive context, make use of current context */ + sts = __pmUpdateBounds(opts, 0, begin, end); + } else { + /* multiple archives - figure out combined start and end */ + for (i = 0; i < opts->narchives; i++) { + sts = pmNewContext(PM_CONTEXT_ARCHIVE, opts->archives[i]); + if (sts < 0) { + pmprintf("%s: Cannot open archive %s: %s\n", + pmProgname, opts->archives[i], pmErrStr(sts)); + break; + } + ctx = sts; + sts = __pmUpdateBounds(opts, i, begin, end); + pmDestroyContext(ctx); + if (sts < 0) + break; + } + } + return sts; +} + +/* + * Final stages of argument parsing, anything that needs to wait + * until after we have a context - e.g. timezones, time windows. + */ +int +pmGetContextOptions(int ctxid, pmOptions *opts) +{ + int window = (opts->start_optarg || opts->finish_optarg || + opts->align_optarg || opts->origin_optarg) || + (opts->flags & PM_OPTFLAG_BOUNDARIES); + int tzh; + + /* timezone setup */ + if (opts->tzflag) { + char hostname[MAXHOSTNAMELEN]; + + pmGetContextHostName_r(ctxid, hostname, MAXHOSTNAMELEN); + if ((tzh = pmNewContextZone()) < 0) { + pmprintf("%s: Cannot set context timezone: %s\n", + pmProgname, pmErrStr(tzh)); + opts->errors++; + } + else if (opts->flags & PM_OPTFLAG_STDOUT_TZ) { + printf("Note: timezone set to local timezone of host \"%s\"%s\n\n", + hostname, + opts->context != PM_CONTEXT_ARCHIVE ? "" : " from archive"); + } + } + else if (opts->timezone) { + if ((tzh = pmNewZone(opts->timezone)) < 0) { + pmprintf("%s: Cannot set timezone to \"%s\": %s\n", + pmProgname, opts->timezone, pmErrStr(tzh)); + opts->errors++; + } + else if (opts->flags & PM_OPTFLAG_STDOUT_TZ) { + printf("Note: timezone set to \"TZ=%s\"\n\n", opts->timezone); + } + } + + /* time window setup */ + if (!opts->errors && window) { + struct timeval first_boundary, last_boundary; + char *msg; + + if (__pmBoundaryOptions(opts, &first_boundary, &last_boundary) < 0) + opts->errors++; + else if (pmParseTimeWindow( + opts->start_optarg, opts->finish_optarg, + opts->align_optarg, opts->origin_optarg, + &first_boundary, &last_boundary, + &opts->start, &opts->finish, &opts->origin, + &msg) < 0) { + pmprintf("%s: invalid time window: %s\n", pmProgname, msg); + opts->errors++; + free(msg); + } + } + + if (opts->errors) { + if (!(opts->flags & PM_OPTFLAG_USAGE_ERR)) + opts->flags |= PM_OPTFLAG_RUNTIME_ERR; + return PM_ERR_GENERIC; + } + + return 0; +} + +/* + * All arguments have been parsed at this point (both internal and external). + * We can now perform any final processing that could not be done earlier. + * + * Note that some end processing requires a context (in particular, the + * "time window" processing, which may require timezone setup, and so on). + * Such processing is deferred to pmGetContextOptions(). + */ +void +__pmEndOptions(pmOptions *opts) +{ + if (opts->flags & PM_OPTFLAG_DONE) + return; + + /* inform caller of the struct version used */ + if (opts->version != PMAPI_VERSION_2) + opts->version = PMAPI_VERSION_2; + + if (!opts->context) { + if (opts->Lflag) + opts->context = PM_CONTEXT_LOCAL; + else if (opts->nhosts && !opts->narchives) + opts->context = PM_CONTEXT_HOST; + else if (opts->narchives && !opts->nhosts) + opts->context = PM_CONTEXT_ARCHIVE; + } + + if ((opts->start_optarg || opts->align_optarg || opts->origin_optarg) && + opts->context != PM_CONTEXT_ARCHIVE) { + pmprintf("%s: time window options are supported for archives only\n", + pmProgname); + opts->errors++; + } + + if (opts->tzflag && opts->context != PM_CONTEXT_ARCHIVE && + opts->context != PM_CONTEXT_HOST) { + pmprintf("%s: use of timezone from metric source requires a source\n", + pmProgname); + opts->errors++; + } + + if (opts->errors && !(opts->flags & PM_OPTFLAG_RUNTIME_ERR)) + opts->flags |= PM_OPTFLAG_USAGE_ERR; + opts->flags |= PM_OPTFLAG_DONE; +} + +static void +__pmSetAlignment(pmOptions *opts, char *arg) +{ + opts->align_optarg = arg; +} + +static void +__pmSetOrigin(pmOptions *opts, char *arg) +{ + opts->origin_optarg = arg; +} + +static void +__pmSetStartTime(pmOptions *opts, char *arg) +{ + opts->start_optarg = arg; +} + +static void +__pmSetDebugFlag(pmOptions *opts, char *arg) +{ + int sts; + + if ((sts = __pmParseDebug(arg)) < 0) { + pmprintf("%s: unrecognized debug flag specification (%s)\n", + pmProgname, arg); + opts->errors++; + } + else { + pmDebug |= sts; + } +} + +static void +__pmSetGuiModeFlag(pmOptions *opts) +{ + if (opts->guiport_optarg) { + pmprintf("%s: at most one of -g and -p allowed\n", pmProgname); + opts->errors++; + } else { + opts->guiflag = 1; + } +} + +static void +__pmSetGuiPort(pmOptions *opts, char *arg) +{ + char *endnum; + + if (opts->guiflag) { + pmprintf("%s: at most one of -g and -p allowed\n", pmProgname); + opts->errors++; + } else { + opts->guiport_optarg = arg; + opts->guiport = (int)strtol(arg, &endnum, 10); + if (*endnum != '\0' || opts->guiport < 0) + opts->guiport = 0; + } +} + +void +__pmAddOptArchive(pmOptions *opts, char *arg) +{ + char **archives = opts->archives; + size_t size = sizeof(char *) * (opts->narchives + 1); + + if (opts->narchives && !(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: too many archives requested: %s\n", pmProgname, arg); + opts->errors++; + } else if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one host or archive allowed\n", pmProgname); + opts->errors++; + } else if ((archives = realloc(archives, size)) != NULL) { + archives[opts->narchives] = arg; + opts->archives = archives; + opts->narchives++; + } else { + __pmNoMem("pmGetOptions(archive)", size, PM_FATAL_ERR); + } +} + +static char * +comma_or_end(const char *start) +{ + char *end; + + if ((end = strchr(start, ',')) != NULL) + return end; + if (*start == '\0') + return NULL; + end = (char *)start + strlen(start); + return end; +} + +void +__pmAddOptArchiveList(pmOptions *opts, char *arg) +{ + char *start = arg, *end; + + if (!(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: too many archives requested: %s\n", pmProgname, arg); + opts->errors++; + } else if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one of hosts or archives allowed\n", pmProgname); + opts->errors++; + } else { + while ((end = comma_or_end(start)) != NULL) { + size_t size = sizeof(char *) * (opts->narchives + 1); + size_t length = end - start; + char **archives = opts->archives; + char *archive; + + if (length == 0) + goto next; + + if ((archives = realloc(archives, size)) != NULL) { + if ((archive = strndup(start, length)) != NULL) { + archives[opts->narchives] = archive; + opts->archives = archives; + opts->narchives++; + } else { + __pmNoMem("pmGetOptions(archive)", length, PM_FATAL_ERR); + } + } else { + __pmNoMem("pmGetOptions(archives)", size, PM_FATAL_ERR); + } + next: + start = (*end == '\0') ? end : end + 1; + } + } +} + +void +__pmAddOptHost(pmOptions *opts, char *arg) +{ + char **hosts = opts->hosts; + size_t size = sizeof(char *) * (opts->nhosts + 1); + + if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg); + opts->errors++; + } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one host or archive allowed\n", pmProgname); + opts->errors++; + } else if ((hosts = realloc(hosts, size)) != NULL) { + hosts[opts->nhosts] = arg; + opts->hosts = hosts; + opts->nhosts++; + } else { + __pmNoMem("pmGetOptions(host)", size, PM_FATAL_ERR); + } +} + +static inline char * +skip_whitespace(char *p) +{ + while (*p && isspace((int)*p) && *p != '\n') + p++; + return p; +} + +static inline char * +skip_nonwhitespace(char *p) +{ + while (*p && !isspace((int)*p)) + p++; + return p; +} + +void +__pmAddOptArchiveFolio(pmOptions *opts, char *arg) +{ + char buffer[MAXPATHLEN]; + FILE *fp; + +#define FOLIO_MAGIC "PCPFolio" +#define FOLIO_VERSION "Version: 1" + + if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one of hosts or archives allowed\n", pmProgname); + opts->errors++; + } else if ((fp = fopen(arg, "r")) == NULL) { + pmprintf("%s: cannot open archive folio %s: %s\n", pmProgname, + arg, pmErrStr_r(-oserror(), buffer, sizeof(buffer))); + opts->flags |= PM_OPTFLAG_RUNTIME_ERR; + opts->errors++; + } else { + size_t length; + char *p, *log, *dir; + int line, sep = __pmPathSeparator(); + + if (fgets(buffer, sizeof(buffer)-1, fp) == NULL) { + pmprintf("%s: archive folio %s has no header\n", pmProgname, arg); + goto badfolio; + } + if (strncmp(buffer, FOLIO_MAGIC, sizeof(FOLIO_MAGIC)-1) != 0) { + pmprintf("%s: archive folio %s has bad magic\n", pmProgname, arg); + goto badfolio; + } + if (fgets(buffer, sizeof(buffer)-1, fp) == NULL) { + pmprintf("%s: archive folio %s has no version\n", pmProgname, arg); + goto badfolio; + } + if (strncmp(buffer, FOLIO_VERSION, sizeof(FOLIO_VERSION)-1) != 0) { + pmprintf("%s: unknown version archive folio %s\n", pmProgname, arg); + goto badfolio; + } + + line = 2; + dir = dirname(arg); + + while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) { + line++; + p = buffer; + + if (strncmp(p, "Archive:", sizeof("Archive:")-1) != 0) + continue; + p = skip_nonwhitespace(p); + p = skip_whitespace(p); + if (*p == '\n') { + pmprintf("%s: missing host on archive folio line %d\n", + pmProgname, line); + goto badfolio; + } + p = skip_nonwhitespace(p); + p = skip_whitespace(p); + if (*p == '\n') { + pmprintf("%s: missing path on archive folio line %d\n", + pmProgname, line); + goto badfolio; + } + + log = p; + p = skip_nonwhitespace(p); + *p = '\0'; + + length = strlen(dir) + 1 + strlen(log) + 1; + if ((p = (char *)malloc(length)) == NULL) + __pmNoMem("pmGetOptions(archive)", length, PM_FATAL_ERR); + snprintf(p, length, "%s%c%s", dir, sep, log); + __pmAddOptArchive(opts, p); + } + + fclose(fp); + } + return; + +badfolio: + fclose(fp); + opts->flags |= PM_OPTFLAG_RUNTIME_ERR; + opts->errors++; +} + +static void +__pmAddOptHostFile(pmOptions *opts, char *arg) +{ + if (!(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg); + opts->errors++; + } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one of hosts or archives allowed\n", pmProgname); + opts->errors++; + } else { + FILE *fp = fopen(arg, "r"); + + if (fp) { + char buffer[MAXHOSTNAMELEN]; + + while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) { + size_t size = sizeof(char *) * (opts->nhosts + 1); + char **hosts = opts->hosts; + char *host, *p = buffer; + size_t length; + + while (isspace((int)*p) && *p != '\n') + p++; + if (*p == '\n' || *p == '#') + continue; + host = p; + length = 0; + while (*p != '\n' && *p != '#' && !isspace((int)*p)) + length++; + p += length; + *p = '\0'; + if ((hosts = realloc(hosts, size)) != NULL) { + if ((host = strndup(host, length)) != NULL) { + hosts[opts->nhosts] = host; + opts->hosts = hosts; + opts->nhosts++; + } else { + __pmNoMem("pmGetOptions(host)", length, PM_FATAL_ERR); + } + } else { + __pmNoMem("pmGetOptions(hosts)", size, PM_FATAL_ERR); + } + } + + fclose(fp); + } else { + char errmsg[PM_MAXERRMSGLEN]; + + pmprintf("%s: cannot open hosts file %s: %s\n", pmProgname, arg, + osstrerror_r(errmsg, sizeof(errmsg))); + opts->flags |= PM_OPTFLAG_RUNTIME_ERR; + opts->errors++; + } + } +} + +void +__pmAddOptHostList(pmOptions *opts, char *arg) +{ + if (!(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg); + opts->errors++; + } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) { + pmprintf("%s: only one of hosts or archives allowed\n", pmProgname); + opts->errors++; + } else { + char *start = arg, *end; + + while ((end = comma_or_end(start)) != NULL) { + size_t size = sizeof(char *) * (opts->nhosts + 1); + size_t length = end - start; + char **hosts = opts->hosts; + char *host; + + if (length == 0) + goto next; + + if ((hosts = realloc(hosts, size)) != NULL) { + if ((host = strndup(start, length)) != NULL) { + hosts[opts->nhosts] = host; + opts->hosts = hosts; + opts->nhosts++; + } else { + __pmNoMem("pmGetOptions(host)", length, PM_FATAL_ERR); + } + } else { + __pmNoMem("pmGetOptions(hosts)", size, PM_FATAL_ERR); + } + next: + start = (*end == '\0') ? end : end + 1; + } + } +} + +static void +__pmSetLocalContextTable(pmOptions *opts, char *arg) +{ + char *errmsg; + + if ((errmsg = __pmSpecLocalPMDA(arg)) != NULL) { + pmprintf("%s: __pmSpecLocalPMDA failed: %s\n", pmProgname, errmsg); + opts->errors++; + } +} + +static void +__pmSetLocalContextFlag(pmOptions *opts) +{ + if (opts->context && !(opts->flags & PM_OPTFLAG_MULTI)) { + pmprintf("%s: at most one of -a, -h and -L allowed\n", pmProgname); + opts->errors++; + } else { + opts->Lflag = 1; + } +} + +static void +__pmSetNameSpace(pmOptions *opts, char *arg, int dupok) +{ + int sts; + + if ((sts = pmLoadASCIINameSpace(arg, dupok)) < 0) { + pmprintf("%s: Cannot load namespace from \"%s\": %s\n", + pmProgname, arg, pmErrStr(sts)); + opts->flags |= PM_OPTFLAG_RUNTIME_ERR; + opts->errors++; + } else { + opts->nsflag = 1; + } +} + +static void +__pmSetSampleCount(pmOptions *opts, char *arg) +{ + char *endnum; + + if (opts->finish_optarg) { + pmprintf("%s: at most one of -T and -s allowed\n", pmProgname); + opts->errors++; + } else { + opts->samples = (int)strtol(arg, &endnum, 10); + if (*endnum != '\0' || opts->samples < 0) { + pmprintf("%s: -s requires numeric argument\n", pmProgname); + opts->errors++; + } + } +} + +static void +__pmSetFinishTime(pmOptions *opts, char *arg) +{ + if (opts->samples) { + pmprintf("%s: at most one of -T and -s allowed\n", pmProgname); + opts->errors++; + } else { + opts->finish_optarg = arg; + } +} + +static void +__pmSetSampleInterval(pmOptions *opts, char *arg) +{ + char *endnum; + + if (pmParseInterval(arg, &opts->interval, &endnum) < 0) { + pmprintf("%s: -t argument not in pmParseInterval(3) format:\n", + pmProgname); + pmprintf("%s\n", endnum); + opts->errors++; + free(endnum); + } +} + +static void +__pmSetTimeZone(pmOptions *opts, char *arg) +{ + if (opts->tzflag) { + pmprintf("%s: at most one of -Z and -z allowed\n", pmProgname); + opts->errors++; + } else { + opts->timezone = arg; + } +} + +static void +__pmSetHostZone(pmOptions *opts) +{ + if (opts->timezone) { + pmprintf("%s: at most one of -Z and -z allowed\n", pmProgname); + opts->errors++; + } else { + opts->tzflag = 1; + } +} + +/* + * Called once at the start of option processing, before any getopt calls. + * For our needs, we can set default values at this point based on values + * we find set in the processes environment. + */ +void +__pmStartOptions(pmOptions *opts) +{ + extern char **_environ; + char **p, *s, *value = NULL; + + if (opts->flags & PM_OPTFLAG_INIT) + return; + + for (p = _environ; *p != NULL; p++) { + s = *p; + if (strncmp(s, "PCP_", 4) != 0) + continue; /* short circuit if not PCP-prefixed */ + s += 4; + if ((value = strchr(s, '=')) != NULL) { + *value = '\0'; + value++; /* skip over the equals sign */ + } + + if (strcmp(s, "ALIGN_TIME") == 0) + __pmSetAlignment(opts, value); + else if (strcmp(s, "ARCHIVE") == 0) + __pmAddOptArchive(opts, value); + else if (strcmp(s, "ARCHIVE_LIST") == 0) + __pmAddOptArchiveList(opts, value); + else if (strcmp(s, "DEBUG") == 0) + __pmSetDebugFlag(opts, value); + else if (strcmp(s, "FOLIO") == 0) + __pmAddOptArchiveFolio(opts, value); + else if (strcmp(s, "GUIMODE") == 0) + __pmSetGuiModeFlag(opts); + else if (strcmp(s, "HOST") == 0) + __pmAddOptHost(opts, value); + else if (strcmp(s, "HOST_LIST") == 0) + __pmAddOptHostList(opts, value); + else if (strcmp(s, "LOCALMODE") == 0) + __pmSetLocalContextFlag(opts); + else if (strcmp(s, "NAMESPACE") == 0) + __pmSetNameSpace(opts, value, 0); + else if (strcmp(s, "ORIGIN") == 0 || + strcmp(s, "ORIGIN_TIME") == 0) + __pmSetOrigin(opts, value); + else if (strcmp(s, "GUIPORT") == 0) + __pmSetGuiPort(opts, value); + else if (strcmp(s, "START_TIME") == 0) + __pmSetStartTime(opts, value); + else if (strcmp(s, "SAMPLES") == 0) + __pmSetSampleCount(opts, value); + else if (strcmp(s, "FINISH_TIME") == 0) + __pmSetFinishTime(opts, value); + else if (strcmp(s, "INTERVAL") == 0) + __pmSetSampleInterval(opts, value); + else if (strcmp(s, "TIMEZONE") == 0) + __pmSetTimeZone(opts, value); + else if (strcmp(s, "HOSTZONE") == 0) + __pmSetHostZone(opts); + + if (value) /* reset the environment */ + *(value-1) = '='; + } + + opts->flags |= PM_OPTFLAG_INIT; +} + +int +pmGetOptions(int argc, char *const *argv, pmOptions *opts) +{ + pmLongOptions *opt; + int flag = 0; + int c = EOF; + + if (!(opts->flags & PM_OPTFLAG_INIT)) { + __pmSetProgname(argv[0]); + opts->__initialized = 1; + __pmStartOptions(opts); + } + + /* environment has been checked at this stage, leave opt* well alone */ + if (opts->flags & PM_OPTFLAG_ENV_ONLY) { + __pmEndOptions(opts); + return EOF; + } + + while (!flag) { + c = pmgetopt_r(argc, argv, opts); + + /* provide opportunity for overriding the general set of options */ + if (c != EOF && opts->override && opts->override(c, opts)) + break; + + switch (c) { + case 'A': + __pmSetAlignment(opts, opts->optarg); + break; + case 'a': + __pmAddOptArchive(opts, opts->optarg); + break; + case 'D': + __pmSetDebugFlag(opts, opts->optarg); + break; + case 'g': + __pmSetGuiModeFlag(opts); + break; + case 'H': + __pmAddOptHostFile(opts, opts->optarg); + break; + case 'h': + __pmAddOptHost(opts, opts->optarg); + break; + case 'K': + __pmSetLocalContextTable(opts, opts->optarg); + break; + case 'L': + __pmSetLocalContextFlag(opts); + break; + case 'N': + __pmSetNameSpace(opts, opts->optarg, 1); + break; + case 'n': + __pmSetNameSpace(opts, opts->optarg, 0); + break; + case 'O': + __pmSetOrigin(opts, opts->optarg); + break; + case 'p': + __pmSetGuiPort(opts, opts->optarg); + break; + case 'S': + __pmSetStartTime(opts, opts->optarg); + break; + case 's': + __pmSetSampleCount(opts, opts->optarg); + break; + case 'T': + __pmSetFinishTime(opts, opts->optarg); + break; + case 't': + __pmSetSampleInterval(opts, opts->optarg); + break; + case 'V': + opts->flags |= PM_OPTFLAG_EXIT; + pmprintf("%s version %s\n", pmProgname, PCP_VERSION); + break; + case 'Z': + __pmSetTimeZone(opts, opts->optarg); + break; + case 'z': + __pmSetHostZone(opts); + break; + case '?': + opts->errors++; + break; + case 0: + /* long-option-only standard argument handling */ + opt = &opts->long_options[opts->index]; + if (strcmp(opt->long_opt, PMLONGOPT_HOST_LIST) == 0) + __pmAddOptHostList(opts, opts->optarg); + else if (strcmp(opt->long_opt, PMLONGOPT_ARCHIVE_LIST) == 0) + __pmAddOptArchiveList(opts, opts->optarg); + else if (strcmp(opt->long_opt, PMLONGOPT_ARCHIVE_FOLIO) == 0) + __pmAddOptArchiveFolio(opts, opts->optarg); + else + flag = 1; + break; + default: /* pass back out to caller */ + flag = 1; + } + } + + /* end of arguments - process everything we can now */ + if (c == EOF) + __pmEndOptions(opts); + return c; +} + +void +pmFreeOptions(pmOptions *opts) +{ + if (opts->narchives) + free(opts->archives); + if (opts->nhosts) + free(opts->hosts); +} + +void +pmUsageMessage(pmOptions *opts) +{ + pmLongOptions *option; + const char *message; + int bytes; + + if (opts->flags & (PM_OPTFLAG_RUNTIME_ERR|PM_OPTFLAG_EXIT)) + goto flush; + + message = opts->short_usage ? opts->short_usage : "[options]"; + pmprintf("Usage: %s %s\n", pmProgname, message); + + for (option = opts->long_options; option; option++) { + if (!option->long_opt) /* sentinel */ + break; + if (!option->message) /* undocumented option */ + continue; + if (option->short_opt == '-') { /* section header */ + pmprintf("\n%s:\n", option->message); + continue; + } + if (option->short_opt == '|') { /* descriptive text */ + pmprintf("%s\n", option->message); + continue; + } + + message = option->argname ? option->argname : "?"; + if (option->long_opt && option->long_opt[0] != '\0') { + if (option->short_opt && option->has_arg) + bytes = pmprintf(" -%c %s, --%s=%s", option->short_opt, + message, option->long_opt, message); + else if (option->short_opt) + bytes = pmprintf(" -%c, --%s", option->short_opt, + option->long_opt); + else if (option->has_arg) + bytes = pmprintf(" --%s=%s", option->long_opt, message); + else + bytes = pmprintf(" --%s", option->long_opt); + } else { /* short option with no long option */ + if (option->has_arg) + bytes = pmprintf(" -%c %s", option->short_opt, message); + else + bytes = pmprintf(" -%c", option->short_opt); + } + + if (bytes < 24) /* message will fit here */ + pmprintf("%*s%s\n", 24 - bytes, "", option->message); + else /* message on next line */ + pmprintf("\n%24s%s\n", "", option->message); + } +flush: + if (!(opts->flags & PM_OPTFLAG_NOFLUSH)) + pmflush(); +} + +/* + * Exchange two adjacent subsequences of ARGV. + * One subsequence is elements [first_nonopt,last_nonopt) + * which contains all the non-options that have been skipped so far. + * The other is elements [last_nonopt,optind), which contains all + * the options processed since those non-options were skipped. + + * `first_nonopt' and `last_nonopt' are relocated so that they describe + * the new indices of the non-options in ARGV after they are moved. + */ +static void +__pmgetopt_exchange(char **argv, pmOptions *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + char *tem; + + /* + * Exchange the shorter segment with the far end of the longer segment. + * That puts the shorter segment into the right place. + * It leaves the longer segment in the right place overall, + * but it consists of two parts that need to be swapped next. + */ + while (top > middle && middle > bottom) { + if (top - middle > middle - bottom) { + /* Bottom segment is the short one. */ + int len = middle - bottom; + int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else { + /* Top segment is the short one. */ + int len = top - middle; + int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} + +/* + * Initialize the internal data when the first getopt call is made. + */ +static const char * +__pmgetopt_initialize(int argc, char *const *argv, pmOptions *d) +{ + const char *optstring = d->short_options; + int posixly_correct = !!(d->flags & PM_OPTFLAG_POSIX); + + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + * is the program name); the sequence of previously skipped + * non-option ARGV-elements is empty. + */ + d->__first_nonopt = d->__last_nonopt = d->optind; + d->__nextchar = NULL; + d->__posixly_correct = posixly_correct | !!getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + if (optstring[0] == '-') { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + + return optstring; +} + +/* + * Scan elements of ARGV (whose length is ARGC) for option characters + * given in OPTSTRING. + * + * If an element of ARGV starts with '-', and is not exactly "-" or "--", + * then it is an option element. The characters of this element + * (aside from the initial '-') are option characters. If `getopt' + * is called repeatedly, it returns successively each of the option characters + * from each of the option elements. + * + * If `getopt' finds another option character, it returns that character, + * updating `optind' and `nextchar' so that the next call to `getopt' can + * resume the scan with the following option character or ARGV-element. + * + * If there are no more option characters, `getopt' returns -1. + * Then `optind' is the index in ARGV of the first ARGV-element + * that is not an option. (The ARGV-elements have been permuted + * so that those that are not options now come last.) + * + * OPTSTRING is a string containing the legitimate option characters. + * If an option character is seen that is not listed in OPTSTRING, + * return '?' after printing an error message. If you set `opterr' to + * zero, the error message is suppressed but we still return '?'. + * + * If a char in OPTSTRING is followed by a colon, that means it wants an arg, + * so the following text in the same ARGV-element, or the text of the following + * ARGV-element, is returned in `optarg'. Two colons mean an option that + * wants an optional arg; if there is text in the current ARGV-element, + * it is returned in `optarg', otherwise `optarg' is set to zero. + * + * If OPTSTRING starts with `-' or `+', it requests different methods of + * handling the non-option ARGV-elements. + * See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + * + * Long-named options begin with `--' instead of `-'. + * Their names may be abbreviated as long as the abbreviation is unique + * or is an exact match for some defined option. If they have an + * argument, it follows the option name in the same ARGV-element, separated + * from the option name by a `=', or else the in next ARGV-element. + * When `getopt' finds a long-named option, it returns 0 if that option's + * `flag' field is nonzero, the value of the option's `val' field + * if the `flag' field is zero. + * + * The elements of ARGV aren't really const, because we permute them. + * But we pretend they're const in the prototype to be compatible + * with other systems. + * + * LONGOPTS is a vector of `pmOptions' terminated by an + * element containing a name which is zero. + * + * LONGIND returns the index in LONGOPT of the long-named option found. + * It is only valid when a long-named option has been found by the most + * recent call. + * + * If LONG_ONLY is nonzero, '-' as well as '--' can introduce + * long-named options. + */ + +typedef struct pmOptList { + pmLongOptions * p; + struct pmOptList * next; +} pmOptionsList; + +int +pmgetopt_r(int argc, char *const *argv, pmOptions *d) +{ + const char *optstring = d->short_options; + pmLongOptions *longopts = d->long_options; + int *longind = &d->index; + int long_only = (d->flags & PM_OPTFLAG_LONG_ONLY); + int quiet = (d->flags & PM_OPTFLAG_QUIET); + int print_errors = d->opterr || !quiet; + + if (argc < 1 || !optstring) + return -1; + + d->optarg = NULL; + + if (d->optind == 0 || d->__initialized <= 1) { + if (d->optind == 0) + d->optind = 1; /* Don't scan ARGV[0], the program name. */ + if (!d->__initialized) + __pmSetProgname(argv[0]); + optstring = __pmgetopt_initialize(argc, argv, d); + d->__initialized = 2; + } + else if (optstring[0] == '-' || optstring[0] == '+') + optstring++; + if (optstring[0] == ':') + print_errors = 0; + + /* Test whether ARGV[optind] points to a non-option argument. + * Either it does not have option syntax, or there is an environment flag + * from the shell indicating it is not an option. The later information + * is only used when the used in the GNU libc. + */ + + if (d->__nextchar == NULL || *d->__nextchar == '\0') { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + * moved back by the user (who may also have changed the arguments). + */ + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + + if (d->__ordering == PERMUTE) { + /* If we have just processed options following some non-options, + * exchange them so that the options come first. + */ + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + __pmgetopt_exchange((char **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + + /* Skip any additional non-options and + * extend the range of non-options previously skipped. + */ + while (d->optind < argc && \ + (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) + d->optind++; + d->__last_nonopt = d->optind; + } + + /* + * The special ARGV-element `--' means premature end of options. + * Skip it like a null option, + * then exchange with previous non-options as if it were an option, + * then skip everything else like a non-option. + */ + if (d->optind != argc && !strcmp(argv[d->optind], "--")) { + d->optind++; + + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + __pmgetopt_exchange((char **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + d->optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + * and back over any non-options that we skipped and permuted. + */ + if (d->optind == argc) { + /* Set the next-arg-index to point at the non-options + * that we previously skipped, so the caller will digest them. + */ + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + * either stop the scan or describe it to the caller and pass it by.o + */ + if (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + * Skip the initial punctuation. + */ + d->__nextchar = (argv[d->optind] + 1 + + (longopts != NULL && argv[d->optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* + * Check whether the ARGV-element is a long option. + * + * If long_only and the ARGV-element has the form "-f", where f is + * a valid short option, don't consider it an abbreviated form of + * a long option that starts with f. Otherwise there would be no + * way to give the -f short option. + * + * On the other hand, if there's a long option "fubar" and + * the ARGV-element is "-fu", do consider that an abbreviation of + * the long option, just like "--fu", and not "-f" with arg "u". + * + * This distinction seems to be the most useful approach. + */ + if (longopts != NULL + && (argv[d->optind][1] == '-' + || (long_only && (argv[d->optind][2] + || !strchr(optstring, argv[d->optind][1]))))) { + char *nameend; + unsigned int namelen; + pmLongOptions *p; + pmLongOptions *pfound = NULL; + pmOptionsList *ambig_list = NULL; + int exact = 0; + int indfound = -1; + int option_index; + + for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + namelen = nameend - d->__nextchar; + + /* Test all long options for either exact match + * or abbreviated matches. + */ + for (p = longopts, option_index = 0; p->long_opt; p++, option_index++) { + if (!strncmp(p->long_opt, d->__nextchar, namelen)) { + if (namelen == (unsigned int) strlen(p->long_opt)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->short_opt != p->short_opt) { + /* Second or later nonexact match found. */ + pmOptionsList *newp = malloc(sizeof(*newp)); + newp->p = p; + newp->next = ambig_list; + ambig_list = newp; + } + } + } + + if (ambig_list != NULL && !exact) { + if (print_errors) { + pmOptionsList first; + first.p = pfound; + first.next = ambig_list; + ambig_list = &first; + + pmprintf("%s: option '%s' is ambiguous; possibilities:", + pmProgname, argv[d->optind]); + do { + pmprintf(" '--%s'", ambig_list->p->long_opt); + ambig_list = ambig_list->next; + } while (ambig_list != NULL); + pmprintf("\n"); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + d->optopt = 0; + free(ambig_list); + return '?'; + } + else if (ambig_list != NULL) { + free(ambig_list); + } + + if (pfound != NULL) { + option_index = indfound; + d->optind++; + if (*nameend) { + if (pfound->has_arg) { + d->optarg = nameend + 1; + } else { + if (print_errors) { + if (argv[d->optind - 1][1] == '-') { + /* --option */ + pmprintf("%s: option '--%s' doesn't allow an argument\n", + pmProgname, pfound->long_opt); + } else { + /* +option or -option */ + pmprintf("%s: option '%c%s' doesn't allow an argument\n", + pmProgname, argv[d->optind - 1][0], + pfound->long_opt); + } + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->short_opt; + return '?'; + } + } + else if (pfound->has_arg == 1) { + if (d->optind < argc) { + d->optarg = argv[d->optind++]; + } else { + if (print_errors) { + pmprintf("%s: option '--%s' requires an argument\n", + pmProgname, pfound->long_opt); + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->short_opt; + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + return pfound->short_opt; + } + + /* Can't find it as a long option. If this is not a long-only form, + * or the option starts with '--', or is not a valid short option, + * then it's an error. + * Otherwise interpret it as a short option. + */ + if (!long_only || argv[d->optind][1] == '-' + || strchr (optstring, *d->__nextchar) == NULL) { + if (print_errors) { + if (argv[d->optind][1] == '-') { + /* --option */ + pmprintf("%s: unrecognized option '--%s'\n", + pmProgname, d->__nextchar); + } else { + /* +option or -option */ + pmprintf("%s: unrecognized option '%c%s'\n", + pmProgname, argv[d->optind][0], d->__nextchar); + } + } + d->__nextchar = (char *) ""; + d->optind++; + d->optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *d->__nextchar++; + char *temp = strchr(optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*d->__nextchar == '\0') + ++d->optind; + + if (temp == NULL || c == ':' || c == ';') { + if (print_errors) { + pmprintf("%s: invalid option -- '%c'\n", pmProgname, c); + } + d->optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') { + if (longopts == NULL) + goto no_longs; + + char *nameend; + pmLongOptions *p; + pmLongOptions *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + * we must advance to the next element now. + */ + d->optind++; + } + else if (d->optind == argc) { + if (print_errors) { + pmprintf("%s: option requires an argument -- '%c'\n", + pmProgname, c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else { + /* We already incremented `d->optind' once; + * increment it again when taking next ARGV-elt as argument. + */ + d->optarg = argv[d->optind++]; + } + + /* optarg is now the argument, see if it's in the table of longopts. */ + + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; + nameend++) + /* Do nothing */ ; + + /* Test all long options for either exact or abbreviated matches. */ + for (p = longopts, option_index = 0; p->long_opt; p++, option_index++) + if (!strncmp(p->long_opt, d->__nextchar, nameend - d->__nextchar)) { + if ((unsigned int)(nameend - d->__nextchar) == strlen(p->long_opt)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->short_opt != p->short_opt) { + /* Second or later nonexact match found. */ + ambig = 1; + } + } + if (ambig && !exact) { + if (print_errors) { + pmprintf("%s: option '-W %s' is ambiguous\n", + pmProgname, d->optarg); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + return '?'; + } + if (pfound != NULL) { + option_index = indfound; + if (*nameend) { + if (pfound->has_arg) { + d->optarg = nameend + 1; + } else { + if (print_errors) { + pmprintf("%s: option '-W %s' doesn't allow an argument\n", + pmProgname, pfound->long_opt); + } + d->__nextchar += strlen(d->__nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) { + if (d->optind < argc) { + d->optarg = argv[d->optind++]; + } else { + if (print_errors) { + pmprintf("%s: option '-W %s' requires an argument\n", + pmProgname, pfound->long_opt); + } + d->__nextchar += strlen(d->__nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + else { + d->optarg = NULL; + } + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + return pfound->short_opt; + } + + no_longs: + d->__nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') { + if (temp[2] == ':') { + /* This is an option that accepts an argument optionally. */ + if (*d->__nextchar != '\0') { + d->optarg = d->__nextchar; + d->optind++; + } else { + d->optarg = NULL; + } + d->__nextchar = NULL; + } + else { + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + * we must advance to the next element now. + */ + d->optind++; + } + else if (d->optind == argc) { + if (print_errors) { + pmprintf("%s: option requires an argument -- '%c'\n", + pmProgname, c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else { + /* We already incremented `optind' once; + * increment it again when taking next ARGV-elt as argument. + */ + d->optarg = argv[d->optind++]; + } + d->__nextchar = NULL; + } + } + return c; + } +} diff --git a/src/libpcp/src/hash.c b/src/libpcp/src/hash.c new file mode 100644 index 0000000..9bdf97f --- /dev/null +++ b/src/libpcp/src/hash.c @@ -0,0 +1,202 @@ +/* + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2013 Red Hat, 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include <stddef.h> + +void +__pmHashInit(__pmHashCtl *hcp) +{ + memset(hcp, 0, sizeof(*hcp)); +} + +__pmHashNode * +__pmHashSearch(unsigned int key, __pmHashCtl *hcp) +{ + __pmHashNode *hp; + + if (hcp->hsize == 0) + return NULL; + + for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) { + if (hp->key == key) + return hp; + } + return NULL; +} + +int +__pmHashAdd(unsigned int key, void *data, __pmHashCtl *hcp) +{ + __pmHashNode *hp; + int k; + + hcp->nodes++; + + if (hcp->hsize == 0) { + hcp->hsize = 1; /* arbitrary number */ + if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) { + hcp->hsize = 0; + return -oserror(); + } + } + else if (hcp->nodes / 4 > hcp->hsize) { + __pmHashNode *tp; + __pmHashNode **old = hcp->hash; + int oldsize = hcp->hsize; + + hcp->hsize *= 2; + if (hcp->hsize % 2) hcp->hsize++; + if (hcp->hsize % 3) hcp->hsize += 2; + if (hcp->hsize % 5) hcp->hsize += 2; + if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) { + hcp->hsize = oldsize; + hcp->hash = old; + return -oserror(); + } + /* + * re-link chains + */ + while (oldsize) { + for (hp = old[--oldsize]; hp != NULL; ) { + tp = hp; + hp = hp->next; + k = tp->key % hcp->hsize; + tp->next = hcp->hash[k]; + hcp->hash[k] = tp; + } + } + free(old); + } + + if ((hp = (__pmHashNode *)malloc(sizeof(__pmHashNode))) == NULL) + return -oserror(); + + k = key % hcp->hsize; + hp->key = key; + hp->data = data; + hp->next = hcp->hash[k]; + hcp->hash[k] = hp; + + return 1; +} + +int +__pmHashDel(unsigned int key, void *data, __pmHashCtl *hcp) +{ + __pmHashNode *hp; + __pmHashNode *lhp = NULL; + + if (hcp->hsize == 0) + return 0; + + for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) { + if (hp->key == key && hp->data == data) { + if (lhp == NULL) + hcp->hash[key % hcp->hsize] = hp->next; + else + lhp->next = hp->next; + free(hp); + return 1; + } + lhp = hp; + } + + return 0; +} + +void +__pmHashClear(__pmHashCtl *hcp) +{ + if (hcp->hsize != 0) { + free(hcp->hash); + hcp->hsize = 0; + } +} + +/* + * Iterate over the entire hash table. For each entry, call *cb, + * passing *cdata and the current key/value pair. The function's + * return value decides how to continue or abort iteration. The + * callback function must not modify the hash table. + */ +void +__pmHashWalkCB(__pmHashWalkCallback cb, void *cdata, const __pmHashCtl *hcp) +{ + int n; + + for (n = 0; n < hcp->hsize; n++) { + __pmHashNode *tp = hcp->hash[n]; + __pmHashNode **tpp = & hcp->hash[n]; + + while (tp != NULL) { + __pmHashWalkState state = (*cb)(tp, cdata); + + switch (state) { + case PM_HASH_WALK_DELETE_STOP: + *tpp = tp->next; /* unlink */ + free(tp); /* delete */ + return; /* & stop */ + + case PM_HASH_WALK_NEXT: + tpp = &tp->next; + tp = *tpp; + break; + + case PM_HASH_WALK_DELETE_NEXT: + *tpp = tp->next; /* unlink */ + /* NB: do not change tpp. It will still point at the previous + * node's "next" pointer. Consider consecutive CONTINUE_DELETEs. + */ + free(tp); /* delete */ + tp = *tpp; /* == tp->next, except that tp is already freed. */ + break; /* & next */ + + case PM_HASH_WALK_STOP: + default: + return; + } + } + } +} + +/* + * Walk a hash table; state flow is START ... NEXT ... NEXT ... + */ +__pmHashNode * +__pmHashWalk(__pmHashCtl *hcp, __pmHashWalkState state) +{ + __pmHashNode *node; + + if (hcp->hsize == 0) + return NULL; + + if (state == PM_HASH_WALK_START) { + hcp->index = 0; + hcp->next = hcp->hash[0]; + } + + while (hcp->next == NULL) { + hcp->index++; + if (hcp->index >= hcp->hsize) + return NULL; + hcp->next = hcp->hash[hcp->index]; + } + + node = hcp->next; + hcp->next = node->next; + return node; +} diff --git a/src/libpcp/src/help.c b/src/libpcp/src/help.c new file mode 100644 index 0000000..ddc4b0d --- /dev/null +++ b/src/libpcp/src/help.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" + +static int +lookuptext(int ident, int type, char **buffer) +{ + int n; + __pmContext *ctxp; + __pmDSO *dp; + + + if ((n = pmWhichContext()) >= 0) { + int ctx = n; + ctxp = __pmHandleToPtr(ctx); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_LOCK(ctxp->c_pmcd->pc_lock); +again: + n = __pmSendTextReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), ident, type); + if (n < 0) + n = __pmMapErrno(n); + else { + __pmPDU *pb; + int pinpdu; + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_TEXT) { + int x_ident; + n = __pmDecodeText(pb, &x_ident, buffer); + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + /* + * Note: __pmDecodeText does not swab ident because it + * does not know whether it's a pmID or a pmInDom. + */ + + if (n == 0 && (*buffer)[0] == '\0' && (type & PM_TEXT_HELP)) { + /* fall back to one-line, if possible */ + free(*buffer); + type &= ~PM_TEXT_HELP; + type |= PM_TEXT_ONELINE; + goto again; + } + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + else if ((dp = __pmLookupDSO(((__pmID_int *)&ident)->domain)) == NULL) + n = PM_ERR_NOAGENT; + else { +again_local: + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + n = dp->dispatch.version.any.text(ident, type, buffer, dp->dispatch.version.any.ext); + if (n == 0 && (*buffer)[0] == '\0' && (type & PM_TEXT_HELP)) { + /* fall back to one-line, if possible */ + type &= ~PM_TEXT_HELP; + type |= PM_TEXT_ONELINE; + goto again_local; + } + if (n == 0) { + /* + * PMDAs don't malloc the buffer but the caller will + * free it, so malloc and copy + */ + *buffer = strdup(*buffer); + } + } + } + else { + /* assume PM_CONTEXT_ARCHIVE -- this is an error */ + n = PM_ERR_NOTHOST; + } + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} + +int +pmLookupText(pmID pmid, int level, char **buffer) +{ + return lookuptext((int)pmid, level | PM_TEXT_PMID, buffer); +} + +int +pmLookupInDomText(pmInDom indom, int level, char **buffer) +{ + return lookuptext((int)indom, level | PM_TEXT_INDOM, buffer); +} diff --git a/src/libpcp/src/instance.c b/src/libpcp/src/instance.c new file mode 100644 index 0000000..07fad87 --- /dev/null +++ b/src/libpcp/src/instance.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995-2006 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" + +int +pmLookupInDom(pmInDom indom, const char *name) +{ + int n; + __pmInResult *result; + __pmContext *ctxp; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + if ((n = pmWhichContext()) >= 0) { + int ctx = n; + ctxp = __pmHandleToPtr(ctx); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_LOCK(ctxp->c_pmcd->pc_lock); + n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + &ctxp->c_origin, indom, PM_IN_NULL, name); + if (n < 0) + n = __pmMapErrno(n); + else { + __pmPDU *pb; + int pinpdu; + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_INSTANCE) { + __pmInResult *result; + if ((n = __pmDecodeInstance(pb, &result)) >= 0) { + n = result->instlist[0]; + __pmFreeInResult(result); + } + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + __pmDSO *dp; + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL) + n = PM_ERR_NOAGENT; + else { + /* We can safely cast away const here */ + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + n = dp->dispatch.version.any.instance(indom, PM_IN_NULL, + (char *)name, &result, + dp->dispatch.version.any.ext); + } + if (n >= 0) { + n = result->instlist[0]; + __pmFreeInResult(result); + } + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + n = __pmLogLookupInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, name); + } + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} + +int +pmNameInDom(pmInDom indom, int inst, char **name) +{ + int n; + __pmInResult *result; + __pmContext *ctxp; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + if ((n = pmWhichContext()) >= 0) { + int ctx = n; + ctxp = __pmHandleToPtr(ctx); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_LOCK(ctxp->c_pmcd->pc_lock); + n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + &ctxp->c_origin, indom, inst, NULL); + if (n < 0) + n = __pmMapErrno(n); + else { + __pmPDU *pb; + int pinpdu; + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_INSTANCE) { + __pmInResult *result; + if ((n = __pmDecodeInstance(pb, &result)) >= 0) { + if ((*name = strdup(result->namelist[0])) == NULL) + n = -oserror(); + __pmFreeInResult(result); + } + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + __pmDSO *dp; + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL) + n = PM_ERR_NOAGENT; + else { + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + n = dp->dispatch.version.any.instance(indom, inst, NULL, &result, dp->dispatch.version.any.ext); + } + if (n >= 0) { + if ((*name = strdup(result->namelist[0])) == NULL) + n = -oserror(); + __pmFreeInResult(result); + } + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + char *tmp; + if ((n = __pmLogNameInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, inst, &tmp)) >= 0) { + if ((*name = strdup(tmp)) == NULL) + n = -oserror(); + } + } + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} + +static int +inresult_to_lists(__pmInResult *result, int **instlist, char ***namelist) +{ + int n, i, sts, need; + char *p; + int *ilist; + char **nlist; + + if (result->numinst == 0) { + __pmFreeInResult(result); + return 0; + } + need = 0; + for (i = 0; i < result->numinst; i++) { + need += sizeof(**namelist) + strlen(result->namelist[i]) + 1; + } + ilist = (int *)malloc(result->numinst * sizeof(result->instlist[0])); + if (ilist == NULL) { + sts = -oserror(); + __pmFreeInResult(result); + return sts; + } + if ((nlist = (char **)malloc(need)) == NULL) { + sts = -oserror(); + free(ilist); + __pmFreeInResult(result); + return sts; + } + + *instlist = ilist; + *namelist = nlist; + p = (char *)&nlist[result->numinst]; + for (i = 0; i < result->numinst; i++) { + ilist[i] = result->instlist[i]; + strcpy(p, result->namelist[i]); + nlist[i] = p; + p += strlen(result->namelist[i]) + 1; + } + n = result->numinst; + __pmFreeInResult(result); + return n; +} + +int +pmGetInDom(pmInDom indom, int **instlist, char ***namelist) +{ + int n; + int i; + __pmInResult *result; + __pmContext *ctxp; + char *p; + int need; + int *ilist; + char **nlist; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((n = pmWhichContext()) >= 0) { + int ctx = n; + ctxp = __pmHandleToPtr(ctx); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_LOCK(ctxp->c_pmcd->pc_lock); + n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + &ctxp->c_origin, indom, PM_IN_NULL, NULL); + if (n < 0) + n = __pmMapErrno(n); + else { + __pmPDU *pb; + int pinpdu; + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_INSTANCE) { + __pmInResult *result; + if ((n = __pmDecodeInstance(pb, &result)) < 0) { + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + return n; + } + n = inresult_to_lists(result, instlist, namelist); + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + __pmDSO *dp; + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + n = PM_ERR_THREAD; + else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL) + n = PM_ERR_NOAGENT; + else { + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + n = dp->dispatch.version.any.instance(indom, PM_IN_NULL, NULL, + &result, + dp->dispatch.version.any.ext); + } + if (n >= 0) + n = inresult_to_lists(result, instlist, namelist); + } + else { + /* assume PM_CONTEXT_ARCHIVE */ + int *insttmp; + char **nametmp; + if ((n = __pmLogGetInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, &insttmp, &nametmp)) >= 0) { + need = 0; + for (i = 0; i < n; i++) + need += sizeof(char *) + strlen(nametmp[i]) + 1; + if ((ilist = (int *)malloc(n * sizeof(insttmp[0]))) == NULL) { + PM_UNLOCK(ctxp->c_lock); + return -oserror(); + } + if ((nlist = (char **)malloc(need)) == NULL) { + free(ilist); + PM_UNLOCK(ctxp->c_lock); + return -oserror(); + } + *instlist = ilist; + *namelist = nlist; + p = (char *)&nlist[n]; + for (i = 0; i < n; i++) { + ilist[i] = insttmp[i]; + strcpy(p, nametmp[i]); + nlist[i] = p; + p += strlen(nametmp[i]) + 1; + } + } + } + PM_UNLOCK(ctxp->c_lock); + } + + if (n == 0) { + /* avoid ambiguity when no instances or errors */ + *instlist = NULL; + *namelist = NULL; + } + + return n; +} + +#ifdef PCP_DEBUG +void +__pmDumpInResult(FILE *f, const __pmInResult *irp) +{ + int i; + char strbuf[20]; + fprintf(f,"pmInResult dump from " PRINTF_P_PFX "%p for InDom %s (0x%x), numinst=%d\n", + irp, pmInDomStr_r(irp->indom, strbuf, sizeof(strbuf)), irp->indom, irp->numinst); + for (i = 0; i < irp->numinst; i++) { + fprintf(f, " [%d]", i); + if (irp->instlist != NULL) + fprintf(f, " inst=%d", irp->instlist[i]); + if (irp->namelist != NULL) + fprintf(f, " name=\"%s\"", irp->namelist[i]); + fputc('\n', f); + } + return; +} +#endif + +void +__pmFreeInResult(__pmInResult *res) +{ + int i; + + if (res->namelist != NULL) { + for (i = 0; i < res->numinst; i++) { + if (res->namelist[i] != NULL) { + free(res->namelist[i]); + } + } + free(res->namelist); + } + if (res->instlist != NULL) + free(res->instlist); + free(res); +} diff --git a/src/libpcp/src/internal.h b/src/libpcp/src/internal.h new file mode 100644 index 0000000..b9f4300 --- /dev/null +++ b/src/libpcp/src/internal.h @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +#ifndef _INTERNAL_H +#define _INTERNAL_H + +/* + * Routines and data structures used within libpcp source files, + * but which we do not want to expose via impl.h or pmapi.h. + */ + +#if defined(__GNUC__) && (__GNUC__ >= 4) +# define _PCP_HIDDEN __attribute__ ((visibility ("hidden"))) +#else +# define _PCP_HIDDEN +#endif + +#include "derive.h" + +extern int __pmConvertTimeout(int) _PCP_HIDDEN; +extern const struct timeval *__pmConnectTimeout(void) _PCP_HIDDEN; +extern const struct timeval * __pmDefaultRequestTimeout(void) _PCP_HIDDEN; +extern int __pmConnectWithFNDELAY(int, void *, __pmSockLen) _PCP_HIDDEN; + +extern int __pmPtrToHandle(__pmContext *) _PCP_HIDDEN; +extern int __pmFetchLocal(__pmContext *, int, pmID *, pmResult **) _PCP_HIDDEN; + +extern int __pmGlibGetDate (struct timespec *, char const *, struct timespec const *) _PCP_HIDDEN; + +#ifdef HAVE_NETWORK_BYTEORDER +/* + * no-ops if already in network byte order but + * the value may be used in an expression. + */ +#define __htonpmUnits(a) (a) +#define __ntohpmUnits(a) (a) +#define __htonpmID(a) (a) +#define __ntohpmID(a) (a) +#define __htonpmInDom(a) (a) +#define __ntohpmInDom(a) (a) +#define __htonpmPDUInfo(a) (a) +#define __ntohpmPDUInfo(a) (a) +#define __htonpmCred(a) (a) +#define __ntohpmCred(a) (a) + +/* + * For network byte order, the following are noops, + * but otherwise the function is void, so they are + * defined as comments to catch code that tries to + * use them in an expression or assignment. + */ +#define __htonpmValueBlock(a) /* noop */ +#define __ntohpmValueBlock(a) /* noop */ +#define __htonf(a) /* noop */ +#define __ntohf(a) /* noop */ +#define __htond(a) /* noop */ +#define __ntohd(a) /* noop */ +#define __htonll(a) /* noop */ +#define __ntohll(a) /* noop */ + +#else +/* + * Functions to convert to/from network byte order + * for little-endian platforms (e.g. Intel). + */ +#define __htonpmID(a) htonl(a) +#define __ntohpmID(a) ntohl(a) +#define __htonpmInDom(a) htonl(a) +#define __ntohpmInDom(a) ntohl(a) + +extern pmUnits __htonpmUnits(pmUnits) _PCP_HIDDEN; +extern pmUnits __ntohpmUnits(pmUnits) _PCP_HIDDEN; +extern __pmPDUInfo __htonpmPDUInfo(__pmPDUInfo) _PCP_HIDDEN; +extern __pmPDUInfo __ntohpmPDUInfo(__pmPDUInfo) _PCP_HIDDEN; +extern __pmCred __htonpmCred(__pmCred) _PCP_HIDDEN; +extern __pmCred __ntohpmCred(__pmCred) _PCP_HIDDEN; + +/* insitu swab for these */ +extern void __htonpmValueBlock(pmValueBlock * const) _PCP_HIDDEN; +extern void __ntohpmValueBlock(pmValueBlock * const) _PCP_HIDDEN; +extern void __htonf(char *) _PCP_HIDDEN; /* float */ +#define __ntohf(v) __htonf(v) +#define __htond(v) __htonll(v) /* double */ +#define __ntohd(v) __ntohll(v) +extern void __htonll(char *) _PCP_HIDDEN; /* 64bit int */ +#define __ntohll(v) __htonll(v) + +#endif /* HAVE_NETWORK_BYTEORDER */ + +#ifdef PM_MULTI_THREAD +#ifdef HAVE___THREAD +/* + * C compiler is probably gcc and supports __thread declarations + */ +#define PM_TPD(x) x +#else +/* + * Roll-your-own Thread Private Data support + */ +extern pthread_key_t __pmTPDKey _PCP_HIDDEN; + +typedef struct { + int curcontext; /* current context */ + char *derive_errmsg; /* derived metric parser error message */ +} __pmTPD; + +static inline __pmTPD * +__pmTPDGet(void) +{ + return (__pmTPD *)pthread_getspecific(__pmTPDKey); +} + +#define PM_TPD(x) __pmTPDGet()->x +#endif +#else /* !PM_MULTI_THREAD */ +/* No threads - just access global variables as-is */ +#define PM_TPD(x) x +#endif + +#ifdef PM_MULTI_THREAD_DEBUG +extern void __pmDebugLock(int, void *, const char *, int) _PCP_HIDDEN; +extern int __pmIsContextLock(void *) _PCP_HIDDEN; +extern int __pmIsChannelLock(void *) _PCP_HIDDEN; +extern int __pmIsDeriveLock(void *) _PCP_HIDDEN; +#endif + +/* AF_UNIX socket family internals */ +#define PM_HOST_SPEC_NPORTS_LOCAL (-1) +#define PM_HOST_SPEC_NPORTS_UNIX (-2) +extern const char *__pmPMCDLocalSocketDefault(void) _PCP_HIDDEN; +extern void __pmCheckAcceptedAddress(__pmSockAddr *) _PCP_HIDDEN; + +#ifdef SOCKET_INTERNAL +#ifdef HAVE_SECURE_SOCKETS +#include <nss.h> +#include <ssl.h> +#include <nspr.h> +#include <prerror.h> +#include <private/pprio.h> +#include <sasl.h> + +#define SECURE_SERVER_CERTIFICATE "PCP Collector certificate" +#define SECURE_USERDB_DEFAULT_KEY "\n" + +/* internal NSS/NSPR/SSL/SASL implementation details */ +extern int __pmSecureSocketsError(int) _PCP_HIDDEN; +#endif + +#if defined(HAVE_SYS_UN_H) +#include <sys/un.h> +#endif + +struct __pmSockAddr { + union { + struct sockaddr raw; + struct sockaddr_in inet; + struct sockaddr_in6 ipv6; +#if defined(HAVE_STRUCT_SOCKADDR_UN) + struct sockaddr_un local; +#endif + } sockaddr; +}; + +typedef struct addrinfo __pmAddrInfo; + +struct __pmHostEnt { + char *name; + __pmAddrInfo *addresses; +}; +#endif + +extern unsigned __pmFirstInetSubnetAddr(unsigned, int) _PCP_HIDDEN; +extern unsigned __pmNextInetSubnetAddr(unsigned, int) _PCP_HIDDEN; +extern unsigned char *__pmFirstIpv6SubnetAddr(unsigned char *, int maskBits) _PCP_HIDDEN; +extern unsigned char *__pmNextIpv6SubnetAddr(unsigned char *, int maskBits) _PCP_HIDDEN; + +extern int __pmInitSecureSockets(void) _PCP_HIDDEN; +extern int __pmInitCertificates(void) _PCP_HIDDEN; +extern int __pmInitSocket(int, int) _PCP_HIDDEN; +extern int __pmSocketReady(int, struct timeval *) _PCP_HIDDEN; +extern void *__pmGetSecureSocket(int) _PCP_HIDDEN; +extern void *__pmGetUserAuthData(int) _PCP_HIDDEN; +extern int __pmSecureServerInit(void) _PCP_HIDDEN; +extern int __pmSecureServerIPCFlags(int, int) _PCP_HIDDEN; +extern int __pmSecureServerHasFeature(__pmServerFeature) _PCP_HIDDEN; +extern int __pmSecureServerSetFeature(__pmServerFeature) _PCP_HIDDEN; +extern int __pmSecureServerClearFeature(__pmServerFeature) _PCP_HIDDEN; + +extern int __pmShutdownLocal(void) _PCP_HIDDEN; +extern int __pmShutdownCertificates(void) _PCP_HIDDEN; +extern int __pmShutdownSecureSockets(void) _PCP_HIDDEN; + +#define SECURE_SERVER_SASL_SERVICE "PCP Collector" +#define LIMIT_AUTH_PDU 2048 /* maximum size of a SASL transfer (in bytes) */ +#define LIMIT_CLIENT_CALLBACKS 8 /* maximum size of callback array */ +#define DEFAULT_SECURITY_STRENGTH 0 /* SASL security strength factor */ + +typedef int (*sasl_callback_func)(void); +extern int __pmInitAuthClients(void) _PCP_HIDDEN; +extern int __pmInitAuthServer(void) _PCP_HIDDEN; + +/* + * Platform independent user/group account manipulation + */ +extern int __pmValidUserID(__pmUserID) _PCP_HIDDEN; +extern int __pmValidGroupID(__pmGroupID) _PCP_HIDDEN; +extern int __pmEqualUserIDs(__pmUserID, __pmUserID) _PCP_HIDDEN; +extern int __pmEqualGroupIDs(__pmGroupID, __pmGroupID) _PCP_HIDDEN; +extern void __pmUserIDFromString(const char *, __pmUserID *) _PCP_HIDDEN; +extern void __pmGroupIDFromString(const char *, __pmGroupID *) _PCP_HIDDEN; +extern char *__pmUserIDToString(__pmUserID, char *, size_t) _PCP_HIDDEN; +extern char *__pmGroupIDToString(__pmGroupID, char *, size_t) _PCP_HIDDEN; +extern int __pmUsernameToID(const char *, __pmUserID *) _PCP_HIDDEN; +extern int __pmGroupnameToID(const char *, __pmGroupID *) _PCP_HIDDEN; +extern char *__pmUsernameFromID(__pmUserID, char *, size_t) _PCP_HIDDEN; +extern char *__pmGroupnameFromID(__pmGroupID, char *, size_t) _PCP_HIDDEN; +extern char *__pmHomedirFromID(__pmUserID, char *, size_t) _PCP_HIDDEN; +extern int __pmUsersGroupIDs(const char *, __pmGroupID **, unsigned int *) _PCP_HIDDEN; +extern int __pmGroupsUserIDs(const char *, __pmUserID **, unsigned int *) _PCP_HIDDEN; +extern int __pmGetUserIdentity(const char *, __pmUserID *, __pmGroupID *, int) _PCP_HIDDEN; + +extern int __pmStringListAdd(char *, int, char ***) _PCP_HIDDEN; +extern char *__pmStringListFind(const char *, int, char **) _PCP_HIDDEN; + +/* + * Representations of server presence on the network. + */ +typedef struct __pmServerAvahiPresence __pmServerAvahiPresence; + +struct __pmServerPresence { + /* Common data. */ + char *serviceSpec; + int port; + /* API-specific data. */ + __pmServerAvahiPresence *avahi; +}; + +/* Service discovery internals. */ +typedef struct { + const char *spec; + __pmSockAddr *address; + const char *protocol; +} __pmServiceInfo; + +typedef struct { + const volatile unsigned *flags; /* Service discovery flags */ + struct timeval timeout; /* Global timeout period */ + volatile int timedOut; /* Global timeout occurred */ + int resolve; /* Resolve discovered addresses */ +} __pmServiceDiscoveryOptions; + +extern int __pmAddDiscoveredService(__pmServiceInfo *, + const __pmServiceDiscoveryOptions *, + int, + char ***) _PCP_HIDDEN; +extern char *__pmServiceDiscoveryParseTimeout (const char *s, + struct timeval *timeout) + _PCP_HIDDEN; + +extern int __pmServiceAddPorts(const char *, int **, int) _PCP_HIDDEN; +extern int __pmPMCDAddPorts(int **, int) _PCP_HIDDEN; +extern int __pmProxyAddPorts(int **, int) _PCP_HIDDEN; +extern int __pmWebdAddPorts(int **, int) _PCP_HIDDEN; +extern int __pmAddPorts(const char *, int **, int) _PCP_HIDDEN; + +#endif /* _INTERNAL_H */ diff --git a/src/libpcp/src/interp.c b/src/libpcp/src/interp.c new file mode 100644 index 0000000..ec49387 --- /dev/null +++ b/src/libpcp/src/interp.c @@ -0,0 +1,1714 @@ +/* + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes: + * + * nr[] and nr_cache[] are diagnostic counters that are maintained with + * non-atomic updates ... we've decided that it is acceptable for their + * values to be subject to possible (but unlikely) missed updates + */ + +/* + * Note: _FORTIFY_SOURCE cannot be set here because the calls to memcpy() + * with vp->vbuf as the destination raise a warning that is not + * correct as the allocation for a pmValueBlock always extends + * vbuf[] to be the correct size, not the [1] as per the declaration + * in pmapi.h + */ +#ifdef _FORTIFY_SOURCE +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 0 +#endif + +#include <limits.h> +#include <inttypes.h> +#include <assert.h> +#include "pmapi.h" +#include "impl.h" + +#define UPD_MARK_NONE 0 +#define UPD_MARK_FORW 1 +#define UPD_MARK_BACK 2 + +#if defined(HAVE_CONST_LONGLONG) +#define SIGN_64_MASK 0x8000000000000000LL +#else +#define SIGN_64_MASK 0x8000000000000000 +#endif + +typedef union { /* value from pmResult */ + pmValueBlock *pval; + int lval; +} value; + +/* + * state values for s_prior and s_next + */ +#define S_UNDEFINED 0 /* no searching done yet */ +#define S_NOTFOUND 1 /* searched to end or start of archive and + did not find a value or a <mark> */ +#define S_MARK 2 /* searched and found <mark> before value + at t_prior or t_mext */ +#define S_VALUE 3 /* searched and found value at t_prior or + t_next */ + +static const char *statestr[] = { " <undefined>", " <notfound>", " <mark>", "" }; + + +typedef struct instcntl { /* metric-instance control */ + struct instcntl *next; /* next for this metric control */ + struct instcntl *want; /* ones of interest */ + struct instcntl *unbound; /* not yet bound above [or below] */ + int search; /* looking for found this one? */ + int inst; /* instance identifier */ + int inresult; /* will be in this result */ + double t_prior; + int s_prior; /* state at t_prior */ + value v_prior; + double t_next; + int s_next; /* state at t_next */ + value v_next; + double t_first; /* no records before this */ + double t_last; /* no records after this */ + struct pmidcntl *metric; /* back to metric control */ +} instcntl_t; + +typedef struct pmidcntl { /* metric control */ + pmDesc desc; + int valfmt; /* used to build result */ + int numval; /* number of instances in this result */ + struct instcntl *first; /* first metric-instace control */ +} pmidcntl_t; + +typedef struct { + pmResult *rp; /* cached pmResult from __pmLogRead */ + int sts; /* from __pmLogRead */ + FILE *mfp; /* log stream */ + int vol; /* log volume */ + long head_posn; /* posn in file before forwards __pmLogRead */ + long tail_posn; /* posn in file after forwards __pmLogRead */ + int mode; /* PM_MODE_FORW or PM_MODE_BACK */ + int used; /* used count for LFU replacement */ +} cache_t; + +#define NUMCACHE 4 + +/* + * diagnostic counters ... indexed by PM_MODE_FORW (2) and + * PM_MODE_BACK (3), hence 4 elts for cached and non-cached reads + */ +static long nr_cache[PM_MODE_BACK+1]; +static long nr[PM_MODE_BACK+1]; + +/* + * called with the context lock held + */ +static int +cache_read(__pmArchCtl *acp, int mode, pmResult **rp) +{ + long posn; + cache_t *cp; + cache_t *lfup; + cache_t *cache; + int save_curvol; + + if (acp->ac_vol == acp->ac_log->l_curvol) { + posn = ftell(acp->ac_log->l_mfp); + assert(posn >= 0); + } + else + posn = 0; + + if (acp->ac_cache == NULL) { + /* cache initialization */ + acp->ac_cache = cache = (cache_t *)malloc(NUMCACHE*sizeof(cache_t)); + // TODO error check + for (cp = cache; cp < &cache[NUMCACHE]; cp++) { + cp->rp = NULL; + cp->mfp = NULL; + } + acp->ac_cache_idx = 0; + } + else + cache = acp->ac_cache; + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) { + fprintf(stderr, "cache_read: fd=%d mode=%s vol=%d (curvol=%d) %s_posn=%ld ", + fileno(acp->ac_log->l_mfp), + mode == PM_MODE_FORW ? "forw" : "back", + acp->ac_vol, acp->ac_log->l_curvol, + mode == PM_MODE_FORW ? "head" : "tail", + (long)posn); + } +#endif + + acp->ac_cache_idx = (acp->ac_cache_idx + 1) % NUMCACHE; + lfup = &cache[acp->ac_cache_idx]; + for (cp = cache; cp < &cache[NUMCACHE]; cp++) { + if (cp->mfp == acp->ac_log->l_mfp && cp->vol == acp->ac_vol && + ((mode == PM_MODE_FORW && cp->head_posn == posn) || + (mode == PM_MODE_BACK && cp->tail_posn == posn)) && + cp->rp != NULL) { + int sts; + *rp = cp->rp; + cp->used++; + if (mode == PM_MODE_FORW) + fseek(acp->ac_log->l_mfp, cp->tail_posn, SEEK_SET); + else + fseek(acp->ac_log->l_mfp, cp->head_posn, SEEK_SET); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) { + __pmTimeval tmp; + double t_this; + tmp.tv_sec = (__int32_t)cp->rp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)cp->rp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &acp->ac_log->l_label.ill_start); + fprintf(stderr, "hit cache[%d] t=%.6f\n", + (int)(cp - cache), t_this); + nr_cache[mode]++; + } +#endif + sts = cp->sts; + return sts; + } + } + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) + fprintf(stderr, "miss\n"); + nr[mode]++; +#endif + + if (lfup->rp != NULL) + pmFreeResult(lfup->rp); + + save_curvol = acp->ac_log->l_curvol; + + lfup->sts = __pmLogRead(acp->ac_log, mode, NULL, &lfup->rp, PMLOGREAD_NEXT); + if (lfup->sts < 0) + lfup->rp = NULL; + *rp = lfup->rp; + + if (posn == 0 || save_curvol != acp->ac_log->l_curvol) { + /* + * vol switch since last time, or vol switch in __pmLogRead() ... + * new vol, stdio stream and we don't know where we started from + * ... don't cache + */ + lfup->mfp = NULL; +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) + fprintf(stderr, "cache_read: reload vol switch, mark cache[%d] unused\n", + (int)(lfup - cache)); +#endif + } + else { + lfup->mode = mode; + lfup->vol = acp->ac_vol; + lfup->mfp = acp->ac_log->l_mfp; + lfup->used = 1; + if (mode == PM_MODE_FORW) { + lfup->head_posn = posn; + lfup->tail_posn = ftell(acp->ac_log->l_mfp); + assert(lfup->tail_posn >= 0); + } + else { + lfup->tail_posn = posn; + lfup->head_posn = ftell(acp->ac_log->l_mfp); + assert(lfup->head_posn >= 0); + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) { + fprintf(stderr, "cache_read: reload cache[%d] vol=%d (curvol=%d) head=%ld tail=%ld ", + (int)(lfup - cache), lfup->vol, acp->ac_log->l_curvol, + (long)lfup->head_posn, (long)lfup->tail_posn); + if (lfup->sts == 0) + fprintf(stderr, "sts=%d\n", lfup->sts); + else { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "sts=%s\n", pmErrStr_r(lfup->sts, errmsg, sizeof(errmsg))); + } + } +#endif + } + + return lfup->sts; +} + +void +__pmLogCacheClear(FILE *mfp) +{ + /* retired ... functionality moved to __pmFreeInterpData() */ + return; +} + +#ifdef PCP_DEBUG +/* + * prior == 1 for ?_prior fields, else use ?_next fields + */ +static void +dumpval(FILE *f, int type, int valfmt, int prior, instcntl_t *icp) +{ + int mark; + value *vp; + if (prior) { + if (icp->t_prior == -1) { + fprintf(stderr, " <undefined>"); + return; + } + mark = icp->s_prior == S_MARK; + vp = &icp->v_prior; + } + else { + if (icp->t_next == -1) { + fprintf(stderr, " <undefined>"); + return; + } + mark = icp->s_next == S_MARK; + vp = &icp->v_next; + } + if (mark) { + fprintf(f, " <mark>"); + return; + } + if (type == PM_TYPE_32 || type == PM_TYPE_U32) + fprintf(f, " v=%d", vp->lval); + else if (type == PM_TYPE_FLOAT && valfmt == PM_VAL_INSITU) { + float tmp; + memcpy((void *)&tmp, (void *)&vp->lval, sizeof(tmp)); + fprintf(f, " v=%f", (double)tmp); + } + else if (type == PM_TYPE_64) { + __int64_t tmp; + memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp)); + fprintf(f, " v=%"PRIi64, tmp); + } + else if (type == PM_TYPE_U64) { + __uint64_t tmp; + memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp)); + fprintf(f, " v=%"PRIu64, tmp); + } + else if (type == PM_TYPE_FLOAT) { + float tmp; + memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp)); + fprintf(f, " v=%f", (double)tmp); + } + else if (type == PM_TYPE_DOUBLE) { + double tmp; + memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp)); + fprintf(f, " v=%f", tmp); + } + else + fprintf(f, "v=??? (lval=%d)", vp->lval); +} +#endif + +static void +update_bounds(__pmContext *ctxp, double t_req, pmResult *logrp, int do_mark, int *done_prior, int *done_next) +{ + /* + * for every metric in the result from the log + * for every instance in the result from the log + * if we have ever asked for this metric and instance, update the + * range bounds, if necessary + */ + int k; + int i; + __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc; + __pmHashNode *hp; + pmidcntl_t *pcp; + instcntl_t *icp; + double t_this; + __pmTimeval tmp; + int changed; + + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + + if (logrp->numpmid == 0 && do_mark != UPD_MARK_NONE) { + /* mark record, discontinuity in log */ + for (icp = (instcntl_t *)ctxp->c_archctl->ac_want; icp != NULL; icp = icp->want) { + if (t_this <= t_req && + (t_this >= icp->t_prior || icp->t_prior > t_req)) { + /* <mark> is closer than best lower bound to date */ + icp->t_prior = t_this; + icp->s_prior = S_MARK; + if (icp->metric->valfmt != PM_VAL_INSITU) { + if (icp->v_prior.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_prior.pval); + icp->v_prior.pval = NULL; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "pmid %s inst %d <mark> t_prior=%.6f t_first=%.6f t_last=%.6f\n", + pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_prior, icp->t_first, icp->t_last); + } +#endif + if (icp->search && done_prior != NULL) { + icp->search = 0; + (*done_prior)++; + } + } + if (t_this >= t_req && + ((t_this <= icp->t_next || icp->t_next < 0) || + icp->t_next < t_req)) { + /* <mark> is closer than best upper bound to date */ + icp->t_next = t_this; + icp->s_next = S_MARK; + if (icp->metric->valfmt != PM_VAL_INSITU) { + if (icp->v_next.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_next.pval); + icp->v_next.pval = NULL; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "pmid %s inst %d <mark> t_next=%.6f t_first=%.6f t_last=%.6f\n", + pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_next, icp->t_first, icp->t_last); + } +#endif + if (icp->search && done_next != NULL) { + icp->search = 0; + (*done_next)++; + } + } + } + return; + } + + changed = 0; + for (k = 0; k < logrp->numpmid; k++) { + hp = __pmHashSearch((int)logrp->vset[k]->pmid, hcp); + if (hp == NULL) + continue; + pcp = (pmidcntl_t *)hp->data; + if (pcp->valfmt == -1 && logrp->vset[k]->numval > 0) + pcp->valfmt = logrp->vset[k]->valfmt; + for (icp = pcp->first; icp != NULL; icp = icp->next) { + for (i = 0; i < logrp->vset[k]->numval; i++) { + if (logrp->vset[k]->vlist[i].inst == icp->inst || + icp->inst == PM_IN_NULL) { + /* matched on instance */ +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) { + char strbuf[20]; + fprintf(stderr, "update: match pmid %s inst %d t_this=%.6f t_prior=%.6f t_next=%.6f t_first=%.6f t_last=%.6f\n", + pmIDStr_r(logrp->vset[k]->pmid, strbuf, sizeof(strbuf)), icp->inst, + t_this, icp->t_prior, icp->t_next, + icp->t_first, icp->t_last); + } +#endif + if (t_this <= t_req && + (icp->t_prior > t_req || t_this >= icp->t_prior)) { + /* + * at or before the requested time, and this is the + * closest-to-date lower bound + */ + changed = 1; + if (icp->t_prior < icp->t_next && icp->t_prior >= t_req) { + /* shuffle prior to next */ + icp->t_next = icp->t_prior; + if (pcp->valfmt == PM_VAL_INSITU) + icp->v_next.lval = icp->v_prior.lval; + else { + if (icp->v_next.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_next.pval); + icp->v_next.pval = icp->v_prior.pval; + } + } + icp->t_prior = t_this; + icp->s_prior = S_VALUE; + if (pcp->valfmt == PM_VAL_INSITU) + icp->v_prior.lval = logrp->vset[k]->vlist[i].value.lval; + else { + if (icp->v_prior.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_prior.pval); + icp->v_prior.pval = logrp->vset[k]->vlist[i].value.pval; + __pmPinPDUBuf((void *)icp->v_prior.pval); + } + if (icp->search && done_prior != NULL) { + /* one we were looking for */ + changed |= 2; + icp->search = 0; + (*done_prior)++; + } + } + if (t_this >= t_req && + (icp->t_next < t_req || t_this <= icp->t_next)) { + /* + * at or after the requested time, and this is the + * closest-to-date upper bound + */ + changed |= 1; + if (icp->t_prior < icp->t_next && icp->t_next <= t_req) { + /* shuffle next to prior */ + icp->t_prior = icp->t_next; + icp->s_prior = icp->s_next; + if (pcp->valfmt == PM_VAL_INSITU) + icp->v_prior.lval = icp->v_next.lval; + else { + if (icp->v_prior.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_prior.pval); + icp->v_prior.pval = icp->v_next.pval; + } + } + icp->t_next = t_this; + icp->s_next = S_VALUE; + if (pcp->valfmt == PM_VAL_INSITU) + icp->v_next.lval = logrp->vset[k]->vlist[i].value.lval; + else { + if (icp->v_next.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_next.pval); + icp->v_next.pval = logrp->vset[k]->vlist[i].value.pval; + __pmPinPDUBuf((void *)icp->v_next.pval); + } + if (icp->search && done_next != NULL) { + /* one we were looking for */ + changed |= 2; + icp->search = 0; + (*done_next)++; + } + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_INTERP) && changed) { + char strbuf[20]; + fprintf(stderr, "update%s pmid %s inst %d prior: t=%.6f", + changed & 2 ? "+search" : "", + pmIDStr_r(logrp->vset[k]->pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_prior); + dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 1, icp); + fprintf(stderr, " next: t=%.6f", icp->t_next); + dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 0, icp); + fprintf(stderr, " t_first=%.6f t_last=%.6f\n", + icp->t_first, icp->t_last); + } +#endif + goto next_inst; + } + } +next_inst: + ; + } + } + + return; +} + +static void +do_roll(__pmContext *ctxp, double t_req) +{ + pmResult *logrp; + __pmTimeval tmp; + double t_this; + + /* + * now roll forwards in the direction of log reading + * to make sure we are up to t_req + */ + if (ctxp->c_delta > 0) { + while (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) >= 0) { + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (t_this > t_req) + break; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) + fprintf(stderr, "roll forw to t=%.6f%s\n", + t_this, logrp->numpmid == 0 ? " <mark>" : ""); +#endif + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + update_bounds(ctxp, t_req, logrp, UPD_MARK_FORW, NULL, NULL); + } + } + else { + while (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) >= 0) { + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (t_this < t_req) + break; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) + fprintf(stderr, "roll back to t=%.6f%s\n", + t_this, logrp->numpmid == 0 ? " <mark>" : ""); +#endif + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + update_bounds(ctxp, t_req, logrp, UPD_MARK_BACK, NULL, NULL); + } + } +} + +#define pmXTBdeltaToTimeval(d, m, t) { \ + (t)->tv_sec = 0; \ + (t)->tv_usec = (long)0; \ + switch(PM_XTB_GET(m)) { \ + case PM_TIME_NSEC: (t)->tv_usec = (long)((d) / 1000); break; \ + case PM_TIME_USEC: (t)->tv_usec = (long)(d); break; \ + case PM_TIME_MSEC: (t)->tv_sec = (d) / 1000; (t)->tv_usec = (long)(1000 * ((d) % 1000)); break; \ + case PM_TIME_SEC: (t)->tv_sec = (d); break; \ + case PM_TIME_MIN: (t)->tv_sec = (d) * 60; break; \ + case PM_TIME_HOUR: (t)->tv_sec = (d) * 360; break; \ + default: (t)->tv_sec = (d) / 1000; (t)->tv_usec = (long)(1000 * ((d) % 1000)); break; \ + } \ +} + +int +__pmLogFetchInterp(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result) +{ + int i; + int j; + int sts; + double t_req; + double t_this; + pmResult *rp; + pmResult *logrp; + __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc; + __pmHashNode *hp; + pmidcntl_t *pcp = NULL; /* initialize to pander to gcc */ + instcntl_t *icp; + int back = 0; + int forw = 0; + int done; + int done_roll; + static int dowrap = -1; + __pmTimeval tmp; + struct timeval delta_tv; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (dowrap == -1) { + /* PCP_COUNTER_WRAP in environment enables "counter wrap" logic */ + if (getenv("PCP_COUNTER_WRAP") == NULL) + dowrap = 0; + else + dowrap = 1; + } + PM_UNLOCK(__pmLock_libpcp); + + t_req = __pmTimevalSub(&ctxp->c_origin, &ctxp->c_archctl->ac_log->l_label.ill_start); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + fprintf(stderr, "__pmLogFetchInterp @ "); + __pmPrintTimeval(stderr, &ctxp->c_origin); + fprintf(stderr, " t_req=%.6f curvol=%d posn=%ld (vol=%d) serial=%d\n", + t_req, ctxp->c_archctl->ac_log->l_curvol, + (long)ctxp->c_archctl->ac_offset, ctxp->c_archctl->ac_vol, + ctxp->c_archctl->ac_serial); + nr_cache[PM_MODE_FORW] = nr[PM_MODE_FORW] = 0; + nr_cache[PM_MODE_BACK] = nr[PM_MODE_BACK] = 0; + } +#endif + + /* + * the 0.001 is magic slop for 1 msec, which is about as accurate + * as we can expect any of this timing stuff to be ... + */ + if (t_req < -0.001) { + sts = PM_ERR_EOL; + goto all_done; + } + + if (t_req > ctxp->c_archctl->ac_end + 0.001) { + struct timeval end; + __pmTimeval tmp; + + /* + * Past end of archive ... see if it has grown since we last looked. + */ + if (pmGetArchiveEnd(&end) >= 0) { + tmp.tv_sec = (__int32_t)end.tv_sec; + tmp.tv_usec = (__int32_t)end.tv_usec; + ctxp->c_archctl->ac_end = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + } + if (t_req > ctxp->c_archctl->ac_end) { + sts = PM_ERR_EOL; + goto all_done; + } + } + + if ((rp = (pmResult *)malloc(sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *))) == NULL) + return -oserror(); + + rp->timestamp.tv_sec = ctxp->c_origin.tv_sec; + rp->timestamp.tv_usec = ctxp->c_origin.tv_usec; + rp->numpmid = numpmid; + + /* zeroth pass ... clear search and inresult flags */ + for (j = 0; j < hcp->hsize; j++) { + for (hp = hcp->hash[j]; hp != NULL; hp = hp->next) { + pcp = (pmidcntl_t *)hp->data; + for (icp = pcp->first; icp != NULL; icp = icp->next) { + icp->search = icp->inresult = 0; + icp->unbound = icp->want = NULL; + } + } + } + + /* + * first pass ... scan all metrics, establish which ones are in + * the log, and which instances are being requested ... also build + * the skeletal pmResult + */ + ctxp->c_archctl->ac_want = NULL; + for (j = 0; j < numpmid; j++) { + if (pmidlist[j] == PM_ID_NULL) + continue; + hp = __pmHashSearch((int)pmidlist[j], hcp); + if (hp == NULL) { + /* first time we've been asked for this one in this context */ + if ((pcp = (pmidcntl_t *)malloc(sizeof(pmidcntl_t))) == NULL) { + __pmNoMem("__pmLogFetchInterp.pmidcntl_t", sizeof(pmidcntl_t), PM_FATAL_ERR); + /*NOTREACHED*/ + } + pcp->valfmt = -1; + pcp->first = NULL; + sts = __pmHashAdd((int)pmidlist[j], (void *)pcp, hcp); + if (sts < 0) { + rp->numpmid = j; + pmFreeResult(rp); + free(pcp); + return sts; + } + sts = __pmLogLookupDesc(ctxp->c_archctl->ac_log, pmidlist[j], &pcp->desc); + if (sts < 0) + /* not in the archive log */ + pcp->desc.type = -1; + else { + /* enumerate all the instances from the domain underneath */ + int *instlist = NULL; + char **namelist = NULL; + instcntl_t *lcp; + if (pcp->desc.indom == PM_INDOM_NULL) { + sts = 1; + if ((instlist = (int *)malloc(sizeof(int))) == NULL) { + __pmNoMem("__pmLogFetchInterp.instlist", sizeof(int), PM_FATAL_ERR); + } + instlist[0] = PM_IN_NULL; + } + else { + sts = pmGetInDomArchive(pcp->desc.indom, &instlist, &namelist); + } + lcp = NULL; + for (i = 0; i < sts; i++) { + if ((icp = (instcntl_t *)malloc(sizeof(instcntl_t))) == NULL) { + __pmNoMem("__pmLogFetchInterp.instcntl_t", sizeof(instcntl_t), PM_FATAL_ERR); + } + if (lcp) + lcp->next = icp; + else + pcp->first = icp; + lcp = icp; + icp->metric = pcp; + icp->inresult = icp->search = 0; + icp->next = icp->want = icp->unbound = NULL; + icp->inst = instlist[i]; + icp->t_prior = icp->t_next = icp->t_first = icp->t_last = -1; + icp->s_prior = icp->s_next = S_MARK; + icp->v_prior.pval = icp->v_next.pval = NULL; + } + if (instlist != NULL) + free(instlist); + if (namelist != NULL) + free(namelist); + } + } + else + /* seen this one before */ + pcp = (pmidcntl_t *)hp->data; + + pcp->numval = 0; + if (pcp->desc.type == -1) { + pcp->numval = PM_ERR_PMID_LOG; + } + else if (pcp->desc.indom != PM_INDOM_NULL) { + /* use the profile to filter the instances to be returned */ + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (__pmInProfile(pcp->desc.indom, ctxp->c_instprof, icp->inst)) { + icp->inresult = 1; + icp->want = (instcntl_t *)ctxp->c_archctl->ac_want; + ctxp->c_archctl->ac_want = icp; + pcp->numval++; + } + else + icp->inresult = 0; + } + } + else { + pcp->first->inresult = 1; + pcp->first->want = (instcntl_t *)ctxp->c_archctl->ac_want; + ctxp->c_archctl->ac_want = pcp->first; + pcp->numval = 1; + } + } + + if (ctxp->c_archctl->ac_serial == 0) { + /* need gross positioning from temporal index */ + __pmLogSetTime(ctxp); + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + + /* + * and now fine-tuning ... + * back-up (relative to the direction we are reading the log) + * to make sure + */ + if (ctxp->c_delta > 0) { + while (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) >= 0) { + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (t_this <= t_req) { + break; + } + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + update_bounds(ctxp, t_req, logrp, UPD_MARK_NONE, NULL, NULL); + } + } + else { + while (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) >= 0) { + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (t_this > t_req) { + break; + } + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + update_bounds(ctxp, t_req, logrp, UPD_MARK_NONE, NULL, NULL); + } + } + ctxp->c_archctl->ac_serial = 1; + } + + /* get to the last remembered place */ + __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol); + fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET); + + /* + * optimization to supress roll forwards unless really needed ... + * if the sample interval is much shorter than the time between log + * records, then do not roll forwards unless some scanning is + * required ... and if scanning is required in the "forwards" + * direction, no need to roll forwards + */ + done_roll = 0; + + /* + * second pass ... see which metrics are not currently bounded below + */ + ctxp->c_archctl->ac_unbound = NULL; + for (j = 0; j < numpmid; j++) { + if (pmidlist[j] == PM_ID_NULL) + continue; + hp = __pmHashSearch((int)pmidlist[j], hcp); + assert(hp != NULL); + pcp = (pmidcntl_t *)hp->data; + if (pcp->numval > 0) { + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (!icp->inresult) + continue; + if (icp->t_first >= 0 && t_req < icp->t_first) + /* before earliest, don't bother */ + continue; +retry_back: + /* + * At this stage there _may_ be a value earlier in the + * archive of interest ... + * t_prior = -1 => have not explored in this direction, + * so need to go back + * t_prior > t_req => need to push t_prior to be <= t_req + * if possible, so go back + * t_next is valid and a mark and t_next > t_req => need + * to search back also + */ + if (icp->t_prior < 0 || icp->t_prior > t_req || + (icp->t_next >= 0 && icp->s_next == S_MARK && icp->t_next > t_req)) { + if (back == 0 && !done_roll) { + done_roll = 1; + if (ctxp->c_delta > 0) { + /* forwards before scanning back */ + do_roll(ctxp, t_req); + goto retry_back; + } + } + back++; + icp->search = 1; + icp->unbound = (instcntl_t *)ctxp->c_archctl->ac_unbound; + ctxp->c_archctl->ac_unbound = icp; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "search back for inst %d and pmid %s (t_first=%.6f t_prior=%.6f%s t_next=%.6f%s t_last=%.6f)\n", + icp->inst, pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->t_first, + icp->t_prior, statestr[icp->s_prior], + icp->t_next, statestr[icp->s_next], + icp->t_last); + } +#endif + } + } + } + } + + if (back) { + /* + * at least one metric requires a bound from earlier in the log ... + * position ourselves, ... and search + */ + __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol); + fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET); + done = 0; + + while (done < back) { + if (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) < 0) { + /* ran into start of log */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + fprintf(stderr, "Start of Log, %d metric-inst not found\n", + back - done); + } +#endif + break; + } + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (ctxp->c_delta < 0 && t_this >= t_req) { + /* going backwards, and not up to t_req yet */ + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + } + update_bounds(ctxp, t_req, logrp, UPD_MARK_BACK, &done, NULL); + + /* + * forget about those that can never be found from here + * in this direction + */ + for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) { + if (icp->search && t_this <= icp->t_first) { + icp->search = 0; + done++; + } + } + } + /* end of search, trim t_first as required */ + for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) { + if ((icp->t_prior == -1 || icp->t_prior > t_req) && + icp->t_first < t_req) { + icp->t_first = t_req; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "pmid %s inst %d no values before t_first=%.6f\n", + pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_first); + } +#endif + } + icp->search = 0; + } + } + + /* + * third pass ... see which metrics are not currently bounded above + */ + ctxp->c_archctl->ac_unbound = NULL; + for (j = 0; j < numpmid; j++) { + if (pmidlist[j] == PM_ID_NULL) + continue; + hp = __pmHashSearch((int)pmidlist[j], hcp); + assert(hp != NULL); + pcp = (pmidcntl_t *)hp->data; + if (pcp->numval > 0) { + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (!icp->inresult) + continue; + if (icp->t_last >= 0 && t_req > icp->t_last) + /* after latest, don't bother */ + continue; +retry_forw: + /* + * At this stage there _may_ be a value later in the + * archive of interest ... + * t_next = -1 => have not explored in this direction, + * so need to go forward + * t_next < t_req => need to push t_next to be >= t_req + * if possible, so go forward + * t_prior is valid and a mark and t_prior < t_req => need + * to search forward also + */ + if (icp->t_next < 0 || icp->t_next < t_req || + (icp->t_prior >= 0 && icp->s_prior == S_MARK && icp->t_prior < t_req)) { + if (forw == 0 && !done_roll) { + done_roll = 1; + if (ctxp->c_delta < 0) { + /* backwards before scanning forwards */ + do_roll(ctxp, t_req); + goto retry_forw; + } + } + forw++; + icp->search = 1; + icp->unbound = (instcntl_t *)ctxp->c_archctl->ac_unbound; + ctxp->c_archctl->ac_unbound = icp; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "search forw for inst %d and pmid %s (t_first=%.6f t_prior=%.6f%s t_next=%.6f%s t_last=%.6f)\n", + icp->inst, pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->t_first, + icp->t_prior, statestr[icp->s_prior], + icp->t_next, statestr[icp->s_next], + icp->t_last); + } +#endif + } + } + } + } + + if (forw) { + /* + * at least one metric requires a bound from later in the log ... + * position ourselves ... and search + */ + __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol); + fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET); + done = 0; + + while (done < forw) { + if (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) < 0) { + /* ran into end of log */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + fprintf(stderr, "End of Log, %d metric-inst not found\n", + forw - done); + } +#endif + break; + } + tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec; + t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start); + if (ctxp->c_delta > 0 && t_this <= t_req) { + /* going forwards, and not up to t_req yet */ + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + } + update_bounds(ctxp, t_req, logrp, UPD_MARK_FORW, NULL, &done); + + /* + * forget about those that can never be found from here + * in this direction + */ + for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) { + if (icp->search && icp->t_last >= 0 && t_this >= icp->t_last) { + icp->search = 0; + done++; + } + } + } + /* end of search, trim t_last as required */ + for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) { + if (icp->t_next < t_req && + (icp->t_last < 0 || t_req < icp->t_last)) { + icp->t_last = t_req; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + char strbuf[20]; + fprintf(stderr, "pmid %s inst %d no values after t_last=%.6f\n", + pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_last); + } +#endif + } + icp->search = 0; + } + } + + /* + * check to see how many qualifying values there are really going to be + */ + for (j = 0; j < numpmid; j++) { + if (pmidlist[j] == PM_ID_NULL) + continue; + hp = __pmHashSearch((int)pmidlist[j], hcp); + assert(hp != NULL); + pcp = (pmidcntl_t *)hp->data; + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (!icp->inresult) + continue; + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->s_prior == S_MARK || icp->t_prior == -1 || + icp->t_prior > t_req) { + /* no earlier value, so no value */ + pcp->numval--; + icp->inresult = 0; + } + } + else { + /* assume COUNTER or INSTANT */ + if (icp->s_prior == S_MARK || icp->t_prior == -1 || + icp->t_prior > t_req || + icp->s_next == S_MARK || icp->t_next == -1 || icp->t_next < t_req) { + /* in mid-range, and no bound, so no value */ + pcp->numval--; + icp->inresult = 0; + } + else if (pcp->desc.sem == PM_SEM_COUNTER) { + /* + * for counters, has to be arithmetic also, + * else cannot interpolate ... + */ + if (pcp->desc.type != PM_TYPE_32 && + pcp->desc.type != PM_TYPE_U32 && + pcp->desc.type != PM_TYPE_64 && + pcp->desc.type != PM_TYPE_U64 && + pcp->desc.type != PM_TYPE_FLOAT && + pcp->desc.type != PM_TYPE_DOUBLE) + pcp->numval = PM_ERR_TYPE; + } + } + } + } + + for (j = 0; j < numpmid; j++) { + if (pmidlist[j] == PM_ID_NULL) { + rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) - + sizeof(pmValue)); + } + else { + hp = __pmHashSearch((int)pmidlist[j], hcp); + assert(hp != NULL); + pcp = (pmidcntl_t *)hp->data; + + if (pcp->numval >= 1) + rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) + + (pcp->numval - 1)*sizeof(pmValue)); + else + rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) - + sizeof(pmValue)); + } + + if (rp->vset[j] == NULL) { + __pmNoMem("__pmLogFetchInterp.vset", sizeof(pmValueSet), PM_FATAL_ERR); + } + + rp->vset[j]->pmid = pmidlist[j]; + if (pmidlist[j] == PM_ID_NULL) { + rp->vset[j]->numval = 0; + continue; + } + rp->vset[j]->numval = pcp->numval; + rp->vset[j]->valfmt = pcp->valfmt; + + i = 0; + if (pcp->numval > 0) { + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (!icp->inresult) + continue; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP && done_roll) { + char strbuf[20]; + fprintf(stderr, "pmid %s inst %d prior: t=%.6f", + pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->inst, icp->t_prior); + dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 1, icp); + fprintf(stderr, " next: t=%.6f", icp->t_next); + dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 0, icp); + fprintf(stderr, " t_first=%.6f t_last=%.6f\n", + icp->t_first, icp->t_last); + } +#endif + rp->vset[j]->vlist[i].inst = icp->inst; + if (pcp->desc.type == PM_TYPE_32 || pcp->desc.type == PM_TYPE_U32) { + if (icp->t_prior == t_req) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + else if (icp->t_next == t_req) + rp->vset[j]->vlist[i++].value.lval = icp->v_next.lval; + else { + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->t_prior >= 0) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + } + else if (pcp->desc.sem == PM_SEM_INSTANT) { + if (icp->t_prior >= 0 && icp->t_next >= 0) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + } + else { + /* assume COUNTER */ + if (icp->t_prior >= 0 && icp->t_next >= 0) { + if (pcp->desc.type == PM_TYPE_32) { + if (icp->v_next.lval >= icp->v_prior.lval || + dowrap == 0) { + rp->vset[j]->vlist[i++].value.lval = 0.5 + + icp->v_prior.lval + (t_req - icp->t_prior) * + (icp->v_next.lval - icp->v_prior.lval) / + (icp->t_next - icp->t_prior); + } + else { + /* not monotonic increasing and want wrap */ + rp->vset[j]->vlist[i++].value.lval = 0.5 + + (t_req - icp->t_prior) * + (__int32_t)(UINT_MAX - icp->v_prior.lval + 1 + icp->v_next.lval) / + (icp->t_next - icp->t_prior); + rp->vset[j]->vlist[i].value.lval += icp->v_prior.lval; + } + } + else { + pmAtomValue av; + pmAtomValue *avp_prior = (pmAtomValue *)&icp->v_prior.lval; + pmAtomValue *avp_next = (pmAtomValue *)&icp->v_next.lval; + if (avp_next->ul >= avp_prior->ul) { + av.ul = 0.5 + avp_prior->ul + + (t_req - icp->t_prior) * + (avp_next->ul - avp_prior->ul) / + (icp->t_next - icp->t_prior); + } + else { + /* not monotonic increasing */ + if (dowrap) { + av.ul = 0.5 + + (t_req - icp->t_prior) * + (__uint32_t)(UINT_MAX - avp_prior->ul + 1 + avp_next->ul ) / + (icp->t_next - icp->t_prior); + av.ul += avp_prior->ul; + } + else { + __uint32_t tmp; + tmp = avp_prior->ul - avp_next->ul; + av.ul = 0.5 + avp_prior->ul - + (t_req - icp->t_prior) * tmp / + (icp->t_next - icp->t_prior); + } + } + rp->vset[j]->vlist[i++].value.lval = av.ul; + } + } + } + } + } + else if (pcp->desc.type == PM_TYPE_FLOAT && icp->metric->valfmt == PM_VAL_INSITU) { + /* OLD style FLOAT insitu */ + if (icp->t_prior == t_req) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + else if (icp->t_next == t_req) + rp->vset[j]->vlist[i++].value.lval = icp->v_next.lval; + else { + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->t_prior >= 0) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + } + else if (pcp->desc.sem == PM_SEM_INSTANT) { + if (icp->t_prior >= 0 && icp->t_next >= 0) + rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval; + } + else { + /* assume COUNTER */ + pmAtomValue av; + pmAtomValue *avp_prior = (pmAtomValue *)&icp->v_prior.lval; + pmAtomValue *avp_next = (pmAtomValue *)&icp->v_next.lval; + if (icp->t_prior >= 0 && icp->t_next >= 0) { + av.f = avp_prior->f + (t_req - icp->t_prior) * + (avp_next->f - avp_prior->f) / + (icp->t_next - icp->t_prior); + /* yes this IS correct ... */ + rp->vset[j]->vlist[i++].value.lval = av.l; + } + } + } + } + else if (pcp->desc.type == PM_TYPE_FLOAT) { + /* NEW style FLOAT in pmValueBlock */ + int need; + pmValueBlock *vp; + int ok = 1; + + need = PM_VAL_HDR_SIZE + sizeof(float); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + sts = -oserror(); + goto bad_alloc; + } + vp->vlen = need; + vp->vtype = PM_TYPE_FLOAT; + rp->vset[j]->valfmt = PM_VAL_DPTR; + rp->vset[j]->vlist[i++].value.pval = vp; + if (icp->t_prior == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float)); + else if (icp->t_next == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(float)); + else { + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->t_prior >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float)); + else + ok = 0; + } + else if (pcp->desc.sem == PM_SEM_INSTANT) { + if (icp->t_prior >= 0 && icp->t_next >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float)); + else + ok = 0; + } + else { + /* assume COUNTER */ + if (icp->t_prior >= 0 && icp->t_next >= 0) { + pmAtomValue av; + void *avp_prior = icp->v_prior.pval->vbuf; + void *avp_next = icp->v_next.pval->vbuf; + float f_prior; + float f_next; + + memcpy((void *)&av.f, avp_prior, sizeof(av.f)); + f_prior = av.f; + memcpy((void *)&av.f, avp_next, sizeof(av.f)); + f_next = av.f; + + av.f = f_prior + (t_req - icp->t_prior) * + (f_next - f_prior) / + (icp->t_next - icp->t_prior); + memcpy((void *)vp->vbuf, (void *)&av.f, sizeof(av.f)); + } + else + ok = 0; + } + } + if (!ok) { + i--; + free(vp); + } + } + else if (pcp->desc.type == PM_TYPE_64 || pcp->desc.type == PM_TYPE_U64) { + int need; + pmValueBlock *vp; + int ok = 1; + + need = PM_VAL_HDR_SIZE + sizeof(__int64_t); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + sts = -oserror(); + goto bad_alloc; + } + vp->vlen = need; + if (pcp->desc.type == PM_TYPE_64) + vp->vtype = PM_TYPE_64; + else + vp->vtype = PM_TYPE_U64; + rp->vset[j]->valfmt = PM_VAL_DPTR; + rp->vset[j]->vlist[i++].value.pval = vp; + if (icp->t_prior == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t)); + else if (icp->t_next == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(__int64_t)); + else { + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->t_prior >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t)); + else + ok = 0; + } + else if (pcp->desc.sem == PM_SEM_INSTANT) { + if (icp->t_prior >= 0 && icp->t_next >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t)); + else + ok = 0; + } + else { + /* assume COUNTER */ + if (icp->t_prior >= 0 && icp->t_next >= 0) { + pmAtomValue av; + void *avp_prior = (void *)icp->v_prior.pval->vbuf; + void *avp_next = (void *)icp->v_next.pval->vbuf; + if (pcp->desc.type == PM_TYPE_64) { + __int64_t ll_prior; + __int64_t ll_next; + memcpy((void *)&av.ll, avp_prior, sizeof(av.ll)); + ll_prior = av.ll; + memcpy((void *)&av.ll, avp_next, sizeof(av.ll)); + ll_next = av.ll; + if (ll_next >= ll_prior || dowrap == 0) + av.ll = ll_next - ll_prior; + else + /* not monotonic increasing and want wrap */ + av.ll = (__int64_t)(ULONGLONG_MAX - ll_prior + 1 + ll_next); + av.ll = (__int64_t)(0.5 + (double)ll_prior + + (t_req - icp->t_prior) * (double)av.ll / (icp->t_next - icp->t_prior)); + memcpy((void *)vp->vbuf, (void *)&av.ll, sizeof(av.ll)); + } + else { + __int64_t ull_prior; + __int64_t ull_next; + memcpy((void *)&av.ull, avp_prior, sizeof(av.ull)); + ull_prior = av.ull; + memcpy((void *)&av.ull, avp_next, sizeof(av.ull)); + ull_next = av.ull; + if (ull_next >= ull_prior) { + av.ull = ull_next - ull_prior; +#if !defined(HAVE_CAST_U64_DOUBLE) + { + double tmp; + + if (SIGN_64_MASK & av.ull) + tmp = (double)(__int64_t)(av.ull & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK; + else + tmp = (double)(__int64_t)av.ull; + + av.ull = (__uint64_t)(0.5 + (double)ull_prior + + (t_req - icp->t_prior) * tmp / + (icp->t_next - icp->t_prior)); + } +#else + av.ull = (__uint64_t)(0.5 + (double)ull_prior + + (t_req - icp->t_prior) * (double)av.ull / + (icp->t_next - icp->t_prior)); +#endif + } + else { + /* not monotonic increasing */ + if (dowrap) { + av.ull = ULONGLONG_MAX - ull_prior + 1 + + ull_next; +#if !defined(HAVE_CAST_U64_DOUBLE) + { + double tmp; + + if (SIGN_64_MASK & av.ull) + tmp = (double)(__int64_t)(av.ull & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK; + else + tmp = (double)(__int64_t)av.ull; + + av.ull = (__uint64_t)(0.5 + (double)ull_prior + + (t_req - icp->t_prior) * tmp / + (icp->t_next - icp->t_prior)); + } +#else + av.ull = (__uint64_t)(0.5 + (double)ull_prior + + (t_req - icp->t_prior) * (double)av.ull / + (icp->t_next - icp->t_prior)); +#endif + } + else { + __uint64_t tmp; + tmp = ull_prior - ull_next; +#if !defined(HAVE_CAST_U64_DOUBLE) + { + double xtmp; + + if (SIGN_64_MASK & av.ull) + xtmp = (double)(__int64_t)(tmp & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK; + else + xtmp = (double)(__int64_t)tmp; + + av.ull = (__uint64_t)(0.5 + (double)ull_prior - + (t_req - icp->t_prior) * xtmp / + (icp->t_next - icp->t_prior)); + } +#else + av.ull = (__uint64_t)(0.5 + (double)ull_prior - + (t_req - icp->t_prior) * (double)tmp / + (icp->t_next - icp->t_prior)); +#endif + } + } + memcpy((void *)vp->vbuf, (void *)&av.ull, sizeof(av.ull)); + } + } + else + ok = 0; + } + } + if (!ok) { + i--; + free(vp); + } + } + else if (pcp->desc.type == PM_TYPE_DOUBLE) { + int need; + pmValueBlock *vp; + int ok = 1; + + need = PM_VAL_HDR_SIZE + sizeof(double); + if ((vp = (pmValueBlock *)malloc(need)) == NULL) { + sts = -oserror(); + goto bad_alloc; + } + vp->vlen = need; + vp->vtype = PM_TYPE_DOUBLE; + rp->vset[j]->valfmt = PM_VAL_DPTR; + rp->vset[j]->vlist[i++].value.pval = vp; + if (icp->t_prior == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double)); + else if (icp->t_next == t_req) + memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(double)); + else { + if (pcp->desc.sem == PM_SEM_DISCRETE) { + if (icp->t_prior >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double)); + else + ok = 0; + } + else if (pcp->desc.sem == PM_SEM_INSTANT) { + if (icp->t_prior >= 0 && icp->t_next >= 0) + memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double)); + else + ok = 0; + } + else { + /* assume COUNTER */ + if (icp->t_prior >= 0 && icp->t_next >= 0) { + pmAtomValue av; + void *avp_prior = (void *)icp->v_prior.pval->vbuf; + void *avp_next = (void *)icp->v_next.pval->vbuf; + double d_prior; + double d_next; + memcpy((void *)&av.d, avp_prior, sizeof(av.d)); + d_prior = av.d; + memcpy((void *)&av.d, avp_next, sizeof(av.d)); + d_next = av.d; + av.d = d_prior + (t_req - icp->t_prior) * + (d_next - d_prior) / + (icp->t_next - icp->t_prior); + memcpy((void *)vp->vbuf, (void *)&av.d, sizeof(av.d)); + } + else + ok = 0; + } + } + if (!ok) { + i--; + free(vp); + } + } + else if ((pcp->desc.type == PM_TYPE_AGGREGATE || + pcp->desc.type == PM_TYPE_EVENT || + pcp->desc.type == PM_TYPE_HIGHRES_EVENT || + pcp->desc.type == PM_TYPE_STRING) && + icp->t_prior >= 0) { + int need; + pmValueBlock *vp; + + need = icp->v_prior.pval->vlen; + + vp = (pmValueBlock *)malloc(need); + if (vp == NULL) { + sts = -oserror(); + goto bad_alloc; + } + rp->vset[j]->valfmt = PM_VAL_DPTR; + rp->vset[j]->vlist[i++].value.pval = vp; + memcpy((void *)vp, icp->v_prior.pval, need); + } + else { + /* unknown type - skip it, else junk in result */ + i--; + } + } + } + } + + *result = rp; + sts = 0; + +all_done: + pmXTBdeltaToTimeval(ctxp->c_delta, ctxp->c_mode, &delta_tv); + ctxp->c_origin.tv_sec += delta_tv.tv_sec; + ctxp->c_origin.tv_usec += delta_tv.tv_usec; + while (ctxp->c_origin.tv_usec > 1000000) { + ctxp->c_origin.tv_sec++; + ctxp->c_origin.tv_usec -= 1000000; + } + while (ctxp->c_origin.tv_usec < 0) { + ctxp->c_origin.tv_sec--; + ctxp->c_origin.tv_usec += 1000000; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INTERP) { + fprintf(stderr, "__pmLogFetchInterp: log reads: forward %ld", + nr[PM_MODE_FORW]); + if (nr_cache[PM_MODE_FORW]) + fprintf(stderr, " (+%ld cached)", nr_cache[PM_MODE_FORW]); + fprintf(stderr, " backwards %ld", + nr[PM_MODE_BACK]); + if (nr_cache[PM_MODE_BACK]) + fprintf(stderr, " (+%ld cached)", nr_cache[PM_MODE_BACK]); + fprintf(stderr, "\n"); + } +#endif + + return sts; + +bad_alloc: + /* + * leaks a little (vlist[] stuff) ... but does not really matter at + * this point, chance of anything good happening from here on are + * pretty remote + */ + rp->vset[j]->numval = i; + while (++j < numpmid) + rp->vset[j]->numval = 0; + pmFreeResult(rp); + + return sts; + +} + +void +__pmLogResetInterp(__pmContext *ctxp) +{ + __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc; + double t_req; + __pmHashNode *hp; + int k; + pmidcntl_t *pcp; + instcntl_t *icp; + + if (hcp->hsize == 0) + return; + + t_req = __pmTimevalSub(&ctxp->c_origin, &ctxp->c_archctl->ac_log->l_label.ill_start); + + for (k = 0; k < hcp->hsize; k++) { + for (hp = hcp->hash[k]; hp != NULL; hp = hp->next) { + pcp = (pmidcntl_t *)hp->data; + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (icp->t_prior > t_req || icp->t_next < t_req) { + icp->t_prior = icp->t_next = -1; + if (pcp->valfmt != PM_VAL_INSITU) { + if (icp->v_prior.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_prior.pval); + if (icp->v_next.pval != NULL) + __pmUnpinPDUBuf((void *)icp->v_next.pval); + } + icp->v_prior.pval = icp->v_next.pval = NULL; + } + } + } + } +} + +/* + * Free interp data when context is closed ... + * - pinned PDU buffers holding values used for interpolation + * - hash structures for finding metrics and instances + * - read_cache contents + * + * Called with ctxp->c_lock held. + */ +void +__pmFreeInterpData(__pmContext *ctxp) +{ + if (ctxp->c_archctl->ac_pmid_hc.hsize > 0) { + /* we have done some interpolation ... */ + __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc; + __pmHashNode *hp; + pmidcntl_t *pcp; + instcntl_t *icp; + int j; + + for (j = 0; j < hcp->hsize; j++) { + __pmHashNode *last_hp = NULL; + /* + * Don't free __pmHashNode until hp->next has been traversed, + * hence free lags one node in the chain (last_hp used for free). + * Same for linked list of instcntl_t structs (use last_icp + * for free in this case). + */ + for (hp = hcp->hash[j]; hp != NULL; hp = hp->next) { + instcntl_t *last_icp = NULL; + pcp = (pmidcntl_t *)hp->data; + for (icp = pcp->first; icp != NULL; icp = icp->next) { + if (pcp->valfmt != PM_VAL_INSITU) { + /* + * Held values may be in PDU buffers, unpin the PDU + * buffers just in case (__pmUnpinPDUBuf is a NOP if + * the value is not in a PDU buffer) + */ + if (icp->v_prior.pval != NULL) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) { + char strbuf[20]; + fprintf(stderr, "release pmid %s inst %d prior\n", + pmIDStr_r(pcp->desc.pmid, strbuf, sizeof(strbuf)), icp->inst); + } +#endif + __pmUnpinPDUBuf((void *)icp->v_prior.pval); + } + if (icp->v_next.pval != NULL) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) { + char strbuf[20]; + fprintf(stderr, "release pmid %s inst %d next\n", + pmIDStr_r(pcp->desc.pmid, strbuf, sizeof(strbuf)), icp->inst); + } +#endif + __pmUnpinPDUBuf((void *)icp->v_next.pval); + } + } + if (last_icp != NULL) + free(last_icp); + last_icp = icp; + } + if (last_icp != NULL) + free(last_icp); + if (last_hp != NULL) { + if (last_hp->data != NULL) + free(last_hp->data); + free(last_hp); + } + last_hp = hp; + } + if (last_hp != NULL) { + if (last_hp->data != NULL) + free(last_hp->data); + free(last_hp); + } + free(hcp->hash); + /* just being paranoid here */ + hcp->hash = NULL; + hcp->hsize = 0; + } + } + + if (ctxp->c_archctl->ac_cache != NULL) { + /* read cache allocated, work to be done */ + cache_t *cache = (cache_t *)ctxp->c_archctl->ac_cache; + cache_t *cp; + + for (cp = cache; cp < &cache[NUMCACHE]; cp++) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) { + fprintf(stderr, "read cache entry " PRINTF_P_PFX "%p: mfp=" PRINTF_P_PFX "%p rp=" PRINTF_P_PFX "%p\n", cp, cp->mfp, cp->rp); + } +#endif + if (cp->mfp == ctxp->c_archctl->ac_log->l_mfp) { + if (cp->rp != NULL) + pmFreeResult(cp->rp); + cp->rp = NULL; + cp->mfp = NULL; + cp->used = 0; + } + } + } +} diff --git a/src/libpcp/src/ipc.c b/src/libpcp/src/ipc.c new file mode 100644 index 0000000..a9c7538 --- /dev/null +++ b/src/libpcp/src/ipc.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#ifdef HAVE_VALUES_H +#include <values.h> +#endif +#ifdef HAVE_SYS_CONFIG_H +#include <sys/config.h> +#endif + +/* + * We keep a table of connection state for each interesting file descriptor here. + * The version field holds the version of the software at the other end of the + * connection end point (0 is unknown, 1 or 2 are also valid). + * The socket field is used to tell whether this is a socket or pipe (or a file) + * connection, which is most important for the Windows port, as socket interfaces + * are "special" and do not use the usual file descriptor read/write/close calls, + * but must rather use recv/send/closesocket. + * + * The table entries are of fixed length, but the actual size depends on compile + * time options used (in particular, the secure sockets setting requires further + * space allocated to hold the additional security metadata for each socket). + */ +typedef struct { + int version; /* one or two */ + int socket; /* true or false */ + char data[0]; /* opaque data (optional) */ +} __pmIPC; + +static int __pmLastUsedFd = -INT_MAX; +static __pmIPC *__pmIPCTable; +static int ipctablecount; +static int ipcentrysize; + +static inline __pmIPC * +__pmIPCTablePtr(int fd) +{ + char *entry = (char *)__pmIPCTable; + return (__pmIPC *)(entry + fd * ipcentrysize); +} + +/* + * always called with __pmLock_libpcp held + */ +static int +__pmResizeIPC(int fd) +{ + size_t size; + int oldcount; + + if (__pmIPCTable == NULL || fd >= ipctablecount) { + if (ipcentrysize == 0) + ipcentrysize = sizeof(__pmIPC) + __pmDataIPCSize(); + oldcount = ipctablecount; + if (ipctablecount == 0) + ipctablecount = 4; + while (fd >= ipctablecount) + ipctablecount *= 2; + size = ipcentrysize * ipctablecount; + __pmIPCTable = (__pmIPC *)realloc(__pmIPCTable, size); + if (__pmIPCTable == NULL) + return -oserror(); + size -= ipcentrysize * oldcount; + memset(__pmIPCTablePtr(oldcount), 0, size); + } + return 0; +} + +int +__pmSetVersionIPC(int fd, int version) +{ + int sts; + + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmSetVersionIPC: fd=%d version=%d\n", fd, version); + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((sts = __pmResizeIPC(fd)) < 0) { + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + + __pmIPCTablePtr(fd)->version = version; + __pmLastUsedFd = fd; + + if (pmDebug & DBG_TRACE_CONTEXT) + __pmPrintIPC(); + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmSetSocketIPC(int fd) +{ + int sts; + + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmSetSocketIPC: fd=%d\n", fd); + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((sts = __pmResizeIPC(fd)) < 0) { + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + + __pmIPCTablePtr(fd)->socket = 1; + __pmLastUsedFd = fd; + + if (pmDebug & DBG_TRACE_CONTEXT) + __pmPrintIPC(); + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmVersionIPC(int fd) +{ + int sts; + + if (fd == PDU_OVERRIDE2) + return PDU_VERSION2; + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (__pmIPCTable == NULL || fd < 0 || fd >= ipctablecount) { + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, + "IPC protocol botch: table->" PRINTF_P_PFX "%p fd=%d sz=%d\n", + __pmIPCTable, fd, ipctablecount); + PM_UNLOCK(__pmLock_libpcp); + return UNKNOWN_VERSION; + } + sts = __pmIPCTablePtr(fd)->version; + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmLastVersionIPC() +{ + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + sts = __pmVersionIPC(__pmLastUsedFd); + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmSocketIPC(int fd) +{ + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (__pmIPCTable == NULL || fd < 0 || fd >= ipctablecount) { + PM_UNLOCK(__pmLock_libpcp); + return 0; + } + sts = __pmIPCTablePtr(fd)->socket; + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmSetDataIPC(int fd, void *data) +{ + char *dest; + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((sts = __pmResizeIPC(fd)) < 0) { + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmSetDataIPC: fd=%d data=%p(sz=%d)\n", + fd, data, (int)(ipcentrysize - sizeof(__pmIPC))); + + dest = ((char *)__pmIPCTablePtr(fd)) + sizeof(__pmIPC); + memcpy(dest, data, ipcentrysize - sizeof(__pmIPC)); + __pmLastUsedFd = fd; + + if (pmDebug & DBG_TRACE_CONTEXT) + __pmPrintIPC(); + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +__pmDataIPC(int fd, void *data) +{ + char *source; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (fd < 0 || fd >= ipctablecount || __pmIPCTable == NULL || + ipcentrysize == sizeof(__pmIPC)) { + PM_UNLOCK(__pmLock_libpcp); + return -ESRCH; + } + source = ((char *)__pmIPCTablePtr(fd)) + sizeof(__pmIPC); + if ((pmDebug & DBG_TRACE_CONTEXT) && (pmDebug & DBG_TRACE_DESPERATE)) + fprintf(stderr, "__pmDataIPC: fd=%d, data=%p(sz=%d)\n", + fd, source, (int)(ipcentrysize - sizeof(__pmIPC))); + memcpy(data, source, ipcentrysize - sizeof(__pmIPC)); + + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +/* + * Called by log readers who need version info for result decode, + * but don't have a socket fd (have a FILE* & fileno though). + * Also at start of version exchange before version is known + * (when __pmDecodeError is called before knowing version). + */ +void +__pmOverrideLastFd(int fd) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + __pmLastUsedFd = fd; + PM_UNLOCK(__pmLock_libpcp); +} + +void +__pmResetIPC(int fd) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (__pmIPCTable && fd >= 0 && fd < ipctablecount) + memset(__pmIPCTablePtr(fd), 0, ipcentrysize); + PM_UNLOCK(__pmLock_libpcp); +} + +void +__pmPrintIPC(void) +{ + int i; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + fprintf(stderr, "IPC table fd(PDU version):"); + for (i = 0; i < ipctablecount; i++) { + if (__pmIPCTablePtr(i)->version != UNKNOWN_VERSION) + fprintf(stderr, " %d(%d,%d)", i, __pmIPCTablePtr(i)->version, + __pmIPCTablePtr(i)->socket); + } + fputc('\n', stderr); + PM_UNLOCK(__pmLock_libpcp); +} diff --git a/src/libpcp/src/lock.c b/src/libpcp/src/lock.c new file mode 100644 index 0000000..0bcf2a1 --- /dev/null +++ b/src/libpcp/src/lock.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 2011 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +#ifdef PM_MULTI_THREAD +typedef struct { + void *lock; + int count; +} lockdbg_t; + +#define PM_LOCK_OP 1 +#define PM_UNLOCK_OP 2 + +static int multi_init[PM_SCOPE_MAX+1]; +static pthread_t multi_seen[PM_SCOPE_MAX+1]; + +/* the big libpcp lock */ +#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +pthread_mutex_t __pmLock_libpcp = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +#else +pthread_mutex_t __pmLock_libpcp = PTHREAD_MUTEX_INITIALIZER; + +#ifndef HAVE___THREAD +pthread_key_t __pmTPDKey = 0; + +static void +__pmTPD__destroy(void *addr) +{ + free(addr); +} +#endif +#endif + +void +__pmInitLocks(void) +{ + static pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER; + static int done = 0; + int psts; + char errmsg[PM_MAXERRMSGLEN]; + if ((psts = pthread_mutex_lock(&init)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_mutex_lock failed: %s", errmsg); + exit(4); + } + if (!done) { +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + /* + * Unable to initialize at compile time, need to do it here in + * a one trip for all threads run-time initialization. + */ + pthread_mutexattr_t attr; + + if ((psts = pthread_mutexattr_init(&attr)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_mutexattr_init failed: %s", errmsg); + exit(4); + } + if ((psts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_mutexattr_settype failed: %s", errmsg); + exit(4); + } + if ((psts = pthread_mutex_init(&__pmLock_libpcp, &attr)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_mutex_init failed: %s", errmsg); + exit(4); + } +#endif +#ifndef HAVE___THREAD + /* first thread here creates the thread private data key */ + if ((psts = pthread_key_create(&__pmTPDKey, __pmTPD__destroy)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_key_create failed: %s", errmsg); + exit(4); + } +#endif + done = 1; + } + if ((psts = pthread_mutex_unlock(&init)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_mutex_unlock failed: %s", errmsg); + exit(4); + } +#ifndef HAVE___THREAD + if (pthread_getspecific(__pmTPDKey) == NULL) { + __pmTPD *tpd = (__pmTPD *)malloc(sizeof(__pmTPD)); + if (tpd == NULL) { + __pmNoMem("__pmInitLocks: __pmTPD", sizeof(__pmTPD), PM_FATAL_ERR); + /*NOTREACHED*/ + } + if ((psts = pthread_setspecific(__pmTPDKey, tpd)) != 0) { + pmErrStr_r(-psts, errmsg, sizeof(errmsg)); + fprintf(stderr, "__pmInitLocks: pthread_setspecific failed: %s", errmsg); + exit(4); + } + tpd->curcontext = PM_CONTEXT_UNDEF; + } +#endif +} + +int +__pmMultiThreaded(int scope) +{ + int sts = 0; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (!multi_init[scope]) { + multi_init[scope] = 1; + multi_seen[scope] = pthread_self(); + } + else { + if (!pthread_equal(multi_seen[scope], pthread_self())) + sts = 1; + } + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +#ifdef PM_MULTI_THREAD_DEBUG +void +__pmDebugLock(int op, void *lock, const char *file, int line) +{ + int report = 0; + int ctx; + static __pmHashCtl hashctl = { 0, 0, NULL }; + __pmHashNode *hp = NULL; + lockdbg_t *ldp; + int try; + int sts; + + if (lock == (void *)&__pmLock_libpcp) { + if ((pmDebug & DBG_TRACE_APPL0) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0)) + report = DBG_TRACE_APPL0; + } + else if ((ctx = __pmIsContextLock(lock)) >= 0) { + if ((pmDebug & DBG_TRACE_APPL1) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0)) + report = DBG_TRACE_APPL1; + } + else { + if ((pmDebug & DBG_TRACE_APPL2) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0)) + report = DBG_TRACE_APPL2; + } + + if (report) { + __psint_t key = (__psint_t)lock; + fprintf(stderr, "%s:%d %s", file, line, op == PM_LOCK_OP ? "lock" : "unlock"); + try = 0; +again: + hp = __pmHashSearch((unsigned int)key, &hashctl); + while (hp != NULL) { + ldp = (lockdbg_t *)hp->data; + if (ldp->lock == lock) + break; + hp = hp->next; + } + if (hp == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + ldp = (lockdbg_t *)malloc(sizeof(lockdbg_t)); + ldp->lock = lock; + ldp->count = 0; + sts = __pmHashAdd((unsigned int)key, (void *)ldp, &hashctl); + if (sts == 1) { + try++; + if (try == 1) + goto again; + } + hp = NULL; + fprintf(stderr, " hash control failure: %s\n", pmErrStr_r(-sts, errmsg, sizeof(errmsg))); + } + } + + if (report == DBG_TRACE_APPL0) { + fprintf(stderr, "(global_libpcp)"); + } + else if (report == DBG_TRACE_APPL1) { + fprintf(stderr, "(ctx %d)", ctx); + } + else if (report == DBG_TRACE_APPL2) { + if ((ctx = __pmIsChannelLock(lock)) >= 0) + fprintf(stderr, "(ctx %d ipc channel)", ctx); + else if (__pmIsDeriveLock(lock)) + fprintf(stderr, "(derived_metric)"); + else + fprintf(stderr, "(" PRINTF_P_PFX "%p)", lock); + } + if (report) { + if (hp != NULL) { + ldp = (lockdbg_t *)hp->data; + if (op == PM_LOCK_OP) { + if (ldp->count != 0) + fprintf(stderr, " [count=%d]", ldp->count); + ldp->count++; + } + else { + if (ldp->count != 1) + fprintf(stderr, " [count=%d]", ldp->count); + ldp->count--; + } + } + fputc('\n', stderr); +#ifdef HAVE_BACKTRACE +#define MAX_TRACE_DEPTH 32 + { + void *backaddr[MAX_TRACE_DEPTH]; + sts = backtrace(backaddr, MAX_TRACE_DEPTH); + if (sts > 0) { + char **symbols; + symbols = backtrace_symbols(backaddr, MAX_TRACE_DEPTH); + if (symbols != NULL) { + int i; + fprintf(stderr, "backtrace:\n"); + for (i = 0; i < sts; i++) + fprintf(stderr, " %s\n", symbols[i]); + free(symbols); + } + } + } + +#endif + } +} +#else +#define __pmDebugLock(op, lock, file, line) do { } while (0) +#endif + +int +__pmLock(void *lock, const char *file, int line) +{ + int sts; + + if (pmDebug & DBG_TRACE_LOCK) + __pmDebugLock(PM_LOCK_OP, lock, file, line); + + if ((sts = pthread_mutex_lock(lock)) != 0) { + sts = -sts; + if (pmDebug & DBG_TRACE_DESPERATE) + fprintf(stderr, "%s:%d: lock failed: %s\n", file, line, pmErrStr(sts)); + } + return sts; +} + +int +__pmUnlock(void *lock, const char *file, int line) +{ + int sts; + + if (pmDebug & DBG_TRACE_LOCK) + __pmDebugLock(PM_UNLOCK_OP, lock, file, line); + + if ((sts = pthread_mutex_unlock(lock)) != 0) { + sts = -sts; + if (pmDebug & DBG_TRACE_DESPERATE) + fprintf(stderr, "%s:%d: unlock failed: %s\n", file, line, pmErrStr(sts)); + } + return sts; +} + +#else /* !PM_MULTI_THREAD - symbols exposed at the shlib ABI level */ +void *__pmLock_libpcp; +void __pmInitLocks(void) { } +int __pmMultiThreaded(int scope) { (void)scope; return 0; } +int __pmLock(void *l, const char *f, int n) { (void)l, (void)f, (void)n; return 0; } +int __pmUnlock(void *l, const char *f, int n) { (void)l, (void)f, (void)n; return 0; } +#endif diff --git a/src/libpcp/src/logconnect.c b/src/libpcp/src/logconnect.c new file mode 100644 index 0000000..8a2365e --- /dev/null +++ b/src/libpcp/src/logconnect.c @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * Do not need ctxp->c_pmcd->pc_lock lock around __pmSendCreds() call, + * as the connection to pmlogger has not been created, so no-one else + * could be using the fd. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +#include <limits.h> +#include <sys/stat.h> + +/* + * Return timeout (in seconds) to be used when pmlc is communicating + * with pmlogger ... used externally from pmlc and internally from + * __pmConnectLogger() and __pmControlLogger() + */ +int +__pmLoggerTimeout(void) +{ + static int timeout = TIMEOUT_NEVER; + static int done_default = 0; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (!done_default) { + char *timeout_str; + char *end_ptr; + if ((timeout_str = getenv("PMLOGGER_REQUEST_TIMEOUT")) != NULL) { + /* + * Only a positive integer (the unit is seconds) is OK + */ + timeout = strtol(timeout_str, &end_ptr, 10); + if (*end_ptr != '\0' || timeout < 0) { + __pmNotifyErr(LOG_WARNING, + "ignored bad PMLOGGER_REQUEST_TIMEOUT = '%s'\n", + timeout_str); + timeout = TIMEOUT_NEVER; + } + } + done_default = 1; + } + PM_UNLOCK(__pmLock_libpcp); + + return timeout; +} + +#if defined(HAVE_STRUCT_SOCKADDR_UN) +/* + * Return the path to the default PMLOGGER local unix domain socket. + * in the buffer propvided. + * Return the path regardless of whether unix domain sockets are + * supported by our build. Other functions can then print reasonable + * messages if an attempt is made to use one. + */ +const char * +__pmLogLocalSocketDefault(int pid, char *buf, size_t bufSize) +{ + /* snprintf guarantees a terminating nul, even if the output is truncated. */ + if (pid == PM_LOG_PRIMARY_PID) { /* primary */ + snprintf(buf, bufSize, "%s/pmlogger.primary.socket", + pmGetConfig("PCP_RUN_DIR")); + } + else { + snprintf(buf, bufSize, "%s/pmlogger.%d.socket", + pmGetConfig("PCP_RUN_DIR"), pid); + } + + return buf; +} + +/* + * Return the path to the user's own PMLOGGER local unix domain socket + * in the buffer provided. + * Return the path regardless of whether unix domain sockets are + * supported by our build. Other functions can then print reasonable + * messages if an attempt is made to use one. + */ +const char * +__pmLogLocalSocketUser(int pid, char *buf, size_t bufSize) +{ + char home[MAXPATHLEN]; + char *homeResult; + + homeResult = __pmHomedirFromID(getuid(), home, sizeof(home)); + if (homeResult == NULL) + return NULL; + + /* snprintf guarantees a terminating nul, even if the output is truncated. */ + snprintf(buf, bufSize, "%s/.pcp/run/pmlogger.%d.socket", + homeResult, pid); + + return buf; +} +#endif + +/* + * Common function for attempting connections to pmlogger. + */ +static int +connectLogger(int fd, __pmSockAddr *myAddr) +{ + /* Attempt the connection. */ + int sts = __pmConnect(fd, myAddr, __pmSockAddrSize()); + + /* Successful connection? */ + if (sts >= 0) + return sts; + + sts = neterror(); + if (sts == EINPROGRESS) { + /* We're in progress - wait on select. */ + struct timeval stv = { 0, 000000 }; + struct timeval *pstv; + __pmFdSet rfds; + int rc; + stv.tv_sec = __pmLoggerTimeout(); + pstv = stv.tv_sec ? &stv : NULL; + + __pmFD_ZERO(&rfds); + __pmFD_SET(fd, &rfds); + if ((rc = __pmSelectRead(fd+1, &rfds, pstv)) == 1) { + sts = __pmConnectCheckError(fd); + } + else if (rc == 0) { + sts = ETIMEDOUT; + } + else { + sts = (rc < 0) ? neterror() : EINVAL; + } + } + sts = -sts; + + /* Successful connection? */ + if (sts >= 0) + return sts; + + /* Unsuccessful connection. */ + __pmCloseSocket(fd); + return sts; +} + +/* + * Attempt connection to pmlogger via a local socket. + */ +static int +connectLoggerLocal(const char *local_socket) +{ +#if defined(HAVE_STRUCT_SOCKADDR_UN) + char socket_path[MAXPATHLEN]; + int fd; + int sts; + __pmSockAddr *myAddr; + + /* Create a socket */ + fd = __pmCreateUnixSocket(); + if (fd < 0) + return -ECONNREFUSED; + + /* Set up the socket address. */ + myAddr = __pmSockAddrAlloc(); + if (myAddr == NULL) { + __pmNotifyErr(LOG_ERR, "__pmConnectLogger: out of memory\n"); + __pmCloseSocket(fd); + return -ENOMEM; + } + __pmSockAddrSetFamily(myAddr, AF_UNIX); + + /* + * Set the socket path. All socket paths are absolute, but strip off any redundant + * initial path separators. + * snprintf is guaranteed to add a nul byte. + */ + while (*local_socket == __pmPathSeparator()) + ++local_socket; + snprintf(socket_path, sizeof(socket_path), "%c%s", __pmPathSeparator(), local_socket); + __pmSockAddrSetPath(myAddr, socket_path); + + /* Attempt to connect */ + sts = connectLogger(fd, myAddr); + __pmSockAddrFree(myAddr); + + if (sts < 0) { + __pmCloseSocket(fd); + return sts; + } + + return fd; +#else +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: local_socket == %s is not supported\n", + local_socket); +#endif + return -ECONNREFUSED; +#endif +} + +/* + * Determine how to connect based on connectionSpec, pid and port: + * + * If hostname is "local:[//][path]", then try the socket at + * /path, if specified and the socket at PCP_RUN_DIR/pmlogger.<pid>.socket otherwise, + * where <pid> is "primary" if pid is PM_LOG_PRIMARY_PID. + * If this fails then set connectionSpec to "localhost" and then + * + * ConnectionSpec is a host name. + * If port is set, use hostname:port, otherwise + * + * Use hostname+pid to find port, assuming pmcd is running there + */ +int +__pmConnectLogger(const char *connectionSpec, int *pid, int *port) +{ + int n, sts = 0; + __pmLogPort *lpp; + int fd; /* Fd for socket connection to pmcd */ + __pmPDU *pb; + __pmPDUHdr *php; + int pinpdu; + __pmHostEnt *servInfo; + __pmSockAddr *myAddr; + void *enumIx; + const char *prefix_end; + size_t prefix_len; + char path[MAXPATHLEN]; + int originalPid; + int wasLocal; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger(host=%s, pid=%d, port=%d)\n", + connectionSpec, *pid, *port); +#endif + + if (*pid == PM_LOG_NO_PID && *port == PM_LOG_PRIMARY_PORT) { + /* + * __pmLogFindPort and __pmLogLocalSocketDefault can only lookup + * based on pid, so xlate the request + */ + *pid = PM_LOG_PRIMARY_PID; + *port = PM_LOG_NO_PORT; + } + + /* + * If the prefix is "local:[path]", we may try the connection more than once using + * "unix:[path]" followed by "localhost". + */ + for (originalPid = *pid; /**/; *pid = originalPid) { + fd = -1; + /* Look for a "local:" or a "unix:" prefix. */ + wasLocal = 0; + prefix_end = strchr(connectionSpec, ':'); + if (prefix_end != NULL) { + prefix_len = prefix_end - connectionSpec + 1; + if ((wasLocal = (prefix_len == 6 && strncmp(connectionSpec, "local:", prefix_len) == 0)) || + (prefix_len == 5 && strncmp(connectionSpec, "unix:", prefix_len) == 0)) { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (connectionSpec[prefix_len] != '\0') { + /* Try the specified local socket directly. */ + fd = connectLoggerLocal(connectionSpec + prefix_len); + } + else if (*pid != PM_LOG_NO_PID) { + /* Try the socket indicated by the pid. */ + connectionSpec = __pmLogLocalSocketDefault(*pid, path, sizeof(path)); + fd = connectLoggerLocal(connectionSpec); + if (fd < 0) { + /* Try the socket in the user's home directory. */ + connectionSpec = __pmLogLocalSocketUser(*pid, path, sizeof(path)); + if (connectionSpec != NULL) + fd = connectLoggerLocal(connectionSpec); + } + } +#endif + } + if (fd >= 0) + sts = 0; + else + sts = fd; + } + else { + /* + * If not a url, then connectionSpec is a host name. + * + * Catch pid == PM_LOG_ALL_PIDS ... this tells __pmLogFindPort + * to get all ports + */ + if (*pid == PM_LOG_ALL_PIDS) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: pid == PM_LOG_ALL_PIDS makes no sense here\n"); +#endif + return -ECONNREFUSED; + } + + if (*port == PM_LOG_NO_PORT) { + if ((n = __pmLogFindPort(connectionSpec, *pid, &lpp)) < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectLogger: __pmLogFindPort: %s\n", pmErrStr_r(n, errmsg, sizeof(errmsg))); + } +#endif + return n; + } + else if (n != 1) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: __pmLogFindPort -> 1, cannot contact pmlogger\n"); +#endif + return -ECONNREFUSED; + } + *port = lpp->port; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: __pmLogFindPort -> pid = %d\n", lpp->port); +#endif + } + + if ((servInfo = __pmGetAddrInfo(connectionSpec)) == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: __pmGetAddrInfo: %s\n", + hoststrerror()); +#endif + return -EHOSTUNREACH; + } + + /* + * Loop over the addresses resolved for this host name until one of them + * connects. + */ + enumIx = NULL; + for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx); + myAddr != NULL; + myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) { + /* Create a socket */ + if (__pmSockAddrIsInet(myAddr)) + fd = __pmCreateSocket(); + else if (__pmSockAddrIsIPv6(myAddr)) + fd = __pmCreateIPv6Socket(); + else { + __pmNotifyErr(LOG_ERR, + "__pmConnectLogger : invalid address family %d\n", + __pmSockAddrGetFamily(myAddr)); + fd = -1; + } + if (fd < 0) { + __pmSockAddrFree(myAddr); + continue; /* Try the next address */ + } + + /* Attempt to connect */ + __pmSockAddrSetPort(myAddr, *port); + sts = connectLogger(fd, myAddr); + __pmSockAddrFree(myAddr); + + /* Successful connection? */ + if (sts >= 0) + break; + } + __pmHostEntFree(servInfo); + } + + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectLogger: connect: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + } + else { + /* Expect an error PDU back: ACK/NACK for connection */ + pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, __pmLoggerTimeout(), &pb); + if (sts == PDU_ERROR) { + __pmOverrideLastFd(PDU_OVERRIDE2); /* don't dink with the value */ + __pmDecodeError(pb, &sts); + php = (__pmPDUHdr *)pb; + if (*pid != PM_LOG_NO_PID && *pid != PM_LOG_PRIMARY_PID && php->from != *pid) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: ACK response from pid %d, expected pid %d\n", + php->from, *pid); +#endif + sts = -ECONNREFUSED; + } + *pid = php->from; + } + else if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + if (sts == PM_ERR_TIMEOUT) + fprintf(stderr, "__pmConnectLogger: timeout (after %d secs)\n", __pmLoggerTimeout()); + else { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmConnectLogger: Error: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + } +#endif + ; /* fall through */ + } + else { + /* wrong PDU type! */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: ACK botch PDU type=%d not PDU_ERROR?\n", sts); +#endif + sts = PM_ERR_IPC; + } + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (sts >= 0) { + if (sts == LOG_PDU_VERSION2) { + __pmCred handshake[1]; + + __pmSetVersionIPC(fd, sts); + handshake[0].c_type = CVERSION; + handshake[0].c_vala = LOG_PDU_VERSION; + handshake[0].c_valb = 0; + handshake[0].c_valc = 0; + sts = __pmSendCreds(fd, (int)getpid(), 1, handshake); + } + else + sts = PM_ERR_IPC; + if (sts >= 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmConnectLogger: PDU version=%d fd=%d\n", + __pmVersionIPC(fd), fd); +#endif + return fd; + } + } + + /* Error if we get here */ + __pmCloseSocket(fd); + } + + /* + * If the prefix was "local:" and we have a port or a pid, try the + * connection as "localhost". Otherwise, we can't connect. + */ + if (wasLocal && (*port != PM_LOG_NO_PORT || *pid != PM_LOG_NO_PID)) { + connectionSpec = "localhost"; + continue; + } + + /* No more ways to connect. */ + break; + } /* Loop over connect specs. */ + + return sts; +} diff --git a/src/libpcp/src/logcontrol.c b/src/libpcp/src/logcontrol.c new file mode 100644 index 0000000..2dc5c17 --- /dev/null +++ b/src/libpcp/src/logcontrol.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe note + * + * This routine is not thread-safe as there is no serialization on the + * use of the fd between the __pmSendLogControl() and the reading of + * the reply PDU. It is assumed that the caller is single-threaded, + * which is true for the only current user of this routine, pmlc(1). + */ + +#include "pmapi.h" +#include "impl.h" + +int +__pmControlLog(int fd, const pmResult *request, int control, int state, int delta, pmResult **status) +{ + int n; + __pmPDU *pb; + + if (request->numpmid < 1) + return PM_ERR_TOOSMALL; + + /* send a PCP 2.0 log control request */ + n = __pmSendLogControl(fd, request, control, state, delta); + if (n < 0) + n = __pmMapErrno(n); + else { + int pinpdu; + /* get the reply */ + pinpdu = n = __pmGetPDU(fd, ANY_SIZE, __pmLoggerTimeout(), &pb); + if (n == PDU_RESULT) { + n = __pmDecodeResult(pb, status); + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; /* unknown reply type */ + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + + return n; +} diff --git a/src/libpcp/src/logmeta.c b/src/libpcp/src/logmeta.c new file mode 100644 index 0000000..9cf893e --- /dev/null +++ b/src/libpcp/src/logmeta.c @@ -0,0 +1,847 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "fault.h" +#include "internal.h" +#include <stddef.h> + +/* bytes for a length field in a header/trailer, or a string length field */ +#define LENSIZE 4 + +#ifdef PCP_DEBUG +static void +StrTimeval(__pmTimeval *tp) +{ + if (tp == NULL) + fprintf(stderr, "<null timeval>"); + else + __pmPrintTimeval(stderr, tp); +} +#endif + +static int +addindom(__pmLogCtl *lcp, pmInDom indom, const __pmTimeval *tp, int numinst, + int *instlist, char **namelist, int *indom_buf, int allinbuf) +{ + __pmLogInDom *idp; + __pmHashNode *hp; + int sts; + +PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC); + if ((idp = (__pmLogInDom *)malloc(sizeof(__pmLogInDom))) == NULL) + return -oserror(); + idp->stamp = *tp; /* struct assignment */ + idp->numinst = numinst; + idp->instlist = instlist; + idp->namelist = namelist; + idp->buf = indom_buf; + idp->allinbuf = allinbuf; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + char strbuf[20]; + fprintf(stderr, "addindom( ..., %s, ", pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + StrTimeval((__pmTimeval *)tp); + fprintf(stderr, ", numinst=%d)\n", numinst); + } +#endif + + + if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL) { + idp->next = NULL; + sts = __pmHashAdd((unsigned int)indom, (void *)idp, &lcp->l_hashindom); + } + else { + idp->next = (__pmLogInDom *)hp->data; + hp->data = (void *)idp; + sts = 0; + } + return sts; +} + +/* + * Load _all_ of the hashed pmDesc and __pmLogInDom structures from the metadata + * log file -- used at the initialization (NewContext) of an archive. + * Also load all the metric names from the metadata log file and create l_pmns. + */ +int +__pmLogLoadMeta(__pmLogCtl *lcp) +{ + int rlen; + int check; + pmDesc *dp; + int sts = 0; + __pmLogHdr h; + FILE *f = lcp->l_mdfp; + int numpmid = 0; + int n; + + if ((sts = __pmNewPMNS(&(lcp->l_pmns))) < 0) + goto end; + + fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET); + for ( ; ; ) { + n = (int)fread(&h, 1, sizeof(__pmLogHdr), f); + + /* swab hdr */ + h.len = ntohl(h.len); + h.type = ntohl(h.type); + + if (n != sizeof(__pmLogHdr) || h.len <= 0) { + if (feof(f)) { + clearerr(f); + sts = 0; + goto end; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: header read -> %d: expected: %d or len=%d\n", + n, (int)sizeof(__pmLogHdr), h.len); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + goto end; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: record len=%d, type=%d @ offset=%d\n", + h.len, h.type, (int)(ftell(f) - sizeof(__pmLogHdr))); + } +#endif + rlen = h.len - (int)sizeof(__pmLogHdr) - (int)sizeof(int); + if (h.type == TYPE_DESC) { + numpmid++; +PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC); + if ((dp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL) { + sts = -oserror(); + goto end; + } + if ((n = (int)fread(dp, 1, sizeof(pmDesc), f)) != sizeof(pmDesc)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: pmDesc read -> %d: expected: %d\n", + n, (int)sizeof(pmDesc)); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + free(dp); + goto end; + } + else { + /* swab desc */ + dp->type = ntohl(dp->type); + dp->sem = ntohl(dp->sem); + dp->indom = __ntohpmInDom(dp->indom); + dp->units = __ntohpmUnits(dp->units); + dp->pmid = __ntohpmID(dp->pmid); + } + + if ((sts = __pmHashAdd((int)dp->pmid, (void *)dp, &lcp->l_hashpmid)) < 0) { + free(dp); + goto end; + } + + else { + char name[MAXPATHLEN]; + int numnames; + int i; + int len; + + /* read in the names & store in PMNS tree ... */ + if ((n = (int)fread(&numnames, 1, sizeof(numnames), f)) != + sizeof(numnames)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: numnames read -> %d: expected: %d\n", + n, (int)sizeof(numnames)); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + goto end; + } + else { + /* swab numnames */ + numnames = ntohl(numnames); + } + + for (i = 0; i < numnames; i++) { + if ((n = (int)fread(&len, 1, sizeof(len), f)) != + sizeof(len)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: len name[%d] read -> %d: expected: %d\n", + i, n, (int)sizeof(len)); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + goto end; + } + else { + /* swab len */ + len = ntohl(len); + } + + if ((n = (int)fread(name, 1, len, f)) != len) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: name[%d] read -> %d: expected: %d\n", + i, n, len); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + goto end; + } + name[len] = '\0'; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + char strbuf[20]; + fprintf(stderr, "__pmLogLoadMeta: PMID: %s name: %s\n", + pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)), name); + } +#endif + + if ((sts = __pmAddPMNSNode(lcp->l_pmns, dp->pmid, name)) < 0) { + /* + * If we see a duplicate PMID, its a recoverable error. + * We wont be able to see all of the data in the log, but + * its better to provide access to some rather than none, + * esp. when only one or two metric IDs may be corrupted + * in this way (which we may not be interested in anyway). + */ + if (sts != PM_ERR_PMID) + goto end; + sts = 0; + } + }/*for*/ + } + } + else if (h.type == TYPE_INDOM) { + int *tbuf; + pmInDom indom; + __pmTimeval *when; + int numinst; + int *instlist; + char **namelist; + char *namebase; + int *stridx; + int i; + int k; + int allinbuf = 0; + +PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC); + if ((tbuf = (int *)malloc(rlen)) == NULL) { + sts = -oserror(); + goto end; + } + if ((n = (int)fread(tbuf, 1, rlen, f)) != rlen) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: indom read -> %d: expected: %d\n", + n, rlen); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + free(tbuf); + goto end; + } + + k = 0; + when = (__pmTimeval *)&tbuf[k]; + when->tv_sec = ntohl(when->tv_sec); + when->tv_usec = ntohl(when->tv_usec); + k += sizeof(*when)/sizeof(int); + indom = __ntohpmInDom((unsigned int)tbuf[k++]); + numinst = ntohl(tbuf[k++]); + if (numinst > 0) { + instlist = &tbuf[k]; + k += numinst; + stridx = &tbuf[k]; +#if defined(HAVE_32BIT_PTR) + namelist = (char **)stridx; + allinbuf = 1; /* allocation is all in tbuf */ +#else + allinbuf = 0; /* allocation for namelist + tbuf */ + /* need to allocate to hold the pointers */ +PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC); + namelist = (char **)malloc(numinst*sizeof(char*)); + if (namelist == NULL) { + sts = -oserror(); + free(tbuf); + goto end; + } +#endif + k += numinst; + namebase = (char *)&tbuf[k]; + for (i = 0; i < numinst; i++) { + instlist[i] = ntohl(instlist[i]); + namelist[i] = &namebase[ntohl(stridx[i])]; + } + } + else { + /* no instances, or an error */ + instlist = NULL; + namelist = NULL; + } + if ((sts = addindom(lcp, indom, when, numinst, instlist, namelist, tbuf, allinbuf)) < 0) { + free(tbuf); + if (allinbuf == 0) + free(namelist); + goto end; + } + } + else + fseek(f, (long)rlen, SEEK_CUR); + n = (int)fread(&check, 1, sizeof(check), f); + check = ntohl(check); + if (n != sizeof(check) || h.len != check) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: trailer read -> %d or len=%d: expected %d @ offset=%d\n", + n, check, h.len, (int)(ftell(f) - sizeof(check))); + } +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + } + else + sts = PM_ERR_LOGREC; + goto end; + } + }/*for*/ +end: + + fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET); + + if (sts == 0) { + if (numpmid == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "__pmLogLoadMeta: no metrics found?\n"); + } +#endif + sts = PM_ERR_LOGREC; + } + else + __pmFixPMNSHashTab(lcp->l_pmns, numpmid, 1); + } + return sts; +} + +/* + * scan the hashed data structures to find a pmDesc, given a pmid + */ +int +__pmLogLookupDesc(__pmLogCtl *lcp, pmID pmid, pmDesc *dp) +{ + __pmHashNode *hp; + pmDesc *tp; + + if ((hp = __pmHashSearch((unsigned int)pmid, &lcp->l_hashpmid)) == NULL) + return PM_ERR_PMID_LOG; + + tp = (pmDesc *)hp->data; + *dp = *tp; /* struct assignment */ + return 0; +} + +/* + * Add a new pmDesc into the metadata log, and to the hashed data structures + * If numnames is positive, then write out any associated PMNS names. + */ +int +__pmLogPutDesc(__pmLogCtl *lcp, const pmDesc *dp, int numnames, char **names) +{ + FILE *f = lcp->l_mdfp; + pmDesc *tdp; + int olen; /* length to write out */ + int i; + int sts; + int len; + typedef struct { /* skeletal external record */ + __pmLogHdr hdr; + pmDesc desc; + int numnames; /* not present if numnames == 0 */ + char data[0]; /* will be expanded */ + } ext_t; + ext_t *out; + + len = sizeof(__pmLogHdr) + sizeof(pmDesc) + LENSIZE; + if (numnames > 0) { + len += sizeof(numnames); + for (i = 0; i < numnames; i++) + len += LENSIZE + (int)strlen(names[i]); + } +PM_FAULT_POINT("libpcp/" __FILE__ ":10", PM_FAULT_ALLOC); + if ((out = (ext_t *)malloc(len)) == NULL) + return -oserror(); + + out->hdr.len = htonl(len); + out->hdr.type = htonl(TYPE_DESC); + out->desc.type = htonl(dp->type); + out->desc.sem = htonl(dp->sem); + out->desc.indom = __htonpmInDom(dp->indom); + out->desc.units = __htonpmUnits(dp->units); + out->desc.pmid = __htonpmID(dp->pmid); + + if (numnames > 0) { + char *op = (char *)&out->data; + + out->numnames = htonl(numnames); + + /* copy the names and length prefix */ + for (i = 0; i < numnames; i++) { + int slen = (int)strlen(names[i]); + olen = htonl(slen); + memmove((void *)op, &olen, sizeof(olen)); + op += sizeof(olen); + memmove((void *)op, names[i], slen); + op += slen; + } + /* trailer length */ + memmove((void *)op, &out->hdr.len, sizeof(out->hdr.len)); + } + else { + /* no names, trailer length lands on numnames in ext_t */ + out->numnames = out->hdr.len; + } + + if ((sts = fwrite(out, 1, len, f)) != len) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutDesc(...,pmid=%s,name=%s): write failed: returned %d expecting %d: %s\n", + pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)), + numnames > 0 ? names[0] : "<none>", len, sts, + osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + free(out); + return -oserror(); + } + + free(out); + + /* + * need to make a copy of the pmDesc, and add this, since caller + * may re-use *dp + */ +PM_FAULT_POINT("libpcp/" __FILE__ ":5", PM_FAULT_ALLOC); + if ((tdp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL) + return -oserror(); + *tdp = *dp; /* struct assignment */ + return __pmHashAdd((int)dp->pmid, (void *)tdp, &lcp->l_hashpmid); +} + +static __pmLogInDom * +searchindom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp) +{ + __pmHashNode *hp; + __pmLogInDom *idp; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + char strbuf[20]; + fprintf(stderr, "searchindom( ..., %s, ", pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + StrTimeval(tp); + fprintf(stderr, ")\n"); + } +#endif + + if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL) + return NULL; + + idp = (__pmLogInDom *)hp->data; + if (tp != NULL) { + for ( ; idp != NULL; idp = idp->next) { + /* + * need first one at or earlier than the requested time + */ + if (__pmTimevalSub(&idp->stamp, tp) <= 0) + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "request @ "); + StrTimeval(tp); + fprintf(stderr, " is too early for indom @ "); + StrTimeval(&idp->stamp); + fputc('\n', stderr); + } +#endif + } + if (idp == NULL) + return NULL; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOGMETA) { + fprintf(stderr, "success for indom @ "); + StrTimeval(&idp->stamp); + fputc('\n', stderr); + } +#endif + return idp; +} + +/* + * for the given indom retrieve the instance domain that is correct + * as of the latest time (tp == NULL) or at a designated + * time + */ +int +__pmLogGetInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int **instlist, char ***namelist) +{ + __pmLogInDom *idp = searchindom(lcp, indom, tp); + + if (idp == NULL) + return PM_ERR_INDOM_LOG; + + *instlist = idp->instlist; + *namelist = idp->namelist; + + return idp->numinst; +} + +int +__pmLogLookupInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, + const char *name) +{ + __pmLogInDom *idp = searchindom(lcp, indom, tp); + int i; + + if (idp == NULL) + return PM_ERR_INDOM_LOG; + + if (idp->numinst < 0) + return idp->numinst; + + /* full match */ + for (i = 0; i < idp->numinst; i++) { + if (strcmp(name, idp->namelist[i]) == 0) + return idp->instlist[i]; + } + + /* half-baked match to first space */ + for (i = 0; i < idp->numinst; i++) { + char *p = idp->namelist[i]; + while (*p && *p != ' ') + p++; + if (*p == ' ') { + if (strncmp(name, idp->namelist[i], p - idp->namelist[i]) == 0) + return idp->instlist[i]; + } + } + + return PM_ERR_INST_LOG; +} + +int +__pmLogNameInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int inst, char **name) +{ + __pmLogInDom *idp = searchindom(lcp, indom, tp); + int i; + + if (idp == NULL) + return PM_ERR_INDOM_LOG; + + if (idp->numinst < 0) + return idp->numinst; + + for (i = 0; i < idp->numinst; i++) { + if (inst == idp->instlist[i]) { + *name = idp->namelist[i]; + return 0; + } + } + + return PM_ERR_INST_LOG; +} + +int +__pmLogPutInDom(__pmLogCtl *lcp, pmInDom indom, const __pmTimeval *tp, + int numinst, int *instlist, char **namelist) +{ + int sts = 0; + int i; + int *inst; + int *stridx; + char *str; + int len; + typedef struct { /* skeletal external record */ + __pmLogHdr hdr; + __pmTimeval stamp; + pmInDom indom; + int numinst; + char data[0]; /* inst[] then stridx[] then strings */ + /* will be expanded if numinst > 0 */ + } ext_t; + ext_t *out; + + len = (int)sizeof(ext_t) + + (numinst > 0 ? numinst : 0) * ((int)sizeof(instlist[0]) + (int)sizeof(stridx[0])) + + LENSIZE; + for (i = 0; i < numinst; i++) { + len += (int)strlen(namelist[i]) + 1; + } + +PM_FAULT_POINT("libpcp/" __FILE__ ":6", PM_FAULT_ALLOC); + if ((out = (ext_t *)malloc(len)) == NULL) + return -oserror(); + + /* swab all output fields */ + out->hdr.len = htonl(len); + out->hdr.type = htonl(TYPE_INDOM); + out->stamp.tv_sec = htonl(tp->tv_sec); + out->stamp.tv_usec = htonl(tp->tv_usec); + out->indom = __htonpmInDom(indom); + out->numinst = htonl(numinst); + + inst = (int *)&out->data; + stridx = (int *)&inst[numinst]; + str = (char *)&stridx[numinst]; + for (i = 0; i < numinst; i++) { + int slen = strlen(namelist[i])+1; + inst[i] = htonl(instlist[i]); + memmove((void *)str, (void *)namelist[i], slen); + stridx[i] = htonl((int)((ptrdiff_t)str - (ptrdiff_t)&stridx[numinst])); + str += slen; + } + /* trailer length */ + memmove((void *)str, &out->hdr.len, sizeof(out->hdr.len)); + + if ((sts = fwrite(out, 1, len, lcp->l_mdfp)) != len) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutInDom(...,indom=%s,numinst=%d): write failed: returned %d expecting %d: %s\n", + pmInDomStr_r(indom, strbuf, sizeof(strbuf)), numinst, len, sts, + osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + free(out); + return -oserror(); + } + free(out); + + sts = addindom(lcp, indom, tp, numinst, instlist, namelist, NULL, 0); + + return sts; +} + +int +pmLookupInDomArchive(pmInDom indom, const char *name) +{ + int n; + int j; + __pmHashNode *hp; + __pmLogInDom *idp; + __pmContext *ctxp; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((n = pmWhichContext()) >= 0) { + ctxp = __pmHandleToPtr(n); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type != PM_CONTEXT_ARCHIVE) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_NOTARCHIVE; + } + + if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_INDOM_LOG; + } + + for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) { + /* full match */ + for (j = 0; j < idp->numinst; j++) { + if (strcmp(name, idp->namelist[j]) == 0) { + PM_UNLOCK(ctxp->c_lock); + return idp->instlist[j]; + } + } + /* half-baked match to first space */ + for (j = 0; j < idp->numinst; j++) { + char *p = idp->namelist[j]; + while (*p && *p != ' ') + p++; + if (*p == ' ') { + if (strncmp(name, idp->namelist[j], p - idp->namelist[j]) == 0) { + PM_UNLOCK(ctxp->c_lock); + return idp->instlist[j]; + } + } + } + } + n = PM_ERR_INST_LOG; + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} + +int +pmNameInDomArchive(pmInDom indom, int inst, char **name) +{ + int n; + int j; + __pmHashNode *hp; + __pmLogInDom *idp; + __pmContext *ctxp; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((n = pmWhichContext()) >= 0) { + ctxp = __pmHandleToPtr(n); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type != PM_CONTEXT_ARCHIVE) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_NOTARCHIVE; + } + + if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_INDOM_LOG; + } + + for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) { + for (j = 0; j < idp->numinst; j++) { + if (idp->instlist[j] == inst) { + if ((*name = strdup(idp->namelist[j])) == NULL) + n = -oserror(); + else + n = 0; + PM_UNLOCK(ctxp->c_lock); + return n; + } + } + } + n = PM_ERR_INST_LOG; + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} + +int +pmGetInDomArchive(pmInDom indom, int **instlist, char ***namelist) +{ + int n; + int i; + int j; + char *p; + __pmContext *ctxp; + __pmHashNode *hp; + __pmLogInDom *idp; + int numinst = 0; + int strsize = 0; + int *ilist = NULL; + char **nlist = NULL; + char **olist; + + /* avoid ambiguity when no instances or errors */ + *instlist = NULL; + *namelist = NULL; + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((n = pmWhichContext()) >= 0) { + ctxp = __pmHandleToPtr(n); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type != PM_CONTEXT_ARCHIVE) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_NOTARCHIVE; + } + + if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) { + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_INDOM_LOG; + } + + for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) { + for (j = 0; j < idp->numinst; j++) { + for (i = 0; i < numinst; i++) { + if (idp->instlist[j] == ilist[i]) + break; + } + if (i == numinst) { + numinst++; +PM_FAULT_POINT("libpcp/" __FILE__ ":7", PM_FAULT_ALLOC); + if ((ilist = (int *)realloc(ilist, numinst*sizeof(ilist[0]))) == NULL) { + __pmNoMem("pmGetInDomArchive: ilist", numinst*sizeof(ilist[0]), PM_FATAL_ERR); + } +PM_FAULT_POINT("libpcp/" __FILE__ ":8", PM_FAULT_ALLOC); + if ((nlist = (char **)realloc(nlist, numinst*sizeof(nlist[0]))) == NULL) { + __pmNoMem("pmGetInDomArchive: nlist", numinst*sizeof(nlist[0]), PM_FATAL_ERR); + } + ilist[numinst-1] = idp->instlist[j]; + nlist[numinst-1] = idp->namelist[j]; + strsize += strlen(idp->namelist[j])+1; + } + } + } +PM_FAULT_POINT("libpcp/" __FILE__ ":9", PM_FAULT_ALLOC); + if ((olist = (char **)malloc(numinst*sizeof(olist[0]) + strsize)) == NULL) { + __pmNoMem("pmGetInDomArchive: olist", numinst*sizeof(olist[0]) + strsize, PM_FATAL_ERR); + } + p = (char *)olist; + p += numinst * sizeof(olist[0]); + for (i = 0; i < numinst; i++) { + olist[i] = p; + strcpy(p, nlist[i]); + p += strlen(nlist[i]) + 1; + } + free(nlist); + *instlist = ilist; + *namelist = olist; + n = numinst; + PM_UNLOCK(ctxp->c_lock); + } + + return n; +} diff --git a/src/libpcp/src/logportmap.c b/src/libpcp/src/logportmap.c new file mode 100644 index 0000000..9319bc4 --- /dev/null +++ b/src/libpcp/src/logportmap.c @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include <ctype.h> + +static __pmLogPort *logport; + /* array of all known pmlogger ports */ +static int nlogports; /* no. of elements used in logports array */ +static int szlogport; /* size of logport array */ + +/* Make sure the logports array is large enough to hold newsize entries. Free + * any currently allocated names and zero the first newsize entries. + */ +static int +resize_logports(int newsize) +{ + int i; + int need; + + if (nlogports) { + for (i = 0; i < nlogports; i++) { + if (logport[i].pmcd_host != NULL) + free(logport[i].pmcd_host); + if (logport[i].archive != NULL) + free(logport[i].archive); + if (logport[i].name != NULL) + free(logport[i].name); + } + memset(logport, 0, nlogports * sizeof(__pmLogPort)); + } + nlogports = 0; + if (szlogport >= newsize) + return 0; + free(logport); + need = newsize * (int)sizeof(__pmLogPort); + if ((logport = (__pmLogPort *)malloc(need)) == NULL) { + szlogport = 0; + return -1; + } + memset(logport, 0, need); + szlogport = newsize; + return 0; +} + +/* Used by scandir to determine which files are pmlogger port files. The valid + * files are numbers (pids) or PM_LOG_PRIMARY_LINK for the primary logger. + */ +static int +is_portfile(const_dirent *dep) +{ + char *endp; + pid_t pid; + + pid = (pid_t)strtol(dep->d_name, &endp, 10); + if (pid > (pid_t)1) + return __pmProcessExists(pid); + return strcmp(dep->d_name, "primary") == 0; +} + +/* The following function is used for selecting particular port files rather + * than all valid files. snprintf the pid of the pmlogger process or the + * special constant PM_LOG_PRIMARY_LINK into the match array first. + */ +#define PROCFS_ENTRY_SIZE 40 /* encompass any size of entry for pid */ +static char match[PROCFS_ENTRY_SIZE]; + +static int +is_match(const_dirent *dep) +{ + return strcmp(match, dep->d_name) == 0; +} + +/* Return (in result) a list of active pmlogger ports on the local machine. + * The return value of the function is the number of elements in the array. + * The caller must NOT free any part of the result stucture, it's storage is + * managed here. Subsequent calls will overwrite the data so the caller should + * copy it if persistence is required. + */ +int +__pmLogFindLocalPorts(int pid, __pmLogPort **result) +{ + char dir[MAXPATHLEN]; + int lendir; + int i, j, n; + int nf; /* number of port files found */ + struct dirent **files = NULL; /* array of port file dirents */ + char *p; + int len; + char namebuf[MAXPATHLEN]; + int (*scanfn)(const_dirent *dep); + FILE *pfile; + char buf[MAXPATHLEN]; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_LOGPORT)) + return PM_ERR_THREAD; + + if (result == NULL) + return -EINVAL; + + lendir = snprintf(dir, sizeof(dir), "%s%cpmlogger", + pmGetConfig("PCP_TMP_DIR"), __pmPathSeparator()); + + /* Set up the appropriate function to select files from the control port + * directory. Anticipate that this will usually be an exact match for + * the primary logger control port. + */ + scanfn = is_match; + switch (pid) { + case PM_LOG_PRIMARY_PID: /* primary logger control (single) */ + strcpy(match, "primary"); + break; + + case PM_LOG_ALL_PIDS: /* find all ports */ + scanfn = is_portfile; + break; + + default: /* a specific pid (single) */ + if (!__pmProcessExists((pid_t)pid)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "__pmLogFindLocalPorts() -> 0, " + "pid(%d) doesn't exist\n", pid); + } +#endif + *result = NULL; + return 0; + } + snprintf(match, sizeof(match), "%d", pid); + break; + } + + nf = scandir(dir, &files, scanfn, alphasort); +#ifdef PCP_DEBUG + if (nf < 1 && (pmDebug & DBG_TRACE_LOG)) { + fprintf(stderr, "__pmLogFindLocalPorts: scandir() -> %d %s\n", + nf, pmErrStr(oserror())); + } +#endif + if (nf == -1 && oserror() == ENOENT) + nf = 0; + else if (nf == -1) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogFindLocalPorts: scandir: %s\n", osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + return -oserror(); + } + if (resize_logports(nf) < 0) { + for (i=0; i < nf; i++) + free(files[i]); + free(files); + return -oserror(); + } + if (nf == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "__pmLogFindLocalPorts() -> 0, " + "num files = 0\n"); + } +#endif + *result = NULL; + free(files); + return 0; + } + + /* make a buffer for the longest complete pathname found */ + len = (int)strlen(files[0]->d_name); + for (i = 1; i < nf; i++) + if ((j = (int)strlen(files[i]->d_name)) > len) + len = j; + /* +1 for trailing path separator, +1 for null termination */ + len += lendir + 2; + + /* namebuf is the complete pathname, p points to the trailing filename + * within namebuf. + */ + strcpy(namebuf, dir); + p = namebuf + lendir; + *p++ = __pmPathSeparator(); + + /* open the file, try to read the port number and add the port to the + * logport array if successful. + */ + for (i = 0; i < nf; i++) { + char *fname = files[i]->d_name; + int err = 0; + __pmLogPort *lpp = &logport[nlogports]; + + strcpy(p, fname); + if ((pfile = fopen(namebuf, "r")) == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: %s\n", + namebuf, osstrerror_r(errmsg, sizeof(errmsg))); + free(files[i]); + pmflush(); + continue; + } + if (!err && fgets(buf, MAXPATHLEN, pfile) == NULL) { + if (feof(pfile)) { + clearerr(pfile); + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s empty!\n", + namebuf); + } + else { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: %s\n", + namebuf, osstrerror_r(errmsg, sizeof(errmsg))); + } + err = 1; + } + else { + char *endp; + + lpp->port = (int)strtol(buf, &endp, 10); + if (*endp != '\n') { + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no port number\n", + namebuf); + err = 1; + } + else { + lpp->pid = (int)strtol(fname, &endp, 10); + if (*endp != '\0') { + if (strcmp(fname, "primary") == 0) + lpp->pid = PM_LOG_PRIMARY_PORT; + else { + pmprintf("__pmLogFindLocalPorts: unrecognised pmlogger port file %s\n", + namebuf); + err = 1; + } + } + } + } + if (err) { + pmflush(); + fclose(pfile); + } + else { + if (fgets(buf, MAXPATHLEN, pfile) == NULL) { + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no PMCD host name\n", + namebuf); + pmflush(); + } + else { + char *q = strchr(buf, '\n'); + if (q != NULL) + *q = '\0'; + lpp->pmcd_host = strdup(buf); + if (fgets(buf, MAXPATHLEN, pfile) == NULL) { + pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no archive base pathname\n", + namebuf); + pmflush(); + } + else { + char *q = strchr(buf, '\n'); + if (q != NULL) + *q = '\0'; + lpp->archive = strdup(buf); + } + } + fclose(pfile); + if ((lpp->name = strdup(fname)) != NULL) + nlogports++; + else { + if (lpp->pmcd_host != NULL) { + free(lpp->pmcd_host); + lpp->pmcd_host = NULL; + } + if (lpp->archive != NULL) { + free(lpp->archive); + lpp->archive = NULL; + } + break; + } + } + free(files[i]); + } + + if (i == nf) { /* all went well */ + n = nlogports; + *result = logport; + } + else { /* strdup error on fname, clean up */ + *result = NULL; + for (j = i; j < nf; j++) + free(files[j]); + n = -oserror(); + } + free(files); + return n; +} + +/* + * Return 1 if hostname corresponds to the current host, 0 if not and < 0 for + * an error. + */ +int +__pmIsLocalhost(const char *hostname) +{ + int sts = 0; + + if (strcasecmp(hostname, "localhost") == 0 || + strncmp(hostname, "local:", 6) == 0 || + strncmp(hostname, "unix:", 5) == 0) + return 1; + else { + char lhost[MAXHOSTNAMELEN+1]; + __pmHostEnt *servInfo1; + + if (gethostname(lhost, MAXHOSTNAMELEN) < 0) + return -oserror(); + + if ((servInfo1 = __pmGetAddrInfo(lhost)) != NULL) { + __pmHostEnt *servInfo2; + __pmSockAddr *addr1, *addr2; + void *enumIx1, *enumIx2; + + if ((servInfo2 = __pmGetAddrInfo(hostname)) == NULL) { + __pmHostEntFree(servInfo1); + return -EHOSTUNREACH; + } + enumIx1 = NULL; + for (addr1 = __pmHostEntGetSockAddr(servInfo1, &enumIx1); + addr1 != NULL; + addr1 = __pmHostEntGetSockAddr(servInfo1, &enumIx1)) { + enumIx2 = NULL; + for (addr2 = __pmHostEntGetSockAddr(servInfo2, &enumIx2); + addr2 != NULL; + addr2 = __pmHostEntGetSockAddr(servInfo2, &enumIx2)) { + if (__pmSockAddrCompare(addr1, addr2) == 0) { + __pmHostEntFree(servInfo1); + __pmHostEntFree(servInfo2); + return 1; + } + } + } + __pmHostEntFree(servInfo1); + __pmHostEntFree(servInfo2); + } + } + + return sts; +} + +/* Return (in result) a list of active pmlogger ports on the specified machine. + * The return value of the function is the number of elements in the array. + * The caller must NOT free any part of the result stucture, it's storage is + * managed here. Subsequent calls will overwrite the data so the caller should + * copy it if persistence is required. + */ +int +__pmLogFindPort(const char *host, int pid, __pmLogPort **lpp) +{ + int ctx, oldctx; + char *ctxhost; + int sts, numval; + int i, j; + int findone = pid != PM_LOG_ALL_PIDS; + int localcon = 0; /* > 0 for local connection */ + pmDesc desc; + pmResult *res; + char *namelist[] = {"pmcd.pmlogger.port"}; + pmID pmid; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_LOGPORT)) + return PM_ERR_THREAD; + + *lpp = NULL; /* pass null back in event of error */ + localcon = __pmIsLocalhost(host); + if (localcon > 0) + /* do the work here instead of making PMCD do it */ + return __pmLogFindLocalPorts(pid, lpp); + else if (localcon < 0) + return localcon; + + /* note: there may not be a current context */ + ctx = 0; + oldctx = pmWhichContext(); + + /* + * Enclose ctxhost in [] in case it is an ipv6 address. This prevents + * the first colon from being taken as a port separator by pmNewContext + * and does no harm otherwise. + */ + ctxhost = malloc(strlen(host) + 2 + 1); + if (ctxhost == NULL) { + sts = -ENOMEM; + goto ctxErr; + } + sprintf(ctxhost, "[%s]", host); + ctx = pmNewContext(PM_CONTEXT_HOST, ctxhost); + free(ctxhost); + if (ctx < 0) + return ctx; + if ((sts = pmLookupName(1, namelist, &pmid)) < 0) + goto ctxErr; + + if ((sts = pmLookupDesc(pmid, &desc)) < 0) + goto ctxErr; + if ((sts = pmFetch(1, &pmid, &res) < 0)) + goto ctxErr; + if ((sts = numval = res->vset[0]->numval) < 0) + goto resErr; + j = 0; + if (numval) { + if (resize_logports(findone ? 1 : numval) < 0) { + sts = -oserror(); + goto resErr; + } + /* scan the pmResult, copying matching pid(s) to logport */ + for (i = j = 0; i < numval; i++) { + __pmLogPort *p = &logport[j]; + pmValue *vp = &res->vset[0]->vlist[i]; + + if (vp->inst == 1) /* old vcr instance (pseudo-init) */ + continue; + if (findone && vp->inst != pid) + continue; + p->pid = vp->inst; + p->port = vp->value.lval; + sts = pmNameInDom(desc.indom, p->pid, &p->name); + if (sts < 0) { + p->name = NULL; + goto resErr; + } + j++; + if (findone) /* found one, stop searching */ + break; + } + *lpp = logport; + } + sts = j; /* the number actually added */ + +resErr: + pmFreeResult(res); +ctxErr: + if (oldctx >= 0) + pmUseContext(oldctx); + if (ctx >= 0) + pmDestroyContext(ctx); + return sts; +} diff --git a/src/libpcp/src/logutil.c b/src/libpcp/src/logutil.c new file mode 100644 index 0000000..9b577cb --- /dev/null +++ b/src/libpcp/src/logutil.c @@ -0,0 +1,2461 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes: + * + * __pmLogReads is a diagnostic counter that is maintained with + * non-atomic updates ... we've decided that it is acceptable for the + * value to be subject to possible (but unlikely) missed updates + */ + +#include <inttypes.h> +#include <assert.h> +#include <sys/stat.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif + +INTERN int __pmLogReads; + +/* + * Suffixes and associated compresssion application for compressed filenames. + * These can appear _after_ the volume number in the name of a file for an + * archive metric log file, e.g. /var/log/pmlogger/myhost/20101219.0.bz2 + */ +#define USE_NONE 0 +#define USE_BZIP2 1 +#define USE_GZIP 2 +#define USE_XZ 3 +static const struct { + const char *suff; + const int appl; +} compress_ctl[] = { + { ".bz2", USE_BZIP2 }, + { ".bz", USE_BZIP2 }, + { ".gz", USE_GZIP }, + { ".Z", USE_GZIP }, + { ".z", USE_GZIP }, + { ".lzma", USE_XZ }, + { ".xz", USE_XZ }, +}; +static const int ncompress = sizeof(compress_ctl) / sizeof(compress_ctl[0]); + +/* + * first two fields are made to look like a pmValueSet when no values are + * present ... used to populate the pmValueSet in a pmResult when values + * for particular metrics are not available from this log record. + */ +typedef struct { + pmID pc_pmid; + int pc_numval; /* MUST be 0 */ + /* value control for interpolation */ +} pmid_ctl; + +/* + * Hash control for requested metrics, used to construct 'No values' + * result when the corresponding metric is requested but there is + * no values available in the pmResult + * + * Note, this hash table is global across all contexts. + */ +static __pmHashCtl pc_hc; + +#ifdef PCP_DEBUG +static void +dumpbuf(int nch, __pmPDU *pb) +{ + int i, j; + + nch /= sizeof(__pmPDU); + fprintf(stderr, "%03d: ", 0); + for (j = 0, i = 0; j < nch; j++) { + if (i == 8) { + fprintf(stderr, "\n%03d: ", j); + i = 0; + } + fprintf(stderr, "%8x ", pb[j]); + i++; + } + fputc('\n', stderr); +} +#endif + +int +__pmLogChkLabel(__pmLogCtl *lcp, FILE *f, __pmLogLabel *lp, int vol) +{ + int len; + int version = UNKNOWN_VERSION; + int xpectlen = sizeof(__pmLogLabel) + 2 * sizeof(len); + int n; + + if (vol >= 0 && vol < lcp->l_numseen && lcp->l_seen[vol]) { + /* FastPath, cached result of previous check for this volume */ + fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET); + return 0; + } + + if (vol >= 0 && vol >= lcp->l_numseen) { + lcp->l_seen = (int *)realloc(lcp->l_seen, (vol+1)*(int)sizeof(lcp->l_seen[0])); + if (lcp->l_seen == NULL) + lcp->l_numseen = 0; + else { + int i; + for (i = lcp->l_numseen; i < vol; i++) + lcp->l_seen[i] = 0; + lcp->l_numseen = vol+1; + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "__pmLogChkLabel: fd=%d vol=%d", fileno(f), vol); +#endif + + fseek(f, (long)0, SEEK_SET); + n = (int)fread(&len, 1, sizeof(len), f); + len = ntohl(len); + if (n != sizeof(len) || len != xpectlen) { + if (feof(f)) { + clearerr(f); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " file is empty\n"); +#endif + return PM_ERR_NODATA; + } + else { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " header read -> %d (expect %d) or bad header len=%d (expected %d)\n", + n, (int)sizeof(len), len, xpectlen); +#endif + if (ferror(f)) { + clearerr(f); + return -oserror(); + } + else + return PM_ERR_LABEL; + } + } + + if ((n = (int)fread(lp, 1, sizeof(__pmLogLabel), f)) != sizeof(__pmLogLabel)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " bad label len=%d: expected %d\n", + n, (int)sizeof(__pmLogLabel)); +#endif + if (ferror(f)) { + clearerr(f); + return -oserror(); + } + else + return PM_ERR_LABEL; + } + else { + /* swab internal log label */ + lp->ill_magic = ntohl(lp->ill_magic); + lp->ill_pid = ntohl(lp->ill_pid); + lp->ill_start.tv_sec = ntohl(lp->ill_start.tv_sec); + lp->ill_start.tv_usec = ntohl(lp->ill_start.tv_usec); + lp->ill_vol = ntohl(lp->ill_vol); + } + + n = (int)fread(&len, 1, sizeof(len), f); + len = ntohl(len); + if (n != sizeof(len) || len != xpectlen) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " trailer read -> %d (expect %d) or bad trailer len=%d (expected %d)\n", + n, (int)sizeof(len), len, xpectlen); +#endif + if (ferror(f)) { + clearerr(f); + return -oserror(); + } + else + return PM_ERR_LABEL; + } + + version = lp->ill_magic & 0xff; + if ((lp->ill_magic & 0xffffff00) != PM_LOG_MAGIC || + (version != PM_LOG_VERS02) || lp->ill_vol != vol) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + if ((lp->ill_magic & 0xffffff00) != PM_LOG_MAGIC) + fprintf(stderr, " label magic 0x%x not 0x%x as expected", (lp->ill_magic & 0xffffff00), PM_LOG_MAGIC); + if (version != PM_LOG_VERS02) + fprintf(stderr, " label version %d not supported", version); + if (lp->ill_vol != vol) + fprintf(stderr, " label volume %d not %d as expected", lp->ill_vol, vol); + fputc('\n', stderr); + } +#endif + return PM_ERR_LABEL; + } + else { + if (__pmSetVersionIPC(fileno(f), version) < 0) + return -oserror(); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " [magic=%8x version=%d vol=%d pid=%d host=%s]\n", + lp->ill_magic, version, lp->ill_vol, lp->ill_pid, lp->ill_hostname); +#endif + } + + if (vol >= 0 && vol < lcp->l_numseen) + lcp->l_seen[vol] = 1; + + return version; +} + +static int +popen_uncompress(const char *cmd, const char *fname, const char *suffix, int fd) +{ + char pipecmd[2*MAXPATHLEN+2]; + char buffer[4096]; + FILE *finp; + ssize_t bytes; + int sts, infd; + + snprintf(pipecmd, sizeof(pipecmd), "%s %s%s", cmd, fname, suffix); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "__pmLogOpen: uncompress using: %s\n", pipecmd); +#endif + + if ((finp = popen(pipecmd, "r")) == NULL) + return -1; + infd = fileno(finp); + + while ((bytes = read(infd, buffer, sizeof(buffer))) > 0) { + if (write(fd, buffer, bytes) != bytes) { + bytes = -1; + break; + } + } + + if ((sts = pclose(finp)) != 0) + return sts; + return (bytes == 0) ? 0 : -1; +} + +static FILE * +fopen_compress(const char *fname) +{ + int sts; + int fd; + int i; + char *cmd; + char *msg; + FILE *fp; + char tmpname[MAXPATHLEN]; + mode_t cur_umask; + + for (i = 0; i < ncompress; i++) { + snprintf(tmpname, sizeof(tmpname), "%s%s", fname, compress_ctl[i].suff); + if (access(tmpname, R_OK) == 0) { + break; + } + } + if (i == ncompress) { + /* end up here if it does not look like a compressed file */ + return NULL; + } + if (compress_ctl[i].appl == USE_BZIP2) + cmd = "bzip2 -dc"; + else if (compress_ctl[i].appl == USE_GZIP) + cmd = "gzip -dc"; + else if (compress_ctl[i].appl == USE_XZ) + cmd = "xz -dc"; + else { + /* botch in compress_ctl[] ... should not happen */ + return NULL; + } + + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); +#if HAVE_MKSTEMP + snprintf(tmpname, sizeof(tmpname), + "%s/XXXXXX", pmGetConfig("PCP_TMPFILE_DIR")); + msg = tmpname; + fd = mkstemp(tmpname); +#else + if ((msg = tmpnam(NULL)) == NULL) { + umask(cur_umask); + return NULL; + } + fd = open(msg, O_RDWR|O_CREAT|O_EXCL, 0600); +#endif + /* + * unlink temporary file to avoid namespace pollution and allow O/S + * space cleanup on last close + */ + unlink(msg); + umask(cur_umask); + + if (fd < 0) { + sts = oserror(); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "__pmLogOpen: temp file create failed: %s\n", osstrerror()); +#endif + setoserror(sts); + return NULL; + } + + sts = popen_uncompress(cmd, fname, compress_ctl[i].suff, fd); + if (sts == -1) { + sts = oserror(); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmLogOpen: uncompress command failed: %s\n", osstrerror_r(errmsg, sizeof(errmsg))); + } +#endif + close(fd); + setoserror(sts); + return NULL; + } + if (sts != 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { +#if defined(HAVE_SYS_WAIT_H) + if (WIFEXITED(sts)) + fprintf(stderr, "__pmLogOpen: uncompress failed, exit status: %d\n", WEXITSTATUS(sts)); + else if (WIFSIGNALED(sts)) + fprintf(stderr, "__pmLogOpen: uncompress failed, signal: %d\n", WTERMSIG(sts)); + else +#endif + fprintf(stderr, "__pmLogOpen: uncompress failed, popen() returns: %d\n", sts); + } +#endif + close(fd); + /* not a great error code, but the best we can do */ + setoserror(-PM_ERR_LOGREC); + return NULL; + } + if ((fp = fdopen(fd, "r")) == NULL) { + sts = oserror(); + close(fd); + setoserror(sts); + return NULL; + } + /* success */ + return fp; +} + +static FILE * +_logpeek(__pmLogCtl *lcp, int vol) +{ + int sts; + FILE *f; + __pmLogLabel label; + char fname[MAXPATHLEN]; + + snprintf(fname, sizeof(fname), "%s.%d", lcp->l_name, vol); + if ((f = fopen(fname, "r")) == NULL) { + if ((f = fopen_compress(fname)) == NULL) + return f; + } + + if ((sts = __pmLogChkLabel(lcp, f, &label, vol)) < 0) { + fclose(f); + setoserror(sts); + return NULL; + } + + return f; +} + +int +__pmLogChangeVol(__pmLogCtl *lcp, int vol) +{ + char name[MAXPATHLEN]; + int sts; + + if (lcp->l_curvol == vol) + return 0; + + if (lcp->l_mfp != NULL) { + __pmResetIPC(fileno(lcp->l_mfp)); + fclose(lcp->l_mfp); + } + snprintf(name, sizeof(name), "%s.%d", lcp->l_name, vol); + if ((lcp->l_mfp = fopen(name, "r")) == NULL) { + /* try for a compressed file */ + if ((lcp->l_mfp = fopen_compress(name)) == NULL) + return -oserror(); + } + + if ((sts = __pmLogChkLabel(lcp, lcp->l_mfp, &lcp->l_label, vol)) < 0) + return sts; + + lcp->l_curvol = vol; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "__pmLogChangeVol: change to volume %d\n", vol); +#endif + return sts; +} + +int +__pmLogLoadIndex(__pmLogCtl *lcp) +{ + int sts = 0; + FILE *f = lcp->l_tifp; + int n; + __pmLogTI *tip; + + lcp->l_numti = 0; + lcp->l_ti = NULL; + + if (lcp->l_tifp != NULL) { + fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET); + for ( ; ; ) { + lcp->l_ti = (__pmLogTI *)realloc(lcp->l_ti, (1 + lcp->l_numti) * sizeof(__pmLogTI)); + if (lcp->l_ti == NULL) { + sts = -oserror(); + break; + } + tip = &lcp->l_ti[lcp->l_numti]; + n = (int)fread(tip, 1, sizeof(__pmLogTI), f); + + if (n != sizeof(__pmLogTI)) { + if (feof(f)) { + clearerr(f); + sts = 0; + break; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "__pmLogLoadIndex: bad TI entry len=%d: expected %d\n", + n, (int)sizeof(__pmLogTI)); +#endif + if (ferror(f)) { + clearerr(f); + sts = -oserror(); + break; + } + else { + sts = PM_ERR_LOGREC; + break; + } + } + else { + /* swab the temporal index record */ + tip->ti_stamp.tv_sec = ntohl(tip->ti_stamp.tv_sec); + tip->ti_stamp.tv_usec = ntohl(tip->ti_stamp.tv_usec); + tip->ti_vol = ntohl(tip->ti_vol); + tip->ti_meta = ntohl(tip->ti_meta); + tip->ti_log = ntohl(tip->ti_log); + } + + lcp->l_numti++; + }/*for*/ + }/*not null*/ + + return sts; +} + +const char * +__pmLogName_r(const char *base, int vol, char *buf, int buflen) +{ + switch (vol) { + case PM_LOG_VOL_TI: + snprintf(buf, buflen, "%s.index", base); + break; + + case PM_LOG_VOL_META: + snprintf(buf, buflen, "%s.meta", base); + break; + + default: + snprintf(buf, buflen, "%s.%d", base, vol); + break; + } + + return buf; +} + +const char * +__pmLogName(const char *base, int vol) +{ + static char tbuf[MAXPATHLEN]; + + return __pmLogName_r(base, vol, tbuf, sizeof(tbuf)); +} + +FILE * +__pmLogNewFile(const char *base, int vol) +{ + char fname[MAXPATHLEN]; + FILE *f; + int save_error; + + __pmLogName_r(base, vol, fname, sizeof(fname)); + + if (access(fname, R_OK) != -1) { + /* exists and readable ... */ + pmprintf("__pmLogNewFile: \"%s\" already exists, not over-written\n", fname); + pmflush(); + setoserror(EEXIST); + return NULL; + } + + if ((f = fopen(fname, "w")) == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + save_error = oserror(); + pmprintf("__pmLogNewFile: failed to create \"%s\": %s\n", fname, osstrerror_r(errmsg, sizeof(errmsg))); + + pmflush(); + setoserror(save_error); + return NULL; + } + /* + * Want unbuffered I/O for all files of the archive, so a single + * fwrite() maps to one logical record for each of the metadata + * records, the index records and the data (pmResult) records. + */ + setvbuf(f, NULL, _IONBF, 0); + + if ((save_error = __pmSetVersionIPC(fileno(f), PDU_VERSION)) < 0) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogNewFile: failed to setup \"%s\": %s\n", fname, osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + fclose(f); + setoserror(save_error); + return NULL; + } + + return f; +} + +int +__pmLogWriteLabel(FILE *f, const __pmLogLabel *lp) +{ + int sts = 0; + struct { /* skeletal external record */ + int header; + __pmLogLabel label; + int trailer; + } out; + + out.header = out.trailer = htonl((int)sizeof(out)); + + /* swab */ + out.label.ill_magic = htonl(lp->ill_magic); + out.label.ill_pid = htonl(lp->ill_pid); + out.label.ill_start.tv_sec = htonl(lp->ill_start.tv_sec); + out.label.ill_start.tv_usec = htonl(lp->ill_start.tv_usec); + out.label.ill_vol = htonl(lp->ill_vol); + memmove((void *)out.label.ill_hostname, (void *)lp->ill_hostname, sizeof(lp->ill_hostname)); + memmove((void *)out.label.ill_tz, (void *)lp->ill_tz, sizeof(lp->ill_tz)); + + if ((sts = fwrite(&out, 1, sizeof(out), f)) != sizeof(out)) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogWriteLabel: write failed: returns %d expecting %d: %s\n", + sts, (int)sizeof(out), osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + sts = -oserror(); + } + else + sts = 0; + + return sts; +} + +int +__pmLogCreate(const char *host, const char *base, int log_version, + __pmLogCtl *lcp) +{ + int save_error = 0; + char fname[MAXPATHLEN]; + + lcp->l_minvol = lcp->l_maxvol = lcp->l_curvol = 0; + lcp->l_hashpmid.nodes = lcp->l_hashpmid.hsize = 0; + lcp->l_hashindom.nodes = lcp->l_hashindom.hsize = 0; + lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL; + + if ((lcp->l_tifp = __pmLogNewFile(base, PM_LOG_VOL_TI)) != NULL) { + if ((lcp->l_mdfp = __pmLogNewFile(base, PM_LOG_VOL_META)) != NULL) { + if ((lcp->l_mfp = __pmLogNewFile(base, 0)) != NULL) { + char tzbuf[PM_TZ_MAXLEN]; + char *tz; + int sts; + + tz = __pmTimezone_r(tzbuf, sizeof(tzbuf)); + + lcp->l_label.ill_magic = PM_LOG_MAGIC | log_version; + /* + * Warning ill_hostname may be truncated, but we + * guarantee it will be null-byte terminated + */ + strncpy(lcp->l_label.ill_hostname, host, PM_LOG_MAXHOSTLEN-1); + lcp->l_label.ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0'; + lcp->l_label.ill_pid = (int)getpid(); + /* + * hack - how do you get the TZ for a remote host? + */ + strcpy(lcp->l_label.ill_tz, tz ? tz : ""); + lcp->l_state = PM_LOG_STATE_NEW; + + /* + * __pmLogNewFile sets the IPC version to PDU_VERSION + * we want log_version instead + */ + sts = __pmSetVersionIPC(fileno(lcp->l_tifp), log_version); + if (sts < 0) + return sts; + sts = __pmSetVersionIPC(fileno(lcp->l_mdfp), log_version); + if (sts < 0) + return sts; + sts = __pmSetVersionIPC(fileno(lcp->l_mfp), log_version); + return sts; + } + else { + save_error = oserror(); + unlink(__pmLogName_r(base, PM_LOG_VOL_TI, fname, sizeof(fname))); + unlink(__pmLogName_r(base, PM_LOG_VOL_META, fname, sizeof(fname))); + setoserror(save_error); + } + } + else { + save_error = oserror(); + unlink(__pmLogName_r(base, PM_LOG_VOL_TI, fname, sizeof(fname))); + setoserror(save_error); + } + } + + lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL; + return oserror() ? -oserror() : -EPERM; +} + +/* + * Close the log files. + * Free up the space used by __pmLogCtl. + */ + +void +__pmLogClose(__pmLogCtl *lcp) +{ + if (lcp->l_tifp != NULL) { + __pmResetIPC(fileno(lcp->l_tifp)); + fclose(lcp->l_tifp); + lcp->l_tifp = NULL; + } + if (lcp->l_mdfp != NULL) { + __pmResetIPC(fileno(lcp->l_mdfp)); + fclose(lcp->l_mdfp); + lcp->l_mdfp = NULL; + } + if (lcp->l_mfp != NULL) { + __pmResetIPC(fileno(lcp->l_mfp)); + fclose(lcp->l_mfp); + lcp->l_mfp = NULL; + } + if (lcp->l_name != NULL) { + free(lcp->l_name); + lcp->l_name = NULL; + } + if (lcp->l_seen != NULL) { + free(lcp->l_seen); + lcp->l_seen = NULL; + lcp->l_numseen = 0; + } + if (lcp->l_pmns != NULL) { + __pmFreePMNS(lcp->l_pmns); + lcp->l_pmns = NULL; + } + + if (lcp->l_ti != NULL) + free(lcp->l_ti); + + if (lcp->l_hashpmid.hsize != 0) { + __pmHashCtl *hcp = &lcp->l_hashpmid; + __pmHashNode *hp; + __pmHashNode *prior_hp; + int i; + + for (i = 0; i < hcp->hsize; i++) { + for (hp = hcp->hash[i], prior_hp = NULL; hp != NULL; hp = hp->next) { + if (hp->data != NULL) + free(hp->data); + if (prior_hp != NULL) + free(prior_hp); + prior_hp = hp; + } + if (prior_hp != NULL) + free(prior_hp); + } + free(hcp->hash); + } + + if (lcp->l_hashindom.hsize != 0) { + __pmHashCtl *hcp = &lcp->l_hashindom; + __pmHashNode *hp; + __pmHashNode *prior_hp; + __pmLogInDom *idp; + __pmLogInDom *prior_idp; + int i; + + for (i = 0; i < hcp->hsize; i++) { + for (hp = hcp->hash[i], prior_hp = NULL; hp != NULL; hp = hp->next) { + for (idp = (__pmLogInDom *)hp->data, prior_idp = NULL; + idp != NULL; idp = idp->next) { + if (idp->buf != NULL) + free(idp->buf); + if (idp->allinbuf == 0 && idp->namelist != NULL) + free(idp->namelist); + if (prior_idp != NULL) + free(prior_idp); + prior_idp = idp; + } + if (prior_idp != NULL) + free(prior_idp); + if (prior_hp != NULL) + free(prior_hp); + prior_hp = hp; + } + if (prior_hp != NULL) + free(prior_hp); + } + free(hcp->hash); + } + +} + +int +__pmLogLoadLabel(__pmLogCtl *lcp, const char *name) +{ + int sts; + int blen; + int exists = 0; + int i; + int sep = __pmPathSeparator(); + char *q; + char *base; + char *tbuf; + char *tp; + char *dir; + DIR *dirp = NULL; + char filename[MAXPATHLEN]; +#if defined(HAVE_READDIR64) + struct dirent64 *direntp; +#else + struct dirent *direntp; +#endif + + /* + * find directory name component ... copy as dirname() may clobber + * the string + */ + if ((tbuf = strdup(name)) == NULL) + return -oserror(); + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + dir = dirname(tbuf); + + /* + * find file name component + */ + strncpy(filename, name, MAXPATHLEN); + filename[MAXPATHLEN-1] = '\0'; + if ((base = strdup(basename(filename))) == NULL) { + sts = -oserror(); + free(tbuf); + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + PM_UNLOCK(__pmLock_libpcp); + + if (access(name, R_OK) == 0) { + /* + * file exists and is readable ... if name contains '.' and + * suffix is "index", "meta" or a string of digits or a string + * of digits followed by one of the compression suffixes, + * strip the suffix + */ + int strip = 0; + if ((q = strrchr(base, '.')) != NULL) { + if (strcmp(q, ".index") == 0) { + strip = 1; + goto done; + } + if (strcmp(q, ".meta") == 0) { + strip = 1; + goto done; + } + for (i = 0; i < ncompress; i++) { + if (strcmp(q, compress_ctl[i].suff) == 0) { + char *q2; + /* + * name ends with one of the supported compressed file + * suffixes, check for a string of digits before that, + * e.g. if base is initially "foo.0.bz2", we want it + * stripped to "foo" + */ + *q = '\0'; + if ((q2 = strrchr(base, '.')) == NULL) { + /* no . to the left of the suffix */ + *q = '.'; + goto done; + } + q = q2; + break; + } + } + if (q[1] != '\0') { + char *end; + /* + * Below we don't care about the value from strtol(), + * we're interested in updating the pointer "end". + * The messiness is thanks to gcc and glibc ... strtol() + * is marked __attribute__((warn_unused_result)) ... + * to avoid warnings on all platforms, assign to a + * dummy variable that is explicitly marked unused. + */ + long tmpl __attribute__((unused)); + tmpl = strtol(q+1, &end, 10); + if (*end == '\0') strip = 1; + } + } +done: + if (strip) { + *q = '\0'; + } + } + + snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, base); + if ((lcp->l_name = strdup(filename)) == NULL) { + sts = -oserror(); + free(tbuf); + free(base); + return sts; + } + + lcp->l_minvol = -1; + lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL; + lcp->l_ti = NULL; + lcp->l_hashpmid.nodes = lcp->l_hashpmid.hsize = 0; + lcp->l_hashindom.nodes = lcp->l_hashindom.hsize = 0; + lcp->l_numseen = 0; lcp->l_seen = NULL; + lcp->l_pmns = NULL; + + blen = (int)strlen(base); + PM_LOCK(__pmLock_libpcp); + if ((dirp = opendir(dir)) != NULL) { +#if defined(HAVE_READDIR64) + while ((direntp = readdir64(dirp)) != NULL) +#else + while ((direntp = readdir(dirp)) != NULL) +#endif + { + if (strncmp(base, direntp->d_name, blen) != 0) + continue; + if (direntp->d_name[blen] != '.') + continue; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name); + fprintf(stderr, "__pmLogOpen: inspect file \"%s\"\n", filename); + } +#endif + tp = &direntp->d_name[blen+1]; + if (strcmp(tp, "index") == 0) { + exists = 1; + snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name); + if ((lcp->l_tifp = fopen(filename, "r")) == NULL) { + sts = -oserror(); + PM_UNLOCK(__pmLock_libpcp); + goto cleanup; + } + } + else if (strcmp(tp, "meta") == 0) { + exists = 1; + snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name); + if ((lcp->l_mdfp = fopen(filename, "r")) == NULL) { + sts = -oserror(); + PM_UNLOCK(__pmLock_libpcp); + goto cleanup; + } + } + else { + char *q; + int vol; + vol = (int)strtol(tp, &q, 10); + if (*q != '0') { + /* may have one of the trailing compressed file suffixes */ + int i; + for (i = 0; i < ncompress; i++) { + if (strcmp(q, compress_ctl[i].suff) == 0) { + /* match */ + *q = '\0'; + break; + } + } + } + if (*q == '\0') { + exists = 1; + if (lcp->l_minvol == -1) { + lcp->l_minvol = vol; + lcp->l_maxvol = vol; + } + else { + if (vol < lcp->l_minvol) + lcp->l_minvol = vol; + if (vol > lcp->l_maxvol) + lcp->l_maxvol = vol; + } + } + } + } + closedir(dirp); + dirp = NULL; + } + else { +#ifdef PCP_DEBUG + sts = -oserror(); + if (pmDebug & DBG_TRACE_LOG) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmLogOpen: cannot scan directory \"%s\": %s\n", dir, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + PM_UNLOCK(__pmLock_libpcp); + goto cleanup; + +#endif + } + PM_UNLOCK(__pmLock_libpcp); + + if (lcp->l_minvol == -1 || lcp->l_mdfp == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + if (lcp->l_minvol == -1) + fprintf(stderr, "__pmLogOpen: Not found: data file \"%s.0\" (or similar)\n", base); + if (lcp->l_mdfp == NULL) + fprintf(stderr, "__pmLogOpen: Not found: metadata file \"%s.meta\"\n", base); + } +#endif + if (exists) + sts = PM_ERR_LOGFILE; + else + sts = -ENOENT; + goto cleanup; + } + free(tbuf); + free(base); + return 0; + +cleanup: + if (dirp != NULL) + closedir(dirp); + __pmLogClose(lcp); + free(tbuf); + free(base); + return sts; +} + +int +__pmLogOpen(const char *name, __pmContext *ctxp) +{ + __pmLogCtl *lcp = ctxp->c_archctl->ac_log; + __pmLogLabel label; + int version; + int sts; + + if ((sts = __pmLogLoadLabel(lcp, name)) < 0) + return sts; + + lcp->l_curvol = -1; + if ((sts = __pmLogChangeVol(lcp, lcp->l_minvol)) < 0) + goto cleanup; + else + version = sts; + + ctxp->c_origin = lcp->l_label.ill_start; + + if (lcp->l_tifp) { + sts = __pmLogChkLabel(lcp, lcp->l_tifp, &label, PM_LOG_VOL_TI); + if (sts < 0) + goto cleanup; + else if (sts != version) { + /* mismatch between meta & actual data versions! */ + sts = PM_ERR_LABEL; + goto cleanup; + } + + if (lcp->l_label.ill_pid != label.ill_pid || + strcmp(lcp->l_label.ill_hostname, label.ill_hostname) != 0) { + sts = PM_ERR_LABEL; + goto cleanup; + } + } + + if ((sts = __pmLogChkLabel(lcp, lcp->l_mdfp, &label, PM_LOG_VOL_META)) < 0) + goto cleanup; + else if (sts != version) { /* version mismatch between meta & ti */ + sts = PM_ERR_LABEL; + goto cleanup; + } + + if ((sts = __pmLogLoadMeta(lcp)) < 0) + goto cleanup; + + if ((sts = __pmLogLoadIndex(lcp)) < 0) + goto cleanup; + + if (lcp->l_label.ill_pid != label.ill_pid || + strcmp(lcp->l_label.ill_hostname, label.ill_hostname) != 0) { + sts = PM_ERR_LABEL; + goto cleanup; + } + + lcp->l_refcnt = 0; + lcp->l_physend = -1; + + ctxp->c_mode = (ctxp->c_mode & 0xffff0000) | PM_MODE_FORW; + + return 0; + +cleanup: + __pmLogClose(lcp); + return sts; +} + +void +__pmLogPutIndex(const __pmLogCtl *lcp, const __pmTimeval *tp) +{ + __pmLogTI ti; + __pmLogTI oti; + int sts; + + if (lcp->l_tifp == NULL || lcp->l_mdfp == NULL || lcp->l_mfp == NULL) { + /* + * archive not really created (failed in __pmLogCreate) ... + * nothing to be done + */ + return; + } + + if (tp == NULL) { + struct timeval tmp; + + __pmtimevalNow(&tmp); + ti.ti_stamp.tv_sec = (__int32_t)tmp.tv_sec; + ti.ti_stamp.tv_usec = (__int32_t)tmp.tv_usec; + } + else + ti.ti_stamp = *tp; /* struct assignment */ + ti.ti_vol = lcp->l_curvol; + fflush(lcp->l_mdfp); + fflush(lcp->l_mfp); + + if (sizeof(off_t) > sizeof(__pm_off_t)) { + /* check for overflow of the offset ... */ + off_t tmp; + + tmp = ftell(lcp->l_mdfp); + assert(tmp >= 0); + ti.ti_meta = (__pm_off_t)tmp; + if (tmp != ti.ti_meta) { + __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive file (meta) too big\n"); + return; + } + tmp = ftell(lcp->l_mfp); + assert(tmp >= 0); + ti.ti_log = (__pm_off_t)tmp; + if (tmp != ti.ti_log) { + __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive file (data) too big\n"); + return; + } + } + else { + ti.ti_meta = (__pm_off_t)ftell(lcp->l_mdfp); + ti.ti_log = (__pm_off_t)ftell(lcp->l_mfp); + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "__pmLogPutIndex: timestamp=%d.06%d vol=%d meta posn=%ld log posn=%ld\n", + (int)ti.ti_stamp.tv_sec, (int)ti.ti_stamp.tv_usec, + ti.ti_vol, (long)ti.ti_meta, (long)ti.ti_log); + } +#endif + + oti.ti_stamp.tv_sec = htonl(ti.ti_stamp.tv_sec); + oti.ti_stamp.tv_usec = htonl(ti.ti_stamp.tv_usec); + oti.ti_vol = htonl(ti.ti_vol); + oti.ti_meta = htonl(ti.ti_meta); + oti.ti_log = htonl(ti.ti_log); + if ((sts = fwrite(&oti, 1, sizeof(oti), lcp->l_tifp)) != sizeof(oti)) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutIndex: write failed: returns %d expecting %d: %s\n", + sts, (int)sizeof(oti), osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + } + if (fflush(lcp->l_tifp) != 0) + __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive temporal index flush failed\n"); +} + +static int +logputresult(int version,__pmLogCtl *lcp, __pmPDU *pb) +{ + /* + * This is a bit tricky ... + * + * Input + * :---------:----------:----------:---------------- .........:---------: + * | int len | int type | int from | timestamp, .... pmResult | unused | + * :---------:----------:----------:---------------- .........:---------: + * ^ + * | + * pb + * + * Output + * :---------:----------:----------:---------------- .........:---------: + * | unused | unused | int len | timestamp, .... pmResult | int len | + * :---------:----------:----------:---------------- .........:---------: + * ^ + * | + * start + * + * If version == 1, pb[] does not have room for trailer len. + * If version == 2, pb[] does have room for trailer len. + */ + int sz; + int sts = 0; + int save_from; + __pmPDU *start = &pb[2]; + + if (lcp->l_state == PM_LOG_STATE_NEW) { + int i; + __pmTimeval *tvp; + /* + * first result, do the label record + */ + i = sizeof(__pmPDUHdr) / sizeof(__pmPDU); + tvp = (__pmTimeval *)&pb[i]; + lcp->l_label.ill_start.tv_sec = ntohl(tvp->tv_sec); + lcp->l_label.ill_start.tv_usec = ntohl(tvp->tv_usec); + lcp->l_label.ill_vol = PM_LOG_VOL_TI; + __pmLogWriteLabel(lcp->l_tifp, &lcp->l_label); + lcp->l_label.ill_vol = PM_LOG_VOL_META; + __pmLogWriteLabel(lcp->l_mdfp, &lcp->l_label); + lcp->l_label.ill_vol = 0; + __pmLogWriteLabel(lcp->l_mfp, &lcp->l_label); + lcp->l_state = PM_LOG_STATE_INIT; + } + + sz = pb[0] - (int)sizeof(__pmPDUHdr) + 2 * (int)sizeof(int); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "logputresult: pdubuf=" PRINTF_P_PFX "%p input len=%d output len=%d posn=%ld\n", pb, pb[0], sz, (long)ftell(lcp->l_mfp)); + } +#endif + + save_from = start[0]; + start[0] = htonl(sz); /* swab */ + + if (version == 1) { + if ((sts = fwrite(start, 1, sz-sizeof(int), lcp->l_mfp)) != sz-sizeof(int)) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutResult: write failed: returns %d expecting %d: %s\n", + sts, (int)(sz-sizeof(int)), osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + sts = -oserror(); + } + else { + if ((sts = fwrite(start, 1, sizeof(int), lcp->l_mfp)) != sizeof(int)) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutResult: trailer write failed: returns %d expecting %d: %s\n", + sts, (int)sizeof(int), osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + sts = -oserror(); + } + } + } + else { + /* assume version == 2 */ + start[(sz-1)/sizeof(__pmPDU)] = start[0]; + if ((sts = fwrite(start, 1, sz, lcp->l_mfp)) != sz) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("__pmLogPutResult2: write failed: returns %d expecting %d: %s\n", + sts, sz, osstrerror_r(errmsg, sizeof(errmsg))); + pmflush(); + sts = -oserror(); + } + } + + /* restore and unswab */ + start[0] = save_from; + + return sts; +} + +/* + * original routine, pb[] does not have room for trailer, so 2 writes + * needed + */ +int +__pmLogPutResult(__pmLogCtl *lcp, __pmPDU *pb) +{ + return logputresult(1, lcp, pb); +} + +/* + * new routine, pb[] does have room for trailer, so only 1 write + * needed + */ +int +__pmLogPutResult2(__pmLogCtl *lcp, __pmPDU *pb) +{ + return logputresult(2, lcp, pb); +} + +/* + * check if PDU buffer seems even half-way reasonable ... + * only used when trying to locate end of archive. + * return 0 for OK, -1 for bad. + */ +static int +paranoidCheck(int len, __pmPDU *pb) +{ + int numpmid; + size_t hdrsz; /* bytes for the PDU head+tail */ + int i; + int j; + int vsize; /* size of vlist_t's in PDU buffer */ + int vbsize; /* size of pmValueBlocks */ + int numval; /* number of values */ + int valfmt; + + struct result_t { /* from p_result.c */ + __pmPDUHdr hdr; + __pmTimeval timestamp; /* when returned */ + int numpmid; /* no. of PMIDs to follow */ + __pmPDU data[1]; /* zero or more */ + } *pp; + struct vlist_t { /* from p_result.c */ + pmID pmid; + int numval; /* no. of vlist els to follow, or error */ + int valfmt; /* insitu or pointer */ + __pmValue_PDU vlist[1]; /* zero or more */ + } *vlp; + + /* + * to start with, need space for result_t with no data (__pmPDU) + * ... this is the external size, which consists of + * <header len> + * <timestamp> (2 words) + * <numpmid> + * <trailer len> + * + * it is confusing because *pb and result_t include the fake + * __pmPDUHdr which is not really in the external file + */ + hdrsz = 5 * sizeof(__pmPDU); + + if (len < hdrsz) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: len=%d, min len=%d\n", + len, (int)hdrsz); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } + + pp = (struct result_t *)pb; + numpmid = ntohl(pp->numpmid); + + /* + * This is a re-implementation of much of __pmDecodeResult() + */ + + if (numpmid < 1) { + if (len != hdrsz) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: numpmid=%d len=%d, expected len=%d\n", + numpmid, len, (int)hdrsz); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } + } + + /* + * Calculate vsize and vbsize from the original PDU buffer ... + * :---------:-----------:----------------:--------------------: + * : numpmid : timestamp : ... vlists ... : .. pmValueBocks .. : + * :---------:-----------:----------------:--------------------: + * <--- vsize ---> <--- vbsize ---> + * bytes bytes + */ + + vsize = vbsize = 0; + for (i = 0; i < numpmid; i++) { + vlp = (struct vlist_t *)&pp->data[vsize/sizeof(__pmPDU)]; + vsize += sizeof(vlp->pmid) + sizeof(vlp->numval); + if (len < hdrsz + vsize + vbsize) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: vset[%d] len=%d, need len>=%d (%d+%d+%d)\n", + i, len, (int)(hdrsz + vsize + vbsize), (int)hdrsz, vsize, vbsize); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } + numval = ntohl(vlp->numval); + if (numval > 0) { +#ifdef DESPERATE + pmID pmid; +#endif + valfmt = ntohl(vlp->valfmt); + if (valfmt != PM_VAL_INSITU && + valfmt != PM_VAL_DPTR && + valfmt != PM_VAL_SPTR) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: vset[%d] bad valfmt=%d\n", + i, valfmt); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } +#ifdef DESPERATE + { + char strbuf[20]; + if (i == 0) fputc('\n', stderr); + pmid = __ntohpmID(vlp->pmid); + fprintf(stderr, "vlist[%d] pmid: %s numval: %d valfmt: %d\n", + i, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), numval, valfmt); + } +#endif + vsize += sizeof(vlp->valfmt) + numval * sizeof(__pmValue_PDU); + if (valfmt != PM_VAL_INSITU) { + for (j = 0; j < numval; j++) { + int index = (int)ntohl((long)vlp->vlist[j].value.pval); + pmValueBlock *pduvbp; + int vlen; + + if (index < 0 || index * sizeof(__pmPDU) > len) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: vset[%d] val[%d], bad pval index=%d not in range 0..%d\n", + i, j, index, (int)(len / sizeof(__pmPDU))); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } + pduvbp = (pmValueBlock *)&pb[index]; + __ntohpmValueBlock(pduvbp); + vlen = pduvbp->vlen; + __htonpmValueBlock(pduvbp); /* restore pdubuf! */ + if (vlen < sizeof(__pmPDU)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "\nparanoidCheck: vset[%d] val[%d], bad vlen=%d\n", + i, j, vlen); + dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */ + } +#endif + return -1; + } + vbsize += PM_PDU_SIZE_BYTES(vlen); + } + } + } + } + + return 0; +} + +static int +paranoidLogRead(__pmLogCtl *lcp, int mode, FILE *peekf, pmResult **result) +{ + return __pmLogRead(lcp, mode, peekf, result, PMLOGREAD_TO_EOF); +} + +/* + * read next forward or backward from the log + * + * by default (peekf == NULL) use lcp->l_mfp and roll volume + * at end of file if another volume is available + * + * if peekf != NULL, use this stream, and do not roll volume + */ +int +__pmLogRead(__pmLogCtl *lcp, int mode, FILE *peekf, pmResult **result, int option) +{ + int head; + int rlen; + int trail; + int sts; + long offset; + __pmPDU *pb; + FILE *f; + int n; + + /* + * Strip any XTB data from mode, its not used here + */ + mode &= __PM_MODE_MASK; + + if (peekf != NULL) + f = peekf; + else + f = lcp->l_mfp; + + offset = ftell(f); + assert(offset >= 0); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "__pmLogRead: fd=%d%s mode=%s vol=%d posn=%ld ", + fileno(f), peekf == NULL ? "" : " (peek)", + mode == PM_MODE_FORW ? "forw" : "back", + lcp->l_curvol, (long)offset); + } +#endif + + if (mode == PM_MODE_BACK) { + for ( ; ; ) { + if (offset <= sizeof(__pmLogLabel) + 2 * sizeof(int)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "BEFORE start\n"); +#endif + if (peekf == NULL) { + int vol = lcp->l_curvol-1; + while (vol >= lcp->l_minvol) { + if (__pmLogChangeVol(lcp, vol) >= 0) { + f = lcp->l_mfp; + fseek(f, 0L, SEEK_END); + offset = ftell(f); + assert(offset >= 0); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "vol=%d posn=%ld ", + lcp->l_curvol, (long)offset); + } +#endif + break; + } + vol--; + } + if (vol < lcp->l_minvol) + return PM_ERR_EOL; + } + else + return PM_ERR_EOL; + } + else { + fseek(f, -(long)sizeof(head), SEEK_CUR); + break; + } + } + } + +again: + n = (int)fread(&head, 1, sizeof(head), f); + head = ntohl(head); /* swab head */ + if (n != sizeof(head)) { + if (feof(f)) { + /* no more data ... looks like End of Archive volume */ + clearerr(f); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "AFTER end\n"); +#endif + fseek(f, offset, SEEK_SET); + if (peekf == NULL) { + int vol = lcp->l_curvol+1; + while (vol <= lcp->l_maxvol) { + if (__pmLogChangeVol(lcp, vol) >= 0) { + f = lcp->l_mfp; + goto again; + } + vol++; + } + } + return PM_ERR_EOL; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "\nError: header fread got %d expected %d\n", n, (int)sizeof(head)); +#endif + if (ferror(f)) { + /* I/O error */ + clearerr(f); + return -oserror(); + } + else + /* corrupted archive */ + return PM_ERR_LOGREC; + } + + /* + * This is pretty ugly (forward case shown backwards is similar) ... + * + * Input + * head <--- rlen bytes -- ...---> tail + * :---------:---------:---------:---------------- .........:---------: + * | ??? | ??? | int len | timestamp, .... pmResult | int len | + * :---------:---------:---------:---------------- .........:---------: + * ^ ^ + * | | + * pb read into here + * + * Decode + * <---- __pmPDUHdr -----------> + * :---------:---------:---------:---------------- .........:---------: + * | length | pdutype | anon | timestamp, .... pmResult | int len | + * :---------:---------:---------:---------------- .........:---------: + * ^ + * | + * pb + * + * Note: cannot volume switch in the middle of a log record + */ + + rlen = head - 2 * (int)sizeof(head); + if (rlen < 0 || (mode == PM_MODE_BACK && rlen > offset)) { + /* + * corrupted! usually means a truncated log ... + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "\nError: truncated log? rlen=%d (offset %d)\n", + rlen, (int)offset); +#endif + return PM_ERR_LOGREC; + } + /* + * need to add int at end for trailer in case buffer is used + * subsequently by __pmLogPutResult2() + */ + if ((pb = __pmFindPDUBuf(rlen + (int)sizeof(__pmPDUHdr) + (int)sizeof(int))) == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "\nError: __pmFindPDUBuf(%d) %s\n", + (int)(rlen + sizeof(__pmPDUHdr)), + osstrerror_r(errmsg, sizeof(errmsg))); + } +#endif + fseek(f, offset, SEEK_SET); + return -oserror(); + } + + if (mode == PM_MODE_BACK) + fseek(f, -(long)(sizeof(head) + rlen), SEEK_CUR); + + if ((n = (int)fread(&pb[3], 1, rlen, f)) != rlen) { + /* data read failed */ + __pmUnpinPDUBuf(pb); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "\nError: data fread got %d expected %d\n", n, rlen); +#endif + fseek(f, offset, SEEK_SET); + if (ferror(f)) { + /* I/O error */ + clearerr(f); + return -oserror(); + } + clearerr(f); + + /* corrupted archive */ + return PM_ERR_LOGREC; + } + else { + __pmPDUHdr *header = (__pmPDUHdr *)pb; + header->len = sizeof(*header) + rlen; + header->type = PDU_RESULT; + header->from = FROM_ANON; + /* swab pdu buffer - done later in __pmDecodeResult */ + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + int j; + char *p; + int jend = PM_PDU_SIZE(header->len); + + /* for Purify ... */ + p = (char *)pb + header->len; + while (p < (char *)pb + jend*sizeof(__pmPDU)) + *p++ = '~'; /* buffer end */ + + fprintf(stderr, "__pmLogRead: PDU buffer\n"); + for (j = 0; j < jend; j++) { + if ((j % 8) == 0 && j > 0) + fprintf(stderr, "\n%03d: ", j); + fprintf(stderr, "%8x ", pb[j]); + } + putc('\n', stderr); + } +#endif + } + + if (mode == PM_MODE_BACK) + fseek(f, -(long)(rlen + sizeof(head)), SEEK_CUR); + + if ((n = (int)fread(&trail, 1, sizeof(trail), f)) != sizeof(trail)) { + __pmUnpinPDUBuf(pb); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "\nError: trailer fread got %d expected %d\n", n, (int)sizeof(trail)); +#endif + fseek(f, offset, SEEK_SET); + if (ferror(f)) { + /* I/O error */ + clearerr(f); + return -oserror(); + } + clearerr(f); + + /* corrupted archive */ + return PM_ERR_LOGREC; + } + else { + /* swab trail */ + trail = ntohl(trail); + } + + if (trail != head) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "\nError: record length mismatch: header (%d) != trailer (%d)\n", head, trail); +#endif + __pmUnpinPDUBuf(pb); + return PM_ERR_LOGREC; + } + + if (option == PMLOGREAD_TO_EOF && paranoidCheck(head, pb) == -1) { + __pmUnpinPDUBuf(pb); + return PM_ERR_LOGREC; + } + + if (mode == PM_MODE_BACK) + fseek(f, -(long)sizeof(trail), SEEK_CUR); + + __pmOverrideLastFd(fileno(f)); + sts = __pmDecodeResult(pb, result); /* also swabs the result */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + head -= sizeof(head) + sizeof(trail); + if (sts >= 0) { + __pmTimeval tmp; + fprintf(stderr, "@"); + __pmPrintStamp(stderr, &(*result)->timestamp); + tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + fprintf(stderr, " (t=%.6f)", __pmTimevalSub(&tmp, &lcp->l_label.ill_start)); + } + else { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmLogRead: __pmDecodeResult failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + fprintf(stderr, "@unknown time"); + } + fprintf(stderr, " len=header+%d+trailer\n", head); + } +#endif + + /* exported to indicate how efficient we are ... */ + __pmLogReads++; + + if (sts < 0) { + __pmUnpinPDUBuf(pb); + return PM_ERR_LOGREC; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + fprintf(stderr, "__pmLogRead timestamp="); + __pmPrintStamp(stderr, &(*result)->timestamp); + fprintf(stderr, " " PRINTF_P_PFX "%p ... " PRINTF_P_PFX "%p", &pb[3], &pb[head/sizeof(__pmPDU)+3]); + fputc('\n', stderr); + dumpbuf(rlen, &pb[3]); /* see above to explain "3" */ + } +#endif + + __pmUnpinPDUBuf(pb); + + return 0; +} + +static int +check_all_derived(int numpmid, pmID pmidlist[]) +{ + int i; + + /* + * Special case ... if we ONLY have derived metrics in the input + * pmidlist then all the derived metrics must be constant + * expressions, so skip all the processing. + * Derived metrics have domain == DYNAMIC_PMID and item != 0. + * This rare, but avoids reading to the end of an archive + * for no good reason. + */ + + for (i = 0; i < numpmid; i++) { + if (pmid_domain(pmidlist[i]) != DYNAMIC_PMID || + pmid_item(pmidlist[i]) == 0) + return 0; + } + return 1; +} + +int +__pmLogFetch(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result) +{ + int i; + int j; + int u; + int all_derived; + int sts = 0; + int found; + double tdiff; + pmResult *newres; + pmDesc desc; + int kval; + __pmHashNode *hp; + pmid_ctl *pcp; + int nskip; + __pmTimeval tmp; + int ctxp_mode = ctxp->c_mode & __PM_MODE_MASK; + + if (ctxp_mode == PM_MODE_INTERP) { + return __pmLogFetchInterp(ctxp, numpmid, pmidlist, result); + } + + all_derived = check_all_derived(numpmid, pmidlist); + + /* re-establish position */ + __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol); + fseek(ctxp->c_archctl->ac_log->l_mfp, + (long)ctxp->c_archctl->ac_offset, SEEK_SET); + +more: + + found = 0; + nskip = 0; + *result = NULL; + while (!found) { + if (ctxp->c_archctl->ac_serial == 0) { + /* + * no serial access, so need to make sure we are + * starting in the correct place + */ + int tmp_mode; + nskip = 0; + if (ctxp_mode == PM_MODE_FORW) + tmp_mode = PM_MODE_BACK; + else + tmp_mode = PM_MODE_FORW; + while (__pmLogRead(ctxp->c_archctl->ac_log, tmp_mode, NULL, result, PMLOGREAD_NEXT) >= 0) { + nskip++; + tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + tdiff = __pmTimevalSub(&tmp, &ctxp->c_origin); + if ((tdiff < 0 && ctxp_mode == PM_MODE_FORW) || + (tdiff > 0 && ctxp_mode == PM_MODE_BACK)) { + pmFreeResult(*result); + *result = NULL; + break; + } + else if (tdiff == 0) { + /* exactly the one we wanted */ + found = 1; + break; + } + pmFreeResult(*result); + *result = NULL; + } + ctxp->c_archctl->ac_serial = 1; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + if (nskip) { + fprintf(stderr, "__pmLogFetch: ctx=%d skip reverse %d to ", + pmWhichContext(), nskip); + if (*result != NULL) + __pmPrintStamp(stderr, &(*result)->timestamp); + else + fprintf(stderr, "unknown time"); + fprintf(stderr, ", found=%d\n", found); + } +#ifdef DESPERATE + else + fprintf(stderr, "__pmLogFetch: ctx=%d no skip reverse\n", + pmWhichContext()); +#endif + } +#endif + nskip = 0; + } + if (found) + break; + if ((sts = __pmLogRead(ctxp->c_archctl->ac_log, ctxp->c_mode, NULL, result, PMLOGREAD_NEXT)) < 0) + break; + tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + tdiff = __pmTimevalSub(&tmp, &ctxp->c_origin); + if ((tdiff < 0 && ctxp_mode == PM_MODE_FORW) || + (tdiff > 0 && ctxp_mode == PM_MODE_BACK)) { + nskip++; + pmFreeResult(*result); + *result = NULL; + continue; + } + found = 1; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + if (nskip) { + fprintf(stderr, "__pmLogFetch: ctx=%d skip %d to ", + pmWhichContext(), nskip); + __pmPrintStamp(stderr, &(*result)->timestamp); + fputc('\n', stderr); + } +#ifdef DESPERATE + else + fprintf(stderr, "__pmLogFetch: ctx=%d no skip\n", + pmWhichContext()); +#endif + } +#endif + } + if (found) { + ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec; + ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec; + } + + if (*result != NULL && (*result)->numpmid == 0) { + /* + * mark record, and not interpolating ... + * if pmFetchArchive(), return it + * otherwise keep searching + */ + if (numpmid == 0) + newres = *result; + else { + pmFreeResult(*result); + goto more; + } + } + else if (found) { + if (numpmid > 0) { + /* + * not necesssarily after them all, so cherry-pick the metrics + * we wanted .. + * there are two tricks here ... + * (1) pmValueSets for metrics requested, but not in the pmResult + * from the log are assigned using the first two fields in the + * pmid_ctl struct -- since these are allocated once as + * needed, and never free'd, we have to make sure pmFreeResult + * finds a pmValueSet in a pinned pdu buffer ... this means + * we must find at least one real value from the log to go + * with any "unavailable" results + * (2) real pmValueSets can be ignored, they are in a pdubuf + * and will be reclaimed when the buffer is unpinned in + * pmFreeResult + */ + + i = (int)sizeof(pmResult) + numpmid * (int)sizeof(pmValueSet *); + if ((newres = (pmResult *)malloc(i)) == NULL) { + __pmNoMem("__pmLogFetch.newres", i, PM_FATAL_ERR); + } + newres->numpmid = numpmid; + newres->timestamp = (*result)->timestamp; + u = 0; + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + for (j = 0; j < numpmid; j++) { + hp = __pmHashSearch((int)pmidlist[j], &pc_hc); + if (hp == NULL) { + /* first time we've been asked for this one */ + if ((pcp = (pmid_ctl *)malloc(sizeof(pmid_ctl))) == NULL) { + __pmNoMem("__pmLogFetch.pmid_ctl", sizeof(pmid_ctl), PM_FATAL_ERR); + } + pcp->pc_pmid = pmidlist[j]; + pcp->pc_numval = 0; + sts = __pmHashAdd((int)pmidlist[j], (void *)pcp, &pc_hc); + if (sts < 0) { + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + } + else + pcp = (pmid_ctl *)hp->data; + for (i = 0; i < (*result)->numpmid; i++) { + if (pmidlist[j] == (*result)->vset[i]->pmid) { + /* match */ + newres->vset[j] = (*result)->vset[i]; + u++; + break; + } + } + if (i == (*result)->numpmid) { + /* + * requested metric not returned from the log, construct + * a "no values available" pmValueSet from the pmid_ctl + */ + newres->vset[j] = (pmValueSet *)pcp; + } + } + PM_UNLOCK(__pmLock_libpcp); + if (u == 0 && !all_derived) { + /* + * not one of our pmids was in the log record, try + * another log record ... + */ + pmFreeResult(*result); + free(newres); + goto more; + } + /* + * *result malloc'd in __pmLogRead, but vset[]'s are either in + * pdubuf or the pmid_ctl struct + */ + free(*result); + *result = newres; + } + else + /* numpmid == 0, pmFetchArchive() call */ + newres = *result; + /* + * Apply instance profile filtering ... + * Note. This is a little strange, as in the numpmid == 0, + * pmFetchArchive() case, this for-loop is not executed ... + * this is correct, the instance profile is ignored for + * pmFetchArchive() + */ + for (i = 0; i < numpmid; i++) { + if (newres->vset[i]->numval <= 0) { + /* + * no need to xlate numval for an error ... already done + * below __pmLogRead() in __pmDecodeResult() ... also xlate + * here would have been skipped in the pmFetchArchive() case + */ + continue; + } + sts = __pmLogLookupDesc(ctxp->c_archctl->ac_log, newres->vset[i]->pmid, &desc); + if (sts < 0) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_WARNING, "__pmLogFetch: missing pmDesc for pmID %s: %s", + pmIDStr_r(desc.pmid, strbuf, sizeof(strbuf)), pmErrStr_r(sts, errmsg, sizeof(errmsg))); + pmFreeResult(newres); + break; + } + if (desc.indom == PM_INDOM_NULL) + /* no instance filtering to be done for these ones */ + continue; + + /* + * scan instances, keeping those "in" the instance profile + * + * WARNING + * This compresses the pmValueSet INSITU, and since + * these are in a pdu buffer, it trashes the the + * pdu buffer and means there is no clever way of + * re-using the pdu buffer to satisfy multiple + * pmFetch requests + * Fortunately, stdio buffering means copying to + * make additional pdu buffers is not too expensive. + */ + kval = 0; + for (j = 0; j < newres->vset[i]->numval; j++) { + if (__pmInProfile(desc.indom, ctxp->c_instprof, newres->vset[i]->vlist[j].inst)) { + if (kval != j) + /* struct assignment */ + newres->vset[i]->vlist[kval] = newres->vset[i]->vlist[j]; + kval++; + } + } + newres->vset[i]->numval = kval; + } + } + + /* remember your position in this context */ + ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; + + return sts; +} + +/* + * error handling wrapper around __pmLogChangeVol() to deal with + * missing volumes ... return lcp->l_ti[] index for entry matching + * success + */ +static int +VolSkip(__pmLogCtl *lcp, int mode, int j) +{ + int vol = lcp->l_ti[j].ti_vol; + + while (lcp->l_minvol <= vol && vol <= lcp->l_maxvol) { + if (__pmLogChangeVol(lcp, vol) >= 0) + return j; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "VolSkip: Skip missing vol %d\n", vol); + } +#endif + if (mode == PM_MODE_FORW) { + for (j++; j < lcp->l_numti; j++) + if (lcp->l_ti[j].ti_vol != vol) + break; + if (j == lcp->l_numti) + return PM_ERR_EOL; + vol = lcp->l_ti[j].ti_vol; + } + else { + for (j--; j >= 0; j--) + if (lcp->l_ti[j].ti_vol != vol) + break; + if (j < 0) + return PM_ERR_EOL; + vol = lcp->l_ti[j].ti_vol; + } + } + return PM_ERR_EOL; +} + +void +__pmLogSetTime(__pmContext *ctxp) +{ + __pmLogCtl *lcp = ctxp->c_archctl->ac_log; + int mode; + + mode = ctxp->c_mode & __PM_MODE_MASK; /* strip XTB data */ + + if (mode == PM_MODE_INTERP) + mode = ctxp->c_delta > 0 ? PM_MODE_FORW : PM_MODE_BACK; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "__pmLogSetTime(%d) ", pmWhichContext()); + __pmPrintTimeval(stderr, &ctxp->c_origin); + fprintf(stderr, " delta=%d", ctxp->c_delta); + } +#endif + + if (lcp->l_numti) { + /* we have a temporal index, use it! */ + int i; + int j = -1; + int toobig = 0; + int match = 0; + int numti = lcp->l_numti; + __pmLogTI *tip = lcp->l_ti; + double t_hi; + double t_lo; + struct stat sbuf; + + sbuf.st_size = -1; + + for (i = 0; i < numti; i++, tip++) { + if (tip->ti_vol < lcp->l_minvol) + /* skip missing preliminary volumes */ + continue; + if (tip->ti_vol == lcp->l_maxvol) { + /* truncated check for last volume */ + if (sbuf.st_size < 0) { + FILE *f = _logpeek(lcp, lcp->l_maxvol); + + sbuf.st_size = 0; + if (f != NULL) { + fstat(fileno(f), &sbuf); + fclose(f); + } + } + if (tip->ti_log > sbuf.st_size) { + j = i; + toobig++; + break; + } + } + t_hi = __pmTimevalSub(&tip->ti_stamp, &ctxp->c_origin); + if (t_hi > 0) { + j = i; + break; + } + else if (t_hi == 0) { + j = i; + match = 1; + break; + } + } + if (i == numti) + j = numti; + + ctxp->c_archctl->ac_serial = 1; + + if (match) { + j = VolSkip(lcp, mode, j); + if (j < 0) + return; + fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET); + if (mode == PM_MODE_BACK) + ctxp->c_archctl->ac_serial = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, " at ti[%d]@", j); + __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp); + } +#endif + } + else if (j < 1) { + j = VolSkip(lcp, PM_MODE_FORW, 0); + if (j < 0) + return; + fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, " before start ti@"); + __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp); + } +#endif + } + else if (j == numti) { + j = VolSkip(lcp, PM_MODE_BACK, numti-1); + if (j < 0) + return; + fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET); + if (mode == PM_MODE_BACK) + ctxp->c_archctl->ac_serial = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, " after end ti@"); + __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp); + } +#endif + } + else { + /* + * [j-1] [origin] [j] + * <----- t_lo -------><----- t_hi ----> + * + * choose closest index point. if toobig, [j] is not + * really valid (log truncated or incomplete) + */ + t_hi = __pmTimevalSub(&lcp->l_ti[j].ti_stamp, &ctxp->c_origin); + t_lo = __pmTimevalSub(&ctxp->c_origin, &lcp->l_ti[j-1].ti_stamp); + if (t_hi <= t_lo && !toobig) { + j = VolSkip(lcp, mode, j); + if (j < 0) + return; + fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET); + if (mode == PM_MODE_FORW) + ctxp->c_archctl->ac_serial = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, " before ti[%d]@", j); + __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp); + } +#endif + } + else { + j = VolSkip(lcp, mode, j-1); + if (j < 0) + return; + fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET); + if (mode == PM_MODE_BACK) + ctxp->c_archctl->ac_serial = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, " after ti[%d]@", j); + __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp); + } +#endif + } + if (ctxp->c_archctl->ac_serial && mode == PM_MODE_FORW) { + /* + * back up one record ... + * index points to the END of the record! + */ + pmResult *result; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " back up ...\n"); +#endif + if (__pmLogRead(lcp, PM_MODE_BACK, NULL, &result, PMLOGREAD_NEXT) >= 0) + pmFreeResult(result); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, "..."); +#endif + } + } + } + else { + /* index either not available, or not useful */ + if (mode == PM_MODE_FORW) { + __pmLogChangeVol(lcp, lcp->l_minvol); + fseek(lcp->l_mfp, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET); + } + else if (mode == PM_MODE_BACK) { + __pmLogChangeVol(lcp, lcp->l_maxvol); + fseek(lcp->l_mfp, (long)0, SEEK_END); + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " index not useful\n"); +#endif + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) + fprintf(stderr, " vol=%d posn=%ld serial=%d\n", + lcp->l_curvol, (long)ftell(lcp->l_mfp), ctxp->c_archctl->ac_serial); +#endif + + /* remember your position in this context */ + ctxp->c_archctl->ac_offset = ftell(lcp->l_mfp); + assert(ctxp->c_archctl->ac_offset >= 0); + ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol; +} + +int +pmGetArchiveLabel(pmLogLabel *lp) +{ + __pmContext *ctxp; + ctxp = __pmHandleToPtr(pmWhichContext()); + if (ctxp == NULL || ctxp->c_type != PM_CONTEXT_ARCHIVE) + return PM_ERR_NOCONTEXT; + else { + __pmLogLabel *rlp; + /* + * we have to copy the structure to hide the differences + * between the internal __pmTimeval and the external struct timeval + */ + rlp = &ctxp->c_archctl->ac_log->l_label; + lp->ll_magic = rlp->ill_magic; + lp->ll_pid = (pid_t)rlp->ill_pid; + lp->ll_start.tv_sec = rlp->ill_start.tv_sec; + lp->ll_start.tv_usec = rlp->ill_start.tv_usec; + memcpy(lp->ll_hostname, rlp->ill_hostname, PM_LOG_MAXHOSTLEN); + memcpy(lp->ll_tz, rlp->ill_tz, sizeof(lp->ll_tz)); + PM_UNLOCK(ctxp->c_lock); + return 0; + } +} + +int +pmGetArchiveEnd(struct timeval *tp) +{ + /* + * set l_physend and l_endtime + * at the end of ... ctxp->c_archctl->ac_log + */ + __pmContext *ctxp; + int sts; + + ctxp = __pmHandleToPtr(pmWhichContext()); + if (ctxp == NULL || ctxp->c_type != PM_CONTEXT_ARCHIVE) + return PM_ERR_NOCONTEXT; + sts = __pmGetArchiveEnd(ctxp->c_archctl->ac_log, tp); + PM_UNLOCK(ctxp->c_lock); + return sts; +} + +int +__pmGetArchiveEnd(__pmLogCtl *lcp, struct timeval *tp) +{ + struct stat sbuf; + FILE *f; + long save = 0; + pmResult *rp = NULL; + pmResult *nrp; + int i; + int sts; + int found; + int head; + long offset; + int vol; + __pm_off_t logend; + __pm_off_t physend = 0; + + /* + * expect things to be stable, so l_maxvol is not empty, and + * l_physend does not change for l_maxvol ... the ugliness is + * to handle situations where these expectations are not met + */ + found = 0; + sts = PM_ERR_LOGREC; /* default error condition */ + f = NULL; + for (vol = lcp->l_maxvol; vol >= lcp->l_minvol; vol--) { + if (lcp->l_curvol == vol) { + f = lcp->l_mfp; + save = ftell(f); + assert(save >= 0); + } + else if ((f = _logpeek(lcp, vol)) == NULL) { + sts = -oserror(); + break; + } + + if (fstat(fileno(f), &sbuf) < 0) { + sts = -oserror(); + break; + } + + if (vol == lcp->l_maxvol && sbuf.st_size == lcp->l_physend) { + /* nothing changed, return cached stuff */ + tp->tv_sec = lcp->l_endtime.tv_sec; + tp->tv_usec = lcp->l_endtime.tv_usec; + sts = 0; + break; + } + + /* if this volume is empty, try previous volume */ + if (sbuf.st_size <= (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int)) { + if (f != lcp->l_mfp) { + fclose(f); + f = NULL; + } + continue; + } + + physend = (__pm_off_t)sbuf.st_size; + if (sizeof(off_t) > sizeof(__pm_off_t)) { + if (physend != sbuf.st_size) { + __pmNotifyErr(LOG_ERR, "pmGetArchiveEnd: PCP archive file" + " (meta) too big (%"PRIi64" bytes)\n", + (uint64_t)sbuf.st_size); + sts = PM_ERR_TOOBIG; + break; + } + } + + /* try to read backwards for the last physical record ... */ + fseek(f, (long)physend, SEEK_SET); + if (paranoidLogRead(lcp, PM_MODE_BACK, f, &rp) >= 0) { + /* success, we are done! */ + found = 1; + break; + } + + /* + * failure at the physical end of file may be related to a truncted + * block flush for a growing archive. Scan temporal index, and use + * last entry at or before end of physical file for this volume + */ + logend = (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int); + for (i = lcp->l_numti - 1; i >= 0; i--) { + if (lcp->l_ti[i].ti_vol != vol) { + if (f != lcp->l_mfp) { + fclose(f); + f = NULL; + } + continue; + } + if (lcp->l_ti[i].ti_log <= physend) { + logend = lcp->l_ti[i].ti_log; + break; + } + } + + /* + * Now chase it forwards from the last index entry ... + * + * BUG 357003 - pmchart can't read archive file + * turns out the index may point to the _end_ of the last + * valid record, so if not at start of volume, back up one + * record, then scan forwards. + */ + fseek(f, (long)logend, SEEK_SET); + if (logend > (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int)) { + if (paranoidLogRead(lcp, PM_MODE_BACK, f, &rp) < 0) { + /* this is badly damaged! */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LOG) { + fprintf(stderr, "pmGetArchiveEnd: " + "Error reading record ending at posn=%d ti[%d]@", + logend, i); + __pmPrintTimeval(stderr, &lcp->l_ti[i].ti_stamp); + fputc('\n', stderr); + } +#endif + break; + } + } + + /* Keep reading records from "logend" until can do so no more... */ + for ( ; ; ) { + offset = ftell(f); + assert(offset >= 0); + if ((int)fread(&head, 1, sizeof(head), f) != sizeof(head)) + /* cannot read header for log record !!?? */ + break; + head = ntohl(head); + if (offset + head > physend) + /* last record is incomplete */ + break; + fseek(f, offset, SEEK_SET); + if (paranoidLogRead(lcp, PM_MODE_FORW, f, &nrp) < 0) + /* this record is truncated, or bad, we lose! */ + break; + /* this one is ok, remember it as it may be the last one */ + found = 1; + if (rp != NULL) + pmFreeResult(rp); + rp = nrp; + } + if (found) + break; + + /* + * this probably means this volume contains no useful records, + * try the previous volume + */ + }/*for*/ + + if (f == lcp->l_mfp) + fseek(f, save, SEEK_SET); /* restore file pointer in current vol */ + else if (f != NULL) + /* temporary FILE * from _logpeek() */ + fclose(f); + + if (found) { + tp->tv_sec = (time_t)rp->timestamp.tv_sec; + tp->tv_usec = (int)rp->timestamp.tv_usec; + if (vol == lcp->l_maxvol) { + lcp->l_endtime.tv_sec = (__int32_t)rp->timestamp.tv_sec; + lcp->l_endtime.tv_usec = (__int32_t)rp->timestamp.tv_usec; + lcp->l_physend = physend; + } + sts = 0; + } + if (rp != NULL) { + /* + * rp is not NULL from found==1 path _or_ from error break + * after an initial paranoidLogRead() success + */ + pmFreeResult(rp); + } + + return sts; +} diff --git a/src/libpcp/src/loop.c b/src/libpcp/src/loop.c new file mode 100644 index 0000000..353e008 --- /dev/null +++ b/src/libpcp/src/loop.c @@ -0,0 +1,871 @@ +/* + * Copyright (c) 2004-2006 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifdef ASYNC_API +#include "pmapi.h" +#include "impl.h" +#include <assert.h> +#include <signal.h> +#include <sys/poll.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> + +#ifndef SIGMAX +/* I would use SIGRTMAX...except it's not constant on Linux */ +#define SIGMAX 64 +#endif + +typedef struct loop_input_s loop_input_t; +typedef struct loop_signal_s loop_signal_t; +typedef struct loop_timeout_s loop_timeout_t; +typedef struct loop_child_s loop_child_t; +typedef struct loop_idle_s loop_idle_t; +typedef struct loop_main_s loop_main_t; + +struct loop_input_s +{ + loop_input_t *next; + int tag; + int fd; + int flags; + int (*callback)(int fd, int flags, void *closure); + void *closure; + int priority; +}; + +struct loop_signal_s +{ + loop_signal_t *next; + int tag; + int (*callback)(int sig, void *closure); + void *closure; +}; + +struct loop_timeout_s +{ + loop_timeout_t *next; + int tag; + int delay; + int tout_msec; + int (*callback)(void *closure); + void *closure; +}; + +struct loop_child_s +{ + loop_child_t *next; + int tag; + pid_t pid; + int (*callback)(pid_t pid, int status, const struct rusage *, void *closure); + void *closure; +}; + +struct loop_idle_s +{ + loop_idle_t *next; + int tag; + int (*callback)(void *closure); + void *closure; +}; + +/* +* per-loop state, kept in an implicit stack +* by the main loop and subsidiary loops. +*/ +struct loop_main_s +{ + loop_main_t *next; + int running; + loop_timeout_t *current_timeout; + loop_child_t *current_child; +}; + +#define pmLoopDebug ((pmDebug & DBG_TRACE_LOOP) != 0) + +static int num_inputs; +static loop_input_t *input_list; +static struct pollfd *pfd; +static loop_input_t **inputs; +static int next_tag = 1; +static int inputs_dirty = 1; +static loop_signal_t *signals[SIGMAX]; +static volatile int signals_pending[SIGMAX]; +static struct timeval poll_start; +static loop_timeout_t *timeout_list; +static loop_main_t *main_stack; +static loop_child_t *child_list; +static int child_pending; +static int sigchld_tag; +static loop_idle_t *idle_list; + +static int +tv_sub(const struct timeval *a, const struct timeval *b) +{ + struct timeval t; + + t.tv_sec = a->tv_sec - b->tv_sec; + if (a->tv_usec >= b->tv_usec) { + t.tv_usec = a->tv_usec - b->tv_usec; + } else { + t.tv_sec--; + t.tv_usec = 1000000 + a->tv_usec - b->tv_usec; + } + return (t.tv_sec * 1000 + t.tv_usec / 1000); +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +int +pmLoopRegisterInput( + int fd, + int flags, + int (*callback)(int fd, int flags, void *closure), + void *closure, + int priority) +{ + loop_input_t *ii; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, + "loop_register_input: fd=%d flags=0x%x " + "callback=%p closure=%p priority=%d", + fd, flags, callback, closure, priority); + + if ((ii = (loop_input_t *)malloc(sizeof(loop_input_t))) == NULL) { + return (-ENOMEM); + } + + ii->tag = next_tag++; + ii->fd = fd; + ii->flags = flags; + ii->callback = callback; + ii->closure = closure; + + ii->next = input_list; + input_list = ii; + num_inputs++; + + inputs_dirty = 1; + + return ii->tag; +} + +void +pmLoopUnregisterInput(int tag) +{ + loop_input_t *ii, *previi; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, "loop_unregister_input: tag=%d", tag); + + for (ii = input_list, previi = NULL ; + ii != NULL && ii->tag != tag ; + previi = ii, ii = ii->next) + ; + if (ii == NULL) + return; + + if (previi == NULL) + input_list = ii->next; + else + previi->next = ii->next; + num_inputs--; + free(ii); + + inputs_dirty = 1; +} + +static int +loop_compare_by_priority(const void *av, const void *bv) +{ + const loop_input_t *a = *(const loop_input_t **)av; + const loop_input_t *b = *(const loop_input_t **)bv; + + return a->priority - b->priority; +} + +static int +loop_setup_inputs(void) +{ + loop_input_t *ii; + int i; + + if (num_inputs <= 0) { + return (0); + } + + if (inputs_dirty) { + pfd = (struct pollfd *)realloc(pfd, + num_inputs * sizeof(struct pollfd)); + inputs = (loop_input_t **)realloc(inputs, + num_inputs * sizeof(loop_input_t *)); + if ((pfd == NULL) || (inputs == NULL)) { + return (-ENOMEM); + } + inputs_dirty = 0; + } + + for (ii = input_list, i = 0; ii != NULL ; ii = ii->next, i++) + inputs[i] = ii; + qsort(inputs, num_inputs, sizeof(loop_input_t *), loop_compare_by_priority); + + for (i = 0 ; i < num_inputs ; i++) + { + ii = inputs[i]; + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, + "loop_setup_inputs: inputs[%d] = (fd=%d " + "callback=%p closure=%p)", + i, ii->fd, ii->callback, ii->closure); + + pfd[i].fd = ii->fd; + pfd[i].events = ii->flags; + pfd[i].revents = 0; + } + return (num_inputs); +} + +static void +loop_dispatch_inputs(void) +{ + int i; + loop_input_t *ii; + int n = num_inputs; /* because num_inputs can change inside the loop */ + + for (i = 0 ; i < n; i++) { + ii = inputs[i]; + + if ((pfd[i].revents & POLLNVAL)) { + /* invalid fd... */ + pmLoopUnregisterInput(ii->tag); + continue; + } + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, + "loop_dispatch_inputs: pfd[%i]=(fd=%d " + "events=0x%x revents=0x%x)", + i, pfd[i].fd, pfd[i].events, pfd[i].revents); + + if ((pfd[i].revents & (ii->flags | POLLHUP | POLLERR))) { + if ((*ii->callback)(ii->fd, pfd[i].revents, ii->closure)) { + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, + "loop_dispatch_inputs: deregistering " + "input with tag %d\n", + ii->tag); + + pmLoopUnregisterInput(ii->tag); + } + } + } +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +static void +loop_sig_handler(int sig) +{ + signals_pending[sig] = 1; +} + +int +pmLoopRegisterSignal( + int sig, + int (*callback)(int sig, void *closure), + void *closure) +{ + loop_signal_t *ss; + int doinstall; + + if (sig < 0 || sig >= SIGMAX) + return -EINVAL; + + if ((ss = (loop_signal_t *)malloc(sizeof(loop_signal_t))) == NULL) + return -ENOMEM; + + ss->tag = next_tag++; + ss->callback = callback; + ss->closure = closure; + + doinstall = (signals[sig] == NULL); + ss->next = signals[sig]; + signals[sig] = ss; + + if (doinstall) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = 0; + sa.sa_handler = loop_sig_handler; + if (sigaction(sig, &sa, NULL) < 0) { + int ee = oserror(); + + __pmNotifyErr(LOG_WARNING, + "sigaction failed - %s", osstrerror()); + return -ee; + } + } + + return ss->tag; +} + +void +pmLoopUnregisterSignal(int tag) +{ + int sig; + loop_signal_t *ss, *prevss; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_unregister_signal: tag=%d", tag); + + for (sig = 0 ; sig < SIGMAX ; sig++) + { + for (ss = signals[sig], prevss = NULL ; + ss != NULL && ss->tag != tag ; + prevss = ss, ss = ss->next) + ; + if (ss == NULL) + continue; + + if (prevss == NULL) + signals[sig] = ss->next; + else + prevss->next = ss->next; + free(ss); + + if (signals[sig] == NULL) + { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + if (sigaction(sig, &sa, NULL) < 0) { + __pmNotifyErr(LOG_WARNING, + "sigaction failed - %s", osstrerror()); + return; + } + } + break; + } +} + +static void +loop_dispatch_signals(void) +{ + int sig; + loop_signal_t *ss, *nextss; + + for (sig = 0 ; sig < SIGMAX ; sig++) { + if (signals_pending[sig]) { + signals_pending[sig] = 0; + + for (ss = signals[sig]; ss != NULL; ss = nextss) { + nextss = ss->next; + if ((*ss->callback)(sig, ss->closure)) { + pmLoopUnregisterSignal(ss->tag); + } + } + } + } +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +/* + * A few words about the timeout data structure. This is the classic + * (i.e. simple and not scalable) data structure for multiplexing + * multiple virtual timeouts onto one real one, as described in any + * OS internals book. + * + * A singly-linked list of loop_timeout_t structures is kept, the head + * is pointed to by timeout_list and threaded by tt->next. Each entry + * stores in tt->delay a timeout in millisecons which expresses when + * the entry is scheduled to fire as the elapsed time after the + * previous entry is scheduled to fire. Thus the delay field in the + * head of the list is logically the amount of time from now until the + * first timeout is scheduled, and so is used directly as the timeout + * for poll(). + * + * From this data structure we can derive the insert algorithm. + * The algorithm walks down the list from the head, keeping a running + * relative delay from the last entry iterated over by subtracting + * from it the tt->delay of each entry skipped. When iterating to the + * next entry would cause this running relative delay to go negative, + * we know we've arrived at the right place to insert the new entry. + * Note that the check is for negative, not negative or zero: this + * ensures that multiple entries for the same scheduled time are + * stored in the same order that they were inserted, which is the + * most intuitive behaviour for the application programmer. + * + * The remove algorithm is simpler, it just scans the list trying + * to match the unique tag. + * + * There are some more hairy parts as well. It is possible for poll() + * to return before the timeout expires, for example if input becomes + * available on a file descriptor. The poll() call does not give any + * indication of how much time remained until the timeout would have + * fired (on some operating systems, the select(2) does this). So a + * sample of the system time is taken before and after every call + * to poll(), the elapsed time in the poll() is calculated, and the + * tt->delay in the head of the timeout_list is adjusted to account + * for the elapsed time. This is necessary to avoid restarting the + * poll() with too long a timeout. An example of the resulting bug + * would be a timeout registered for 10 seconds from now, but every + * 1 second input becomes available on some file descriptor; if the + * poll() timeout were not adjusted the timeout callback would never + * be called and would always be 10 seconds in the future. + */ + +static void +loop_dump_timeouts(void) +{ + loop_timeout_t *tt; + + __pmNotifyErr(LOG_DEBUG,"timeout_list {"); + for (tt = timeout_list ; tt != NULL ; tt = tt->next) { + __pmNotifyErr(LOG_DEBUG," %dms %p %p", + tt->delay, tt->callback, tt->closure); + } + __pmNotifyErr(LOG_DEBUG,"}"); +} + +static void +loop_insert_timeout(loop_timeout_t *tt) +{ + loop_timeout_t *next, *prev; + int delay = tt->delay; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, "loop_insert_timeout: %d %p %p", + tt->delay, tt->callback, tt->closure); + + for (next = timeout_list, prev = NULL ; + (next != NULL) && ((delay - next->delay) >= 0); + prev = next, next = next->next) + delay -= next->delay; + + if (prev == NULL) + timeout_list = tt; + else + prev->next = tt; + tt->next = next; + + if (next != NULL) + next->delay -= delay; + + tt->delay = delay; + + if (pmLoopDebug) + loop_dump_timeouts(); +} + +int +pmLoopRegisterTimeout( + int tout_msec, + int (*callback)(void *closure), + void *closure) +{ + loop_timeout_t *tt; + + if (tout_msec < 0) { + return (-EINVAL); + } + + if ((tt = (loop_timeout_t *)malloc(sizeof(loop_timeout_t))) == NULL) { + return (-ENOMEM); + } + + tt->tag = next_tag++; + tt->delay = tt->tout_msec = tout_msec; + tt->callback = callback; + tt->closure = closure; + + loop_insert_timeout(tt); + + return tt->tag; +} + +void +pmLoopUnregisterTimeout(int tag) +{ + loop_main_t *lm; + loop_timeout_t *tt, *prevtt; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_unregister_timeout: tag=%d", tag); + + /* + * Because the timeout object is detached from the + * global timeout list while its being dispatched + * (and yes there are good reasons for this), we + * have to search for the timeout tag in all the + * currently stacked loops. + */ + for (lm = main_stack ; lm != NULL ; lm = lm->next) + { + if (lm->current_timeout != NULL && + lm->current_timeout->tag == tag) + { + free(lm->current_timeout); + lm->current_timeout = NULL; + return; + } + } + + for (tt = timeout_list, prevtt = NULL; + tt != NULL && tt->tag != tag ; + prevtt = tt, tt = tt->next) + ; + + if (tt == NULL) + return; + + if (prevtt == NULL) + timeout_list = tt->next; + else + prevtt->next = tt->next; + + if (tt->next != NULL) + tt->next->delay += tt->delay; + + free(tt); +} + +/* returns milliseconds */ +static int +loop_setup_timeouts(void) +{ + __pmtimevalNow(&poll_start); + + if (idle_list != NULL) + return 0; /* poll() returns immediately */ + if (timeout_list == NULL) + return -1; /* poll() waits forever */ + return (timeout_list->delay); +} + +static void +loop_dispatch_timeouts(void) +{ + if (timeout_list == NULL) + return; + + timeout_list->delay = 0; + while (timeout_list != NULL && (timeout_list->delay == 0)) { + loop_main_t *lm = main_stack; + int isdone; + assert(lm != NULL); + assert(lm->current_timeout == NULL); + lm->current_timeout = timeout_list; + timeout_list = timeout_list->next; + + isdone = (*lm->current_timeout->callback)(lm->current_timeout->closure); + + assert(lm == main_stack); + + if (!isdone && (lm->current_timeout != NULL)) { + lm->current_timeout->delay = lm->current_timeout->tout_msec; + loop_insert_timeout(lm->current_timeout); + } else { + pmLoopUnregisterTimeout(lm->current_timeout->tag); + } + lm->current_timeout = NULL; + } +} + +static void +loop_adjust_timeout(void) +{ + struct timeval now; + int spent; + + if (timeout_list == NULL) + return; + + __pmtimevalNow(&now); + spent = tv_sub(&now, &poll_start); + if (spent >= timeout_list->delay) { + timeout_list->delay = 0; + loop_dispatch_timeouts(); + } else { + timeout_list->delay -= spent; + } +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +static int +loop_sigchld_handler(int sig, void *closure) +{ + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_sigchld_handler"); + child_pending = 1; + return (0); +} + + +int +pmLoopRegisterChild( + pid_t pid, + int (*callback)(pid_t pid, int status, const struct rusage *, void *closure), + void *closure) +{ + loop_child_t *cc; + int doinstall; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_register_child: pid=%" FMT_PID " callback=%p closure=%p", + pid, callback, closure); + + if (pid <= (pid_t)0) + return -1; + if ((cc = (loop_child_t *)malloc(sizeof(loop_child_t))) == NULL) { + return (-ENOMEM); + } + + cc->tag = next_tag++; + cc->pid = pid; + cc->callback = callback; + cc->closure = closure; + + doinstall = (child_list == NULL); + cc->next = child_list; + child_list = cc; + + if (doinstall) + sigchld_tag = pmLoopRegisterSignal(SIGCHLD, + loop_sigchld_handler, + NULL); + + return cc->tag; +} + +void +pmLoopUnregisterChild(int tag) +{ + loop_main_t *lm; + loop_child_t *cc, *prevcc; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_unregister_child: tag=%d", tag); + + for (cc = child_list, prevcc = NULL ; + cc != NULL && cc->tag != tag ; + prevcc = cc, cc = cc->next) + ; + if (cc == NULL) + return; + + if (prevcc == NULL) + child_list = cc->next; + else + prevcc->next = cc->next; + + for (lm = main_stack ; lm != NULL ; lm = lm->next) + { + if (cc == lm->current_child) + lm->current_child = NULL; + } + free(cc); + + if (child_list == NULL) + { + pmLoopUnregisterSignal(sigchld_tag); + sigchld_tag = -1; + } +} + +static void +loop_dispatch_children(void) +{ + loop_child_t *cc, *nextcc; + int status; + int r; + struct rusage rusage; + + memset (&rusage, 0, sizeof(rusage)); + + child_pending = 0; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children"); + + /* We don't support callback on process groups. Sorry */ + while ((r = wait3(&status, WNOHANG, &rusage)) > 0) + { + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children: r=%d", r); + + for (cc = child_list ; cc != NULL ; cc = nextcc) + { + nextcc = cc->next; + + if (r == (int)cc->pid) { + loop_main_t *lm = main_stack; + int isdone; + + assert(lm != NULL); + lm->current_child = cc; + isdone = (*cc->callback)((pid_t)r, status, &rusage, + cc->closure); + + if (isdone || + (lm->current_child != NULL && + (WIFEXITED(status) || WIFSIGNALED(status)))) { + /* + * This pid won't be coming back or we were told + * that callback has fulfilled its purpose, so + * unregister. + */ + pmLoopUnregisterChild(cc->tag); + } + assert(lm == main_stack); + assert(lm->current_child == NULL || lm->current_child == cc); + lm->current_child = NULL; + break; + } + } + } +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +int +pmLoopRegisterIdle( + int (*callback)(void *closure), + void *closure) +{ + loop_idle_t *ii; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG,"loop_register_idle: callback=%p closure=%p", + callback, closure); + + if ((ii = (loop_idle_t *)malloc(sizeof(loop_idle_t))) == NULL) { + return (-ENOMEM); + } + + ii->tag = next_tag++; + ii->callback = callback; + ii->closure = closure; + + ii->next = idle_list; + idle_list = ii; + + return ii->tag; +} + +void +pmLoopUnregisterIdle(int tag) +{ + loop_idle_t *ii, *previi; + + if (pmLoopDebug) + __pmNotifyErr(LOG_DEBUG, "loop_unregister_idle: tag=%d", tag); + + for (ii = idle_list, previi = NULL ; + ii != NULL && ii->tag != tag ; + previi = ii, ii = ii->next) + ; + if (ii == NULL) + return; + + if (previi == NULL) + idle_list = ii->next; + else + previi->next = ii->next; + + free(ii); +} + +static void +loop_dispatch_idle(void) +{ + loop_idle_t *ii, *nextii; + + for (ii = idle_list ; ii != NULL ; ii = nextii) { + nextii = ii->next; + + if ((*ii->callback)(ii->closure)) { + pmLoopUnregisterIdle(ii->tag); + } + } +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ + +void +pmLoopStop(void) +{ + if (main_stack != NULL) + main_stack->running = 0; +} + +int +pmLoopMain(void) +{ + int r; + int timeout; + loop_main_t lmain; + + memset(&lmain, 0, sizeof(lmain)); + lmain.next = main_stack; + main_stack = &lmain; + + lmain.running = 1; + while (lmain.running) { + int ee; + + if ((ee = loop_setup_inputs()) < 0) + return ee; + timeout = loop_setup_timeouts(); + loop_dispatch_idle(); + + if ((ee == 0) && (timeout == -1) && (idle_list == NULL)) + return 0; + + r = poll(pfd, num_inputs, timeout); + if (r < 0) { + if (oserror() == EINTR) { + loop_dispatch_signals(); + if (child_pending) + loop_dispatch_children(); + continue; + } + __pmNotifyErr(LOG_ERR, "pmLoopMain: poll failed - %s", + osstrerror()); + break; + } else if (r == 0) { + if (timeout > 0) + loop_dispatch_timeouts(); + else + loop_adjust_timeout(); + } else { + loop_dispatch_inputs(); + loop_adjust_timeout(); + } + } + + assert(main_stack == &lmain); + assert(lmain.current_child == NULL); + assert(lmain.current_timeout == NULL); + main_stack = lmain.next; + return 0; +} + +/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ +#endif /*ASYNC_API*/ diff --git a/src/libpcp/src/optfetch.c b/src/libpcp/src/optfetch.c new file mode 100644 index 0000000..f2ca9ae --- /dev/null +++ b/src/libpcp/src/optfetch.c @@ -0,0 +1,709 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Generic routines to provide the "optimized" pmFetch bundling + * services ... the optimization is driven by the crude heuristics + * weights defined in optcost below. + * + * Thread-safe notes + * + * lrand48() is not thread-safe, but we don't really care here. + */ + +/* if DESPERATE, we need DEBUG */ +#if defined(DESPERATE) && !defined(PCP_DEBUG) +#define DEBUG +#endif + +#include "pmapi.h" +#include "impl.h" +#include <assert.h> + +/* + * elements of optcost are + * + * c_pmid cost per PMD for PMIDs in a fetch + * c_indom cost per PMD for indoms in a fetch + * c_fetch cost of a new fetch group (should be less than + * c_indomsize * c_xtrainst, else all fetches will go + * into a single group, unless the scope is global) + * c_indomsize expected numer of instances for an indom + * c_xtrainst cost of retrieving an unwanted metric inst + * c_scope cost opt., 0 for incremental, 1 for global + */ + +/* default costs */ +static optcost_t optcost = { 4, 1, 15, 10, 2, 0 }; + +static int +addpmid(fetchctl_t *fp, pmID pmid) +{ + int i; + int j; + + for (i = 0; i < fp->f_numpmid; i++) { + if (pmid == fp->f_pmidlist[i]) + return 0; + if (pmid > fp->f_pmidlist[i]) + break; + } + fp->f_numpmid++; + fp->f_pmidlist = (pmID *)realloc(fp->f_pmidlist, fp->f_numpmid*sizeof(pmID)); + if (fp->f_pmidlist == NULL) { + __pmNoMem("addpmid", fp->f_numpmid*sizeof(pmID), PM_FATAL_ERR); + } + + for (j = fp->f_numpmid-1; j > i; j--) + fp->f_pmidlist[j] = fp->f_pmidlist[j-1]; + fp->f_pmidlist[i] = pmid; + + return 1; +} + +static int +addinst(int *numinst, int **instlist, optreq_t *new) +{ + int i; + int j; + int k; + int numi; + int *ilist; + int match; + + if (*numinst == 0) + return 0; + if (new->r_numinst == 0) { + *numinst = 0; + if (*instlist != NULL) { + free(*instlist); + *instlist = NULL; + } + return 1; + } + numi = *numinst; + if (numi == -1) + numi = 0; + + ilist = (int *)realloc(*instlist, (numi + new->r_numinst)*sizeof(int)); + if (ilist == NULL) { + __pmNoMem("addinst.up", (numi + new->r_numinst)*sizeof(int), PM_FATAL_ERR); + } + + for (j = 0; j < new->r_numinst; j++) { + match = 0; + for (i = 0; i < numi; i++) { + if (ilist[i] == new->r_instlist[j]) { + match = 1; + break; + } + if (ilist[i] > new->r_instlist[j]) + break; + } + if (match) + continue; + for (k = numi; k > i; k--) + ilist[k] = ilist[k-1]; + ilist[i] = new->r_instlist[j]; + numi++; + } + + ilist = (int *)realloc(ilist, numi*sizeof(int)); + if (ilist == NULL) { + __pmNoMem("addinst.down", numi*sizeof(int), PM_FATAL_ERR); + } + + *numinst = numi; + *instlist = ilist; + + return 1; +} + +/* + * if we retrieve the instances identified by numa and lista[], how much larger + * is this than the set of instances identified by numb and listb[]? + */ +static int +missinst(int numa, int *lista, int numb, int *listb) +{ + int xtra = 0; + int i; + int j; + + PM_LOCK(__pmLock_libpcp); + /* count in lista[] but _not_ in listb[] */ + if (numa == 0) { + /* special case for all instances in lista[] */ + if (numb != 0 && numb < optcost.c_indomsize) + xtra += optcost.c_indomsize - numb; + } + else { + /* not all instances for both lista[] and listb[] */ + i = 0; + j = 0; + while (i < numa && j < numb) { + if (lista[i] == listb[j]) { + i++; + j++; + } + else if (lista[i] < listb[j]) { + i++; + xtra++; + } + else { + j++; + xtra++; + } + } + xtra += (numa - i) + (numb - j); + } + + PM_UNLOCK(__pmLock_libpcp); + return xtra; +} + +static void +redoinst(fetchctl_t *fp) +{ + indomctl_t *idp; + pmidctl_t *pmp; + optreq_t *rqp; + + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + idp->i_numinst = -1; + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + pmp->p_numinst = -1; + for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) { + addinst(&pmp->p_numinst, &pmp->p_instlist, rqp); + addinst(&idp->i_numinst, &idp->i_instlist, rqp); + } + } + } +} + +static void +redopmid(fetchctl_t *fp) +{ + indomctl_t *idp; + pmidctl_t *pmp; + + fp->f_numpmid = 0; + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + addpmid(fp, pmp->p_pmid); + } + } +} + +static int +optCost(fetchctl_t *fp) +{ + indomctl_t *idp; + indomctl_t *xidp; + pmidctl_t *pmp; + pmidctl_t *xpmp; + __pmID_int *pmidp; + __pmInDom_int *indomp; + int pmd; + int cost = 0; + int done; + + PM_LOCK(__pmLock_libpcp); + /* + * cost per PMD for the pmids in this fetch + */ + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + pmidp = (__pmID_int *)&pmp->p_pmid; + pmd = pmidp->domain; + done = 0; + for (xidp = fp->f_idp; xidp != NULL; xidp = xidp->i_next) { + for (xpmp = xidp->i_pmp; xpmp != NULL; xpmp = xpmp->p_next) { + pmidp = (__pmID_int *)&xpmp->p_pmid; + if (xpmp != pmp && pmd == pmidp->domain) { + done = 1; + break; + } + if (xpmp == pmp) { + cost += optcost.c_pmid; + done = 1; + break; + } + } + if (done || xidp == idp) + break; + } + } + } + + /* + * cost per PMD for the indoms in this fetch + */ + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + indomp = (__pmInDom_int *)&idp->i_indom; + pmd = indomp->domain; + for (xidp = fp->f_idp; xidp != idp; xidp = xidp->i_next) { + indomp = (__pmInDom_int *)&xidp->i_indom; + if (pmd == indomp->domain) + break; + } + if (xidp == idp) + cost += optcost.c_indom; + } + + /* + * cost for extra retrievals due to multiple metrics over the same indom + */ + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + cost += optcost.c_xtrainst * missinst(idp->i_numinst, idp->i_instlist, pmp->p_numinst, pmp->p_instlist); + } + } + + PM_UNLOCK(__pmLock_libpcp); + return cost; +} + +#ifdef PCP_DEBUG +static char * +statestr(int state, char *sbuf) +{ + sbuf[0] = '\0'; + if (state & OPT_STATE_NEW) strcat(sbuf, "NEW "); + if (state & OPT_STATE_PMID) strcat(sbuf, "PMID "); + if (state & OPT_STATE_PROFILE) strcat(sbuf, "PROFILE "); + if (state & OPT_STATE_XREQ) strcat(sbuf, "XREQ "); + if (state & OPT_STATE_XPMID) strcat(sbuf, "XPMID "); + if (state & OPT_STATE_XINDOM) strcat(sbuf, "XINDOM "); + if (state & OPT_STATE_XFETCH) strcat(sbuf, "XFETCH "); + if (state & OPT_STATE_XPROFILE) strcat(sbuf, "XPROFILE "); + + return sbuf; +} + +static void +dumplist(FILE *f, int style, char *tag, int numi, int *ilist) +{ + char strbuf[20]; + + fprintf(f, "%s: [%d]", tag, numi); + if (ilist == NULL) + fprintf(f, " (nil)\n"); + else { + int i; + for (i = 0; i < numi; i++) { + if (style == 1) + fprintf(f, " %s", pmIDStr_r((pmID)ilist[i], strbuf, sizeof(strbuf))); + else + fprintf(f, " %d", ilist[i]); + } + fputc('\n', f); + } +} + +static void +___pmOptFetchDump(FILE *f, const fetchctl_t *fp) +{ + indomctl_t *idp; + pmidctl_t *pmp; + optreq_t *rqp; + char strbuf[100]; + + fflush(stderr); + fflush(stdout); + fprintf(f, "Dump optfetch structures from " PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p\n", fp, fp->f_next); + fprintf(f, "Fetch Control @ " PRINTF_P_PFX "%p: cost=%d state=%s\n", fp, fp->f_cost, statestr(fp->f_state, strbuf)); + dumplist(f, 1, "PMIDs", fp->f_numpmid, (int *)fp->f_pmidlist); + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + fprintf(f, " InDom %s Control @ " PRINTF_P_PFX "%p:\n", pmInDomStr_r(idp->i_indom, strbuf, sizeof(strbuf)), idp); + dumplist(f, 0, " instances", idp->i_numinst, idp->i_instlist); + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + fprintf(f, " PMID %s Control @ " PRINTF_P_PFX "%p:\n", pmIDStr_r(pmp->p_pmid, strbuf, sizeof(strbuf)), pmp); + dumplist(f, 0, " instances", pmp->p_numinst, pmp->p_instlist); + for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) { + fprintf(f, " Request @ " PRINTF_P_PFX "%p:\n", rqp); + dumplist(f, 0, " instances", rqp->r_numinst, rqp->r_instlist); + } + } + } + fputc('\n', f); + fflush(f); +} + +void +__pmOptFetchDump(FILE *f, const fetchctl_t *root) +{ + const fetchctl_t *fp; + + for (fp = root; fp != NULL; fp = fp->f_next) + ___pmOptFetchDump(f, fp); +} +#endif /* DEBUG */ + +/* + * add a new request into a group of fetches ... + * only failure is from calloc() and this is fatal + */ +void +__pmOptFetchAdd(fetchctl_t **root, optreq_t *new) +{ + fetchctl_t *fp; + fetchctl_t *tfp; + indomctl_t *idp; + pmidctl_t *pmp = NULL; + int mincost; + int change; + pmInDom indom = new->r_desc->indom; + pmID pmid = new->r_desc->pmid; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + /* add new fetch as first option ... will be reclaimed later if not used */ + if ((fp = (fetchctl_t *)calloc(1, sizeof(fetchctl_t))) == NULL) { + __pmNoMem("optAddFetch.fetch", sizeof(fetchctl_t), PM_FATAL_ERR); + } + fp->f_next = *root; + *root = fp; + + for (fp = *root; fp != NULL; fp = fp->f_next) { + fp->f_cost = optCost(fp); + + change = OPT_STATE_XINDOM | OPT_STATE_XPMID | OPT_STATE_XREQ; + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + if (idp->i_indom != indom) + continue; + change = OPT_STATE_XPMID | OPT_STATE_XREQ; + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + if (pmp->p_pmid == pmid) { + change = OPT_STATE_XREQ; + break; + } + } + break; + } + if (fp == *root) + change |= OPT_STATE_XFETCH; + fp->f_state = (fp->f_state & OPT_STATE_UMASK) | change; + + if (change & OPT_STATE_XINDOM) { + if ((idp = (indomctl_t *)calloc(1, sizeof(indomctl_t))) == NULL) { + __pmNoMem("optAddFetch.indomctl", sizeof(indomctl_t), PM_FATAL_ERR); + } + idp->i_indom = indom; + idp->i_next = fp->f_idp; + idp->i_numinst = -1; + fp->f_idp = idp; + } + if (change & OPT_STATE_XPMID) { + if ((pmp = (pmidctl_t *)calloc(1, sizeof(pmidctl_t))) == NULL) { + __pmNoMem("optAddFetch.pmidctl", sizeof(pmidctl_t), PM_FATAL_ERR); + } + pmp->p_next = idp->i_pmp; + idp->i_pmp = pmp; + pmp->p_pmid = pmid; + pmp->p_numinst = -1; + } + addinst(&pmp->p_numinst, &pmp->p_instlist, new); + if (addinst(&idp->i_numinst, &idp->i_instlist, new)) + fp->f_state |= OPT_STATE_XPROFILE; + + fp->f_newcost = optCost(fp); + if (fp == *root) + fp->f_newcost += optcost.c_fetch; +#ifdef DESPERATE + if (pmDebug & DBG_TRACE_OPTFETCH) { + char strbuf[100]; + fprintf(stderr, "optFetch: cost="); + if (fp->f_cost == OPT_COST_INFINITY) + fprintf(stderr, "INFINITY"); + else + fprintf(stderr, "%d", fp->f_cost); + fprintf(stderr, ", newcost="); + if (fp->f_newcost == OPT_COST_INFINITY) + fprintf(stderr, "INFINITY"); + else + fprintf(stderr, "%d", fp->f_newcost); + fprintf(stderr, ", for %s @ grp 0x%x,", + pmIDStr_r(pmid, strbuf, sizeof(strbuf)), fp); + fprintf(stderr, " state %s\n", + statestr(fp->f_state, strbuf)); + } +#endif + } + + tfp = NULL; + mincost = OPT_COST_INFINITY; + for (fp = *root; fp != NULL; fp = fp->f_next) { + int cost; + if (optcost.c_scope) + /* global */ + cost = fp->f_newcost; + else + /* local */ + cost = fp->f_newcost - fp->f_cost; + if (cost < mincost) { + mincost = cost; + tfp = fp; + } + } +#ifdef DESPERATE + if (pmDebug & DBG_TRACE_OPTFETCH) { + char strbuf[100]; + fprintf(stderr, "optFetch: chose %s cost=%d for %s @ grp 0x%x,", + optcost.c_scope ? "global" : "incremental", + mincost, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), tfp); + fprintf(stderr, " change %s\n", statestr(tfp->f_state, strbuf)); + } +#endif + + /* + * Warning! Traversal of the list is a bit tricky, because the + * current element (fp) may be freed, making fp->fp_next + * a bad idea at the end of each iteration ... + */ + for (fp = *root; fp != NULL; ) { + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + if (idp->i_indom != indom) + continue; + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + if (pmp->p_pmid == pmid) + break; + } + break; + } + assert(idp != NULL && pmp != NULL); + if (fp == tfp) { + /* + * The chosen one ... + */ + if (fp->f_state & OPT_STATE_XFETCH) + fp->f_state |= OPT_STATE_NEW; + if (addpmid(tfp, pmid)) + fp->f_state |= OPT_STATE_PMID; + if (fp->f_state & OPT_STATE_XPROFILE) + fp->f_state |= OPT_STATE_PROFILE; + new->r_next = pmp->p_rqp; + new->r_fetch = tfp; + pmp->p_rqp = new; + fp->f_cost = fp->f_newcost; + fp->f_state &= OPT_STATE_UMASK; + fp = fp->f_next; + } + else { + /* + * Otherwise, need to undo changes made to data structures. + * Note. if new structures added, they will be at the start of + * their respective lists. + */ + if (fp->f_state & OPT_STATE_XPMID) { + idp->i_pmp = pmp->p_next; + free(pmp); + } + if (fp->f_state & OPT_STATE_XINDOM) { + fp->f_idp = idp->i_next; + free(idp); + } + if (fp->f_state & OPT_STATE_XFETCH) { + *root = fp->f_next; + free(fp); + fp = *root; + } + else { + redoinst(fp); + fp->f_state &= OPT_STATE_UMASK; + fp = fp->f_next; + } + } + } + + PM_UNLOCK(__pmLock_libpcp); + return; +} + +/* + * remove a request from a group of fetches + */ +int +__pmOptFetchDel(fetchctl_t **root, optreq_t *new) +{ + fetchctl_t *fp; + fetchctl_t *p_fp; + indomctl_t *idp; + indomctl_t *p_idp; + pmidctl_t *pmp; + pmidctl_t *p_pmp; + optreq_t *rqp; + optreq_t *p_rqp; + + p_fp = NULL; + for (fp = *root; fp != NULL; fp = fp->f_next) { + p_idp = NULL; + for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) { + p_pmp = NULL; + for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) { + p_rqp = NULL; + for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) { + if (rqp == new) { + if (p_rqp != NULL) + /* not first request for this metric */ + p_rqp->r_next = rqp->r_next; + else if (rqp->r_next != NULL) + /* first of several requests for this metric */ + pmp->p_rqp = rqp->r_next; + else { + /* only request for this metric */ + if (p_pmp != NULL) + /* not first metric for this indom */ + p_pmp->p_next = pmp->p_next; + else if (pmp->p_next != NULL) + /* first of several metrics for this indom */ + idp->i_pmp = pmp->p_next; + else { + /* only metric for this indom */ + if (p_idp != NULL) + /* not first indom for this fetch */ + p_idp->i_next = idp->i_next; + else if (idp->i_next != NULL) + /* first of several idoms for this fetch */ + fp->f_idp = idp->i_next; + else { + /* only indom for this fetch */ + if (p_fp != NULL) + /* not first fetch for the group */ + p_fp->f_next = fp->f_next; + else + /* first of fetch for the group */ + *root = fp->f_next; + free(fp); + fp = NULL; + } + free(idp); + } + free(pmp); + } + /* data structures repaired, now redo lists */ + if (fp != NULL) { + redoinst(fp); + redopmid(fp); + fp->f_state = OPT_STATE_PMID | OPT_STATE_PROFILE; + fp->f_cost = optCost(fp); + } + return 0; + } + p_rqp = rqp; + } + p_pmp = pmp; + } + p_idp = idp; + } + p_fp = fp; + } + return -1; +} + +void +__pmOptFetchRedo(fetchctl_t **root) +{ + fetchctl_t *newroot = NULL; + fetchctl_t *fp; + fetchctl_t *t_fp; + indomctl_t *idp; + indomctl_t *t_idp; + pmidctl_t *pmp; + pmidctl_t *t_pmp; + optreq_t *rqp; + optreq_t *t_rqp; + optreq_t *p_rqp; + optreq_t *rlist; + int numreq; + + rlist = NULL; + numreq = 0; + /* + * collect all of the requests first + */ + for (fp = *root; fp != NULL; ) { + for (idp = fp->f_idp; idp != NULL; ) { + for (pmp = idp->i_pmp; pmp != NULL; ) { + for (rqp = pmp->p_rqp; rqp != NULL; ) { + t_rqp = rqp->r_next; + rqp->r_next = rlist; + rlist = rqp; + rqp = t_rqp; + numreq++; + } + t_pmp = pmp; + pmp = pmp->p_next; + free(t_pmp); + } + t_idp = idp; + idp = idp->i_next; + free(t_idp); + } + t_fp = fp; + fp = fp->f_next; + free(t_fp); + } + + if (numreq) { + /* something to do, randomly cut and splice the list of requests */ + numreq = (int)lrand48() % numreq; + t_rqp = rlist; + p_rqp = NULL; + for (rqp = rlist; rqp != NULL; rqp = rqp->r_next) { + if (numreq == 0) + t_rqp = rqp; + numreq--; + p_rqp = rqp; + } + if (p_rqp == NULL || t_rqp == rlist) + /* do nothing */ + ; + else { + /* p_rqp is end of list, t_rqp is new head */ + p_rqp->r_next = rlist; + rlist = t_rqp->r_next; + t_rqp->r_next = NULL; + } + + /* now add them all back again */ + for (rqp = rlist; rqp != NULL; ) { + /* warning, rqp->r_next may change */ + t_rqp = rqp->r_next; + __pmOptFetchAdd(&newroot, rqp); + rqp = t_rqp; + } + } + + *root = newroot; + return; +} + +void +__pmOptFetchGetParams(optcost_t *ocp) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + *ocp = optcost; + PM_UNLOCK(__pmLock_libpcp); + return; +} + +void +__pmOptFetchPutParams(optcost_t *ocp) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + optcost = *ocp; + PM_UNLOCK(__pmLock_libpcp); + return; +} diff --git a/src/libpcp/src/p_auth.c b/src/libpcp/src/p_auth.c new file mode 100644 index 0000000..d2077e7 --- /dev/null +++ b/src/libpcp/src/p_auth.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 Red Hat. + * + * 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include <ctype.h> + +/* + * PDU for per-user authentication (PDU_AUTH) + */ +typedef struct { + __pmPDUHdr hdr; + int attr; /* PCP_ATTR code (optional, can be zero) */ + char value[sizeof(int)]; +} auth_t; + +int +__pmSendAuth(int fd, int from, int attr, const char *value, int length) +{ + size_t need; + auth_t *pp; + int i; + int sts; + + if (length < 0 || length >= LIMIT_AUTH_PDU) + return PM_ERR_IPC; + + need = (sizeof(*pp) - sizeof(pp->value)) + length; + if ((pp = (auth_t *)__pmFindPDUBuf((int)need)) == NULL) + return -oserror(); + pp->hdr.len = (int)need; + pp->hdr.type = PDU_AUTH; + pp->hdr.from = from; + pp->attr = htonl(attr); + memcpy(&pp->value, value, length); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[LIMIT_AUTH_PDU]; + for (i = 0; i < length; i++) + buffer[i] = isprint((int)value[i]) ? value[i] : '.'; + buffer[length] = buffer[LIMIT_AUTH_PDU-1] = '\0'; + if (attr) + fprintf(stderr, "__pmSendAuth [len=%d]: attr=%x value=\"%s\"\n", + length, attr, buffer); + else + fprintf(stderr, "__pmSendAuth [len=%d]: payload=\"%s\"\n", + length, buffer); + } +#endif + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeAuth(__pmPDU *pdubuf, int *attr, char **value, int *vlen) +{ + auth_t *pp; + int i; + int pdulen; + int length; + + pp = (auth_t *)pdubuf; + pdulen = pp->hdr.len; /* ntohl() converted already in __pmGetPDU() */ + length = pdulen - (sizeof(*pp) - sizeof(pp->value)); + if (length < 0 || length >= LIMIT_AUTH_PDU) + return PM_ERR_IPC; + + *attr = ntohl(pp->attr); + *value = length ? pp->value : NULL; + *vlen = length; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[LIMIT_AUTH_PDU]; + for (i = 0; i < length; i++) + buffer[i] = isprint((int)pp->value[i]) ? pp->value[i] : '.'; + buffer[length] = buffer[LIMIT_AUTH_PDU-1] = '\0'; + if (*attr) + fprintf(stderr, "__pmDecodeAuth [len=%d]: attr=%x value=\"%s\"\n", + length, *attr, buffer); + else + fprintf(stderr, "__pmDecodeAuth [len=%d]: payload=\"%s\"\n", + length, buffer); + } +#endif + + return 0; +} diff --git a/src/libpcp/src/p_creds.c b/src/libpcp/src/p_creds.c new file mode 100644 index 0000000..98aa996 --- /dev/null +++ b/src/libpcp/src/p_creds.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +#define LIMIT_CREDS 1024 + +/* + * PDU for process credentials (PDU_CREDS) + */ +typedef struct { + __pmPDUHdr hdr; + int numcreds; + __pmCred credlist[1]; +} creds_t; + +int +__pmSendCreds(int fd, int from, int credcount, const __pmCred *credlist) +{ + size_t need; + creds_t *pp; + int i; + int sts; + + if (credcount <= 0 || credcount > LIMIT_CREDS || credlist == NULL) + return PM_ERR_IPC; + + need = sizeof(creds_t) + ((credcount-1) * sizeof(__pmCred)); + if ((pp = (creds_t *)__pmFindPDUBuf((int)need)) == NULL) + return -oserror(); + pp->hdr.len = (int)need; + pp->hdr.type = PDU_CREDS; + pp->hdr.from = from; + pp->numcreds = htonl(credcount); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + for (i = 0; i < credcount; i++) + fprintf(stderr, "__pmSendCreds: #%d = %x\n", i, *(unsigned int*)&(credlist[i])); +#endif + /* swab and fix bitfield order */ + for (i = 0; i < credcount; i++) + pp->credlist[i] = __htonpmCred(credlist[i]); + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeCreds(__pmPDU *pdubuf, int *sender, int *credcount, __pmCred **credlist) +{ + creds_t *pp; + int i; + int len; + int need; + int numcred; + __pmCred *list; + + pp = (creds_t *)pdubuf; + len = pp->hdr.len; /* ntohl() converted already in __pmGetPDU() */ + numcred = ntohl(pp->numcreds); + if (numcred < 0 || numcred > LIMIT_CREDS) + return PM_ERR_IPC; + need = sizeof(creds_t) + ((numcred-1) * sizeof(__pmCred)); + if (need != len) + return PM_ERR_IPC; + + *sender = pp->hdr.from; /* ntohl() converted already in __pmGetPDU() */ + if ((list = (__pmCred *)malloc(sizeof(__pmCred) * numcred)) == NULL) + return -oserror(); + + /* swab and fix bitfield order */ + for (i = 0; i < numcred; i++) { + list[i] = __ntohpmCred(pp->credlist[i]); + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + for (i = 0; i < numcred; i++) + fprintf(stderr, "__pmDecodeCreds: #%d = { type=0x%x a=0x%x b=0x%x c=0x%x }\n", + i, list[i].c_type, list[i].c_vala, + list[i].c_valb, list[i].c_valc); +#endif + + *credlist = list; + *credcount = numcred; + + return 0; +} diff --git a/src/libpcp/src/p_desc.c b/src/libpcp/src/p_desc.c new file mode 100644 index 0000000..46a8a66 --- /dev/null +++ b/src/libpcp/src/p_desc.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for pmLookupDesc request (PDU_DESC_REQ) + */ +typedef struct { + __pmPDUHdr hdr; + pmID pmid; +} desc_req_t; + +int +__pmSendDescReq(int fd, int from, pmID pmid) +{ + desc_req_t *pp; + int sts; + + if ((pp = (desc_req_t *)__pmFindPDUBuf(sizeof(desc_req_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(desc_req_t); + pp->hdr.type = PDU_DESC_REQ; + pp->hdr.from = from; + pp->pmid = __htonpmID(pmid); + +#ifdef DESPERATE + { + char strbuf[20]; + fprintf(stderr, "__pmSendDescReq: converted 0x%08x (%s) to 0x%08x\n", pmid, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), pp->pmid); + } +#endif + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeDescReq(__pmPDU *pdubuf, pmID *pmid) +{ + desc_req_t *pp; + char *pduend; + + pp = (desc_req_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp != sizeof(desc_req_t)) + return PM_ERR_IPC; + + *pmid = __ntohpmID(pp->pmid); + return 0; +} + +/* + * PDU for pmLookupDesc result (PDU_DESC) + */ +typedef struct { + __pmPDUHdr hdr; + pmDesc desc; +} desc_t; + +int +__pmSendDesc(int fd, int ctx, pmDesc *desc) +{ + desc_t *pp; + int sts; + + if ((pp = (desc_t *)__pmFindPDUBuf(sizeof(desc_t))) == NULL) + return -oserror(); + + pp->hdr.len = sizeof(desc_t); + pp->hdr.type = PDU_DESC; + pp->hdr.from = ctx; + pp->desc.type = htonl(desc->type); + pp->desc.sem = htonl(desc->sem); + pp->desc.indom = __htonpmInDom(desc->indom); + pp->desc.units = __htonpmUnits(desc->units); + pp->desc.pmid = __htonpmID(desc->pmid); + + sts =__pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeDesc(__pmPDU *pdubuf, pmDesc *desc) +{ + desc_t *pp; + char *pduend; + + pp = (desc_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp != sizeof(desc_t)) + return PM_ERR_IPC; + + desc->type = ntohl(pp->desc.type); + desc->sem = ntohl(pp->desc.sem); + desc->indom = __ntohpmInDom(pp->desc.indom); + desc->units = __ntohpmUnits(pp->desc.units); + desc->pmid = __ntohpmID(pp->desc.pmid); + return 0; +} diff --git a/src/libpcp/src/p_error.c b/src/libpcp/src/p_error.c new file mode 100644 index 0000000..819757e --- /dev/null +++ b/src/libpcp/src/p_error.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include <ctype.h> + +/* + * Old V1 error codes are only used in 2 places now: + * 1) embedded in pmResults of V1 archives, and + * 2) as part of the client/pmcd connection challenge where all versions + * if pmcd return the status as a V1 error code as a legacy of + * migration from V1 to V2 protocols that we're stuck with (not + * really an issue, as the error code is normally 0) + * + * These macros were removed from the more public pmapi.h and impl.h + * headers in PCP 3.6 + */ +#define PM_ERR_BASE1 1000 +#define PM_ERR_V1(e) (e)+PM_ERR_BASE2-PM_ERR_BASE1 +#define XLATE_ERR_1TO2(e) \ + ((e) <= -PM_ERR_BASE1 ? (e)+PM_ERR_BASE1-PM_ERR_BASE2 : (e)) +#define XLATE_ERR_2TO1(e) \ + ((e) <= -PM_ERR_BASE2 ? PM_ERR_V1(e) : (e)) + +/* + * PDU for general error reporting (PDU_ERROR) + */ +typedef struct { + __pmPDUHdr hdr; + int code; /* error code */ +} p_error_t; + +/* + * and the extended variant, with a second datum word + */ +typedef struct { + __pmPDUHdr hdr; + int code; /* error code */ + int datum; /* additional information */ +} x_error_t; + +int +__pmSendError(int fd, int from, int code) +{ + p_error_t *pp; + int sts; + + if ((pp = (p_error_t *)__pmFindPDUBuf(sizeof(p_error_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(p_error_t); + pp->hdr.type = PDU_ERROR; + pp->hdr.from = from; + + pp->code = code; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, + "__pmSendError: sending error PDU (code=%d, toversion=%d)\n", + pp->code, __pmVersionIPC(fd)); +#endif + + pp->code = htonl(pp->code); + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmSendXtendError(int fd, int from, int code, int datum) +{ + x_error_t *pp; + int sts; + + if ((pp = (x_error_t *)__pmFindPDUBuf(sizeof(x_error_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(x_error_t); + pp->hdr.type = PDU_ERROR; + pp->hdr.from = from; + + /* + * It is ALWAYS a PCP 1.x error code here ... this was required + * to support migration from the V1 to V2 protocols when a V2 pmcd + * (who is the sole user of this routine) supported connections + * from both V1 and V2 PMAPI clients ... for the same reason we + * cannot retire this translation, even when the V1 protocols are + * no longer supported in all other IPC cases. + * + * For most common cases, code is 0 so it makes no difference. + */ + pp->code = htonl(XLATE_ERR_2TO1(code)); + + pp->datum = datum; /* NOTE: caller must swab this */ + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeError(__pmPDU *pdubuf, int *code) +{ + p_error_t *pp; + int sts; + + pp = (p_error_t *)pdubuf; + if (pp->hdr.len != sizeof(p_error_t) && pp->hdr.len != sizeof(x_error_t)) { + sts = *code = PM_ERR_IPC; + } else { + *code = ntohl(pp->code); + sts = 0; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, + "__pmDecodeError: got error PDU (code=%d, fromversion=%d)\n", + *code, __pmLastVersionIPC()); +#endif + return sts; +} + +int +__pmDecodeXtendError(__pmPDU *pdubuf, int *code, int *datum) +{ + x_error_t *pp = (x_error_t *)pdubuf; + int sts; + + if (pp->hdr.len != sizeof(p_error_t) && pp->hdr.len != sizeof(x_error_t)) { + *code = PM_ERR_IPC; + } else { + /* + * It is ALWAYS a PCP 1.x error code here ... see note above + * in __pmSendXtendError() + */ + *code = XLATE_ERR_1TO2((int)ntohl(pp->code)); + } + if (pp->hdr.len == sizeof(x_error_t)) { + /* really version 2 extended error PDU */ + sts = PDU_VERSION2; + *datum = pp->datum; /* NOTE: caller must swab this */ + } + else { + sts = PM_ERR_IPC; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "__pmDecodeXtendError: " + "got error PDU (code=%d, datum=%d, version=%d)\n", + *code, *datum, sts); +#endif + + return sts; +} diff --git a/src/libpcp/src/p_fetch.c b/src/libpcp/src/p_fetch.c new file mode 100644 index 0000000..db29687 --- /dev/null +++ b/src/libpcp/src/p_fetch.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for pmFetch request (PDU_FETCH) + */ +typedef struct { + __pmPDUHdr hdr; + int ctxnum; /* context no. */ + __pmTimeval when; /* desired time */ + int numpmid; /* no. PMIDs to follow */ + pmID pmidlist[1]; /* one or more */ +} fetch_t; + +int +__pmSendFetch(int fd, int from, int ctxnum, __pmTimeval *when, int numpmid, pmID *pmidlist) +{ + size_t need; + fetch_t *pp; + int j; + int sts; + + need = sizeof(fetch_t) + (numpmid-1) * sizeof(pmID); + if ((pp = (fetch_t *)__pmFindPDUBuf((int)need)) == NULL) + return -oserror(); + pp->hdr.len = (int)need; + pp->hdr.type = PDU_FETCH; + /* + * note: context id may be sent twice due to protocol evolution and + * backwards compatibility issues + */ + pp->hdr.from = from; + pp->ctxnum = htonl(ctxnum); + if (when == NULL) + memset((void *)&pp->when, 0, sizeof(pp->when)); + else { +#if defined(HAVE_32BIT_LONG) + pp->when.tv_sec = htonl(when->tv_sec); + pp->when.tv_usec = htonl(when->tv_usec); +#else + pp->when.tv_sec = htonl((__int32_t)when->tv_sec); + pp->when.tv_usec = htonl((__int32_t)when->tv_usec); +#endif + } + pp->numpmid = htonl(numpmid); + for (j = 0; j < numpmid; j++) + pp->pmidlist[j] = __htonpmID(pmidlist[j]); + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeFetch(__pmPDU *pdubuf, int *ctxnum, __pmTimeval *when, int *numpmidp, pmID **pmidlist) +{ + fetch_t *pp; + char *pduend; + int numpmid; + int j; + + pp = (fetch_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp < sizeof(fetch_t)) + return PM_ERR_IPC; + numpmid = ntohl(pp->numpmid); + if (numpmid <= 0 || numpmid > pp->hdr.len) + return PM_ERR_IPC; + if (numpmid >= (INT_MAX - sizeof(fetch_t)) / sizeof(pmID)) + return PM_ERR_IPC; + if ((pduend - (char*)pp) != sizeof(fetch_t) + ((sizeof(pmID)) * (numpmid-1))) + return PM_ERR_IPC; + + for (j = 0; j < numpmid; j++) + pp->pmidlist[j] = __ntohpmID(pp->pmidlist[j]); + + *ctxnum = ntohl(pp->ctxnum); + when->tv_sec = ntohl(pp->when.tv_sec); + when->tv_usec = ntohl(pp->when.tv_usec); + *numpmidp = numpmid; + *pmidlist = pp->pmidlist; + __pmPinPDUBuf((void *)pdubuf); + return 0; +} diff --git a/src/libpcp/src/p_instance.c b/src/libpcp/src/p_instance.c new file mode 100644 index 0000000..3b5474d --- /dev/null +++ b/src/libpcp/src/p_instance.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for pm*InDom request (PDU_INSTANCE_REQ) + */ +typedef struct { + __pmPDUHdr hdr; + pmInDom indom; + __pmTimeval when; /* desired time */ + int inst; /* may be PM_IN_NULL */ + int namelen; /* chars in name[], may be 0 */ + char name[sizeof(int)]; /* may be missing */ +} instance_req_t; + +int +__pmSendInstanceReq(int fd, int from, const __pmTimeval *when, pmInDom indom, + int inst, const char *name) +{ + instance_req_t *pp; + int need; + int sts; + + need = sizeof(instance_req_t) - sizeof(int); + if (name != NULL) + need += PM_PDU_SIZE_BYTES(strlen(name)); + if ((pp = (instance_req_t *)__pmFindPDUBuf(sizeof(need))) == NULL) + return -oserror(); + pp->hdr.len = need; + pp->hdr.type = PDU_INSTANCE_REQ; + pp->hdr.from = from; + pp->when.tv_sec = htonl((__int32_t)when->tv_sec); + pp->when.tv_usec = htonl((__int32_t)when->tv_usec); + pp->indom = __htonpmInDom(indom); + pp->inst = htonl(inst); + if (name == NULL) + pp->namelen = 0; + else { + pp->namelen = (int)strlen(name); + memcpy((void *)pp->name, (void *)name, pp->namelen); +#ifdef PCP_DEBUG + if ((pp->namelen % sizeof(__pmPDU)) != 0) { + /* for Purify */ + int pad; + char *padp = pp->name + pp->namelen; + + for (pad = sizeof(__pmPDU) - 1; pad >= (pp->namelen % sizeof(__pmPDU)); pad--) + *padp++ = '~'; /* buffer end */ + } +#endif + pp->namelen = htonl(pp->namelen); + } + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeInstanceReq(__pmPDU *pdubuf, __pmTimeval *when, pmInDom *indom, int *inst, char **name) +{ + instance_req_t *pp; + char *pdu_end; + int namelen; + + pp = (instance_req_t *)pdubuf; + pdu_end = (char *)pdubuf + pp->hdr.len; + + if (pdu_end - (char *)pp < sizeof(instance_req_t) - sizeof(pp->name)) + return PM_ERR_IPC; + + when->tv_sec = ntohl(pp->when.tv_sec); + when->tv_usec = ntohl(pp->when.tv_usec); + *indom = __ntohpmInDom(pp->indom); + *inst = ntohl(pp->inst); + namelen = ntohl(pp->namelen); + if (namelen > 0) { + if (namelen >= INT_MAX - 1 || namelen > pp->hdr.len) + return PM_ERR_IPC; + if (pdu_end - (char *)pp < sizeof(instance_req_t) - sizeof(pp->name) + namelen) + return PM_ERR_IPC; + if ((*name = (char *)malloc(namelen+1)) == NULL) + return -oserror(); + strncpy(*name, pp->name, namelen); + (*name)[namelen] = '\0'; + } + else if (namelen < 0) { + return PM_ERR_IPC; + } else { + *name = NULL; + } + return 0; +} + +/* + * PDU for pm*InDom result (PDU_INSTANCE) + */ +typedef struct { + int inst; /* internal instance id */ + int namelen; /* chars in name[], may be 0 */ + char name[sizeof(int)]; /* may be missing */ +} instlist_t; + +typedef struct { + __pmPDUHdr hdr; + pmInDom indom; + int numinst; /* no. of elts to follow */ + __pmPDU rest[1]; /* array of instlist_t */ +} instance_t; + +int +__pmSendInstance(int fd, int from, __pmInResult *result) +{ + instance_t *rp; + instlist_t *ip; + int need; + int i; + int j; + int sts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) + __pmDumpInResult(stderr, result); +#endif + + need = sizeof(*rp) - sizeof(rp->rest); + /* instlist_t + name rounded up to a __pmPDU boundary */ + for (i = 0; i < result->numinst; i++) { + need += sizeof(*ip) - sizeof(ip->name); + if (result->namelist != NULL) + need += PM_PDU_SIZE_BYTES(strlen(result->namelist[i])); + } + + if ((rp = (instance_t *)__pmFindPDUBuf(need)) == NULL) + return -oserror(); + rp->hdr.len = need; + rp->hdr.type = PDU_INSTANCE; + rp->hdr.from = from; + rp->indom = __htonpmInDom(result->indom); + rp->numinst = htonl(result->numinst); + + for (i = j = 0; i < result->numinst; i++) { + ip = (instlist_t *)&rp->rest[j/sizeof(__pmPDU)]; + if (result->instlist != NULL) + ip->inst = htonl(result->instlist[i]); + else + /* weird, but this is going to be ignored at the other end */ + ip->inst = htonl(PM_IN_NULL); + if (result->namelist != NULL) { + ip->namelen = (int)strlen(result->namelist[i]); + memcpy((void *)ip->name, (void *)result->namelist[i], ip->namelen); +#ifdef PCP_DEBUG + if ((ip->namelen % sizeof(__pmPDU)) != 0) { + /* for Purify */ + int pad; + char *padp = ip->name + ip->namelen; + for (pad = sizeof(__pmPDU) - 1; pad >= (ip->namelen % sizeof(__pmPDU)); pad--) + *padp++ = '~'; /* buffer end */ + } +#endif + j += sizeof(*ip) - sizeof(ip->name) + PM_PDU_SIZE_BYTES(ip->namelen); + ip->namelen = htonl(ip->namelen); + } + else { + ip->namelen = 0; + j += sizeof(*ip) - sizeof(ip->name); + } + } + + sts = __pmXmitPDU(fd, (__pmPDU *)rp); + __pmUnpinPDUBuf(rp); + return sts; +} + +int +__pmDecodeInstance(__pmPDU *pdubuf, __pmInResult **result) +{ + int i; + int j; + instance_t *rp; + instlist_t *ip; + __pmInResult *res; + int sts; + char *p; + char *pdu_end; + int keep_instlist; + int keep_namelist; + + rp = (instance_t *)pdubuf; + pdu_end = (char *)pdubuf + rp->hdr.len; + + if (pdu_end - (char *)pdubuf < sizeof(instance_t) - sizeof(__pmPDU)) + return PM_ERR_IPC; + + if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL) + return -oserror(); + res->instlist = NULL; + res->namelist = NULL; + res->indom = __ntohpmInDom(rp->indom); + res->numinst = ntohl(rp->numinst); + + if (res->numinst >= (INT_MAX / sizeof(res->instlist[0])) || + res->numinst >= (INT_MAX / sizeof(res->namelist[0])) || + res->numinst >= rp->hdr.len) { + sts = PM_ERR_IPC; + goto badsts; + } + if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) { + sts = -oserror(); + goto badsts; + } + if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) { + sts = -oserror(); + goto badsts; + } + /* required for __pmFreeInResult() in the event of a later error */ + memset(res->namelist, 0, res->numinst * sizeof(res->namelist[0])); + + if (res->numinst == 1) + keep_instlist = keep_namelist = 0; + else + keep_instlist = keep_namelist = 1; + + for (i = j = 0; i < res->numinst; i++) { + ip = (instlist_t *)&rp->rest[j/sizeof(__pmPDU)]; + if (sizeof(instlist_t) - sizeof(ip->name) > (size_t)(pdu_end - (char *)ip)) { + sts = PM_ERR_IPC; + goto badsts; + } + + res->instlist[i] = ntohl(ip->inst); + if (res->instlist[i] != PM_IN_NULL) + keep_instlist = 1; + ip->namelen = ntohl(ip->namelen); + if (ip->namelen > 0) + keep_namelist = 1; + if (ip->namelen < 0) { + sts = PM_ERR_IPC; + goto badsts; + } + if (sizeof(instlist_t) - sizeof(int) + ip->namelen > (size_t)(pdu_end - (char *)ip)) { + sts = PM_ERR_IPC; + goto badsts; + } + if ((p = (char *)malloc(ip->namelen + 1)) == NULL) { + sts = -oserror(); + goto badsts; + } + memcpy((void *)p, (void *)ip->name, ip->namelen); + p[ip->namelen] = '\0'; + res->namelist[i] = p; + j += sizeof(*ip) - sizeof(ip->name) + PM_PDU_SIZE_BYTES(ip->namelen); + } + if (keep_instlist == 0) { + free(res->instlist); + res->instlist = NULL; + } + if (keep_namelist == 0) { + free(res->namelist[0]); + free(res->namelist); + res->namelist = NULL; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) + __pmDumpInResult(stderr, res); +#endif + *result = res; + return 0; + +badsts: + __pmFreeInResult(res); + return sts; +} diff --git a/src/libpcp/src/p_lcontrol.c b/src/libpcp/src/p_lcontrol.c new file mode 100644 index 0000000..9a57214 --- /dev/null +++ b/src/libpcp/src/p_lcontrol.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for __pmControlLogging request (PDU_LOG_CONTROL) + */ + +typedef struct { + pmID v_pmid; + int v_numval; /* no. of vlist els to follow */ + __pmValue_PDU v_list[1]; /* one or more */ +} vlist_t; + +typedef struct { + __pmPDUHdr c_hdr; + int c_control; /* mandatory or advisory */ + int c_state; /* off, maybe or on */ + int c_delta; /* requested logging interval (msec) */ + int c_numpmid; /* no. of vlist_ts to follow */ + __pmPDU c_data[1]; /* one or more */ +} control_req_t; + +int +__pmSendLogControl(int fd, const pmResult *request, int control, int state, int delta) +{ + pmValueSet *vsp; + int i; + int j; + control_req_t *pp; + int need; + vlist_t *vp; + int sts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) + __pmDumpResult(stderr, request); +#endif + + /* advisory+maybe logging and retrospective logging (delta < 0) are not + *permitted + */ + if (delta < 0 || + (control == PM_LOG_ADVISORY && state == PM_LOG_MAYBE)) + return -EINVAL; + + /* PDU header, control, state and count of metrics */ + need = sizeof(control_req_t) - sizeof(pp->c_data); + for (i = 0; i < request->numpmid; i++) { + /* plus PMID and count of values */ + if (request->vset[i]->numval > 0) + need += sizeof(vlist_t) + (request->vset[i]->numval - 1)*sizeof(__pmValue_PDU); + else + need += sizeof(vlist_t) - sizeof(__pmValue_PDU); + } + if ((pp = (control_req_t *)__pmFindPDUBuf(need)) == NULL) + return -oserror(); + pp->c_hdr.len = need; + pp->c_hdr.type = PDU_LOG_CONTROL; + pp->c_hdr.from = FROM_ANON; /* context does not matter here */ + pp->c_control = htonl(control); + pp->c_state = htonl(state); + pp->c_delta = htonl(delta); + pp->c_numpmid = htonl(request->numpmid); + vp = (vlist_t *)pp->c_data; + for (i = 0; i < request->numpmid; i++) { + vsp = request->vset[i]; + vp->v_pmid = __htonpmID(vsp->pmid); + vp->v_numval = htonl(vsp->numval); + /* + * Note: spec says only PM_VAL_INSITU can be used ... we don't + * check vsp->valfmt -- this is OK, because the "value" is never + * looked at! + */ + for (j = 0; j < vsp->numval; j++) { + vp->v_list[j].inst = htonl(vsp->vlist[j].inst); + vp->v_list[j].value.lval = htonl(vsp->vlist[j].value.lval); + } + if (vsp->numval > 0) + vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) + (vsp->numval-1)*sizeof(__pmValue_PDU)); + else + vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) - sizeof(__pmValue_PDU)); + } + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeLogControl(const __pmPDU *pdubuf, pmResult **request, int *control, int *state, int *delta) +{ + int sts; + int i; + int j; + int nv; + const control_req_t *pp; + char *pduend; + int numpmid; + size_t need; + pmResult *req; + pmValueSet *vsp; + vlist_t *vp; + + pp = (const control_req_t *)pdubuf; + pduend = (char *)pdubuf + pp->c_hdr.len; + if (pduend - (char *)pdubuf < sizeof(control_req_t)) + return PM_ERR_IPC; + + *control = ntohl(pp->c_control); + *state = ntohl(pp->c_state); + *delta = ntohl(pp->c_delta); + numpmid = ntohl(pp->c_numpmid); + vp = (vlist_t *)pp->c_data; + + if (numpmid < 0 || numpmid > pp->c_hdr.len) + return PM_ERR_IPC; + if (numpmid >= (INT_MAX - sizeof(pmResult)) / sizeof(pmValueSet *)) + return PM_ERR_IPC; + need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *); + if ((req = (pmResult *)malloc(need)) == NULL) { + __pmNoMem("__pmDecodeLogControl.req", need, PM_RECOV_ERR); + return -oserror(); + } + req->numpmid = numpmid; + + sts = PM_ERR_IPC; + for (i = 0; i < numpmid; i++) { + /* check that numval field is within the input buffer */ + if (pduend - (char *)vp < sizeof(vlist_t) - sizeof(__pmValue_PDU)) + goto corrupt; + nv = (int)ntohl(vp->v_numval); + if (nv > pp->c_hdr.len) + goto corrupt; + if (nv <= 0) { + need = sizeof(pmValueSet) - sizeof(pmValue); + /* check that pointer cannot move beyond input buffer end */ + if (pduend - (char *)vp < sizeof(vlist_t) - sizeof(__pmValue_PDU)) + goto corrupt; + } else { + /* check that dynamic allocation argument will not wrap */ + if (nv >= (INT_MAX - sizeof(pmValueSet)) / sizeof(pmValue)) + goto corrupt; + need = sizeof(pmValueSet) + ((nv - 1) * sizeof(pmValue)); + /* check that pointer cannot move beyond input buffer end */ + if (nv >= (INT_MAX - sizeof(vlist_t)) / sizeof(__pmValue_PDU)) + goto corrupt; + if (pduend - (char *)vp < sizeof(vlist_t) + ((nv - 1) * sizeof(__pmValue_PDU))) + goto corrupt; + } + if ((vsp = (pmValueSet *)malloc(need)) == NULL) { + __pmNoMem("__pmDecodeLogControl.vsp", need, PM_RECOV_ERR); + sts = -oserror(); + i--; + goto corrupt; + } + req->vset[i] = vsp; + vsp->pmid = __ntohpmID(vp->v_pmid); + vsp->valfmt = PM_VAL_INSITU; + vsp->numval = nv; + for (j = 0; j < nv; j++) { + vsp->vlist[j].inst = ntohl(vp->v_list[j].inst); + vsp->vlist[j].value.lval = ntohl(vp->v_list[j].value.lval); + } + if (nv > 0) + vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) + (nv - 1)*sizeof(__pmValue_PDU)); + else + vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) - sizeof(__pmValue_PDU)); + } + + *request = req; + return 0; + +corrupt: + while (i) + free(req->vset[i--]); + free(req); + return sts; +} diff --git a/src/libpcp/src/p_lrequest.c b/src/libpcp/src/p_lrequest.c new file mode 100644 index 0000000..bbe9696 --- /dev/null +++ b/src/libpcp/src/p_lrequest.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe note + * + * Unlike most of the other __pmSend*() routines, there is no wrapper + * routine in libpcp for __pmSendLogRequest() so there is no place in + * the library to enforce serialization between the sending of the + * PDU in __pmSendLogRequest() and reading the result PDU. + * + * It is assumed that the caller of __pmSendLogRequest() either manages + * this serialization or is single-threaded, which is true for + * the only current user of this routine, pmlc(1). + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" + +/* + * PDU for general pmlogger notification (PDU_LOG_REQUEST) + */ +typedef struct { + __pmPDUHdr hdr; + int type; /* notification type */ +} notify_t; + +int +__pmSendLogRequest(int fd, int type) +{ + notify_t *pp; + int sts; + + if ((pp = (notify_t *)__pmFindPDUBuf(sizeof(notify_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(notify_t); + pp->hdr.type = PDU_LOG_REQUEST; + pp->hdr.from = FROM_ANON; /* context does not matter here */ + pp->type = htonl(type); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int version = __pmVersionIPC(fd); + fprintf(stderr, "_pmSendRequest: sending PDU (type=%d, version=%d)\n", + pp->type, version==UNKNOWN_VERSION? LOG_PDU_VERSION : version); + } +#endif + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeLogRequest(const __pmPDU *pdubuf, int *type) +{ + const notify_t *pp; + const char *pduend; + + pp = (const notify_t *)pdubuf; + pduend = (const char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp < sizeof(notify_t)) + return PM_ERR_IPC; + + *type = ntohl(pp->type); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int version = __pmLastVersionIPC(); + fprintf(stderr, "__pmDecodeLogRequest: got PDU (type=%d, version=%d)\n", + *type, version==UNKNOWN_VERSION? LOG_PDU_VERSION : version); + } +#endif + return 0; +} diff --git a/src/libpcp/src/p_lstatus.c b/src/libpcp/src/p_lstatus.c new file mode 100644 index 0000000..5b61b10 --- /dev/null +++ b/src/libpcp/src/p_lstatus.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe note + * + * Unlike most of the other __pmSend*() routines, there is no wrapper + * routine in libpcp for __pmSendLogStatus() so there is no place in + * the library to enforce serialization between the receiving the + * LOG_REQUEST_STATUS PDU and calling __pmSendLogStatus(). + * + * It is assumed that the caller of __pmSendLogStatus() either manages + * this serialization or is single-threaded, which is true for + * the only current user of this routine, pmlogger(1). + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for logger status information transfer (PDU_LOG_STATUS) + */ +typedef struct { + __pmPDUHdr hdr; + int pad; /* force status to be double word aligned */ + __pmLoggerStatus status; +} logstatus_t; + +int +__pmSendLogStatus(int fd, __pmLoggerStatus *status) +{ + logstatus_t *pp; + int sts; + + if ((pp = (logstatus_t *)__pmFindPDUBuf(sizeof(logstatus_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(logstatus_t); + pp->hdr.type = PDU_LOG_STATUS; + pp->hdr.from = FROM_ANON; /* context does not matter here */ + memcpy(&pp->status, status, sizeof(__pmLoggerStatus)); + + /* Conditional convertion from host to network byteorder HAVE to be + * unconditional if one cares about endianess compatibiltity at all! + */ + pp->status.ls_state = htonl(pp->status.ls_state); + pp->status.ls_vol = htonl(pp->status.ls_vol); + __htonll((char *)&pp->status.ls_size); + pp->status.ls_start.tv_sec = htonl(pp->status.ls_start.tv_sec); + pp->status.ls_start.tv_usec = htonl(pp->status.ls_start.tv_usec); + pp->status.ls_last.tv_sec = htonl(pp->status.ls_last.tv_sec); + pp->status.ls_last.tv_usec = htonl(pp->status.ls_last.tv_usec); + pp->status.ls_timenow.tv_sec = htonl(pp->status.ls_timenow.tv_sec); + pp->status.ls_timenow.tv_usec = htonl(pp->status.ls_timenow.tv_usec); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int version = __pmVersionIPC(fd); + fprintf(stderr, "__pmSendLogStatus: sending PDU (toversion=%d)\n", + version == UNKNOWN_VERSION ? LOG_PDU_VERSION : version); + } +#endif + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeLogStatus(__pmPDU *pdubuf, __pmLoggerStatus **status) +{ + logstatus_t *pp; + char *pduend; + + pp = (logstatus_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if ((pduend - (char*)pp) != sizeof(logstatus_t)) + return PM_ERR_IPC; + + /* Conditional convertion from host to network byteorder HAVE to be + * unconditional if one cares about endianess compatibiltity at all! + */ + pp->status.ls_state = ntohl(pp->status.ls_state); + pp->status.ls_vol = ntohl(pp->status.ls_vol); + __ntohll((char *)&pp->status.ls_size); + pp->status.ls_start.tv_sec = ntohl(pp->status.ls_start.tv_sec); + pp->status.ls_start.tv_usec = ntohl(pp->status.ls_start.tv_usec); + pp->status.ls_last.tv_sec = ntohl(pp->status.ls_last.tv_sec); + pp->status.ls_last.tv_usec = ntohl(pp->status.ls_last.tv_usec); + pp->status.ls_timenow.tv_sec = ntohl(pp->status.ls_timenow.tv_sec); + pp->status.ls_timenow.tv_usec = ntohl(pp->status.ls_timenow.tv_usec); + + *status = &pp->status; + __pmPinPDUBuf(pdubuf); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int version = __pmLastVersionIPC(); + fprintf(stderr, "__pmDecodeLogStatus: got PDU (fromversion=%d)\n", + version == UNKNOWN_VERSION ? LOG_PDU_VERSION : version); + } +#endif + return 0; +} diff --git a/src/libpcp/src/p_pmns.c b/src/libpcp/src/p_pmns.c new file mode 100644 index 0000000..8a90753 --- /dev/null +++ b/src/libpcp/src/p_pmns.c @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for id list (PDU_PMNS_IDS) + */ +typedef struct { + __pmPDUHdr hdr; + int sts; /* to encode status of pmns op */ + int numids; + pmID idlist[1]; +} idlist_t; + +#ifdef PCP_DEBUG +void +__pmDumpIDList(FILE *f, int numids, const pmID idlist[]) +{ + int i; + char strbuf[20]; + + fprintf(f, "IDlist dump: numids = %d\n", numids); + for (i = 0; i < numids; i++) + fprintf(f, " PMID[%d]: 0x%08x %s\n", i, idlist[i], pmIDStr_r(idlist[i], strbuf, sizeof(strbuf))); +} +#endif + +/* + * Send a PDU_PMNS_IDS across socket. + */ +int +__pmSendIDList(int fd, int from, int numids, const pmID idlist[], int sts) +{ + idlist_t *ip; + int need; + int j; + int lsts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "__pmSendIDList\n"); + __pmDumpIDList(stderr, numids, idlist); + } +#endif + + need = (int)(sizeof(idlist_t) + (numids-1) * sizeof(idlist[0])); + + if ((ip = (idlist_t *)__pmFindPDUBuf(need)) == NULL) + return -oserror(); + ip->hdr.len = need; + ip->hdr.type = PDU_PMNS_IDS; + ip->hdr.from = from; + ip->sts = htonl(sts); + ip->numids = htonl(numids); + for (j = 0; j < numids; j++) { + ip->idlist[j] = __htonpmID(idlist[j]); + } + + lsts = __pmXmitPDU(fd, (__pmPDU *)ip); + __pmUnpinPDUBuf(ip); + return lsts; +} + +/* + * Decode a PDU_PMNS_IDS + * Assumes that we have preallocated idlist prior to this call + * (i.e. we know how many should definitely be coming over) + * Returns 0 on success. + */ +int +__pmDecodeIDList(__pmPDU *pdubuf, int numids, pmID idlist[], int *sts) +{ + idlist_t *idlist_pdu; + char *pdu_end; + int nids; + int j; + + idlist_pdu = (idlist_t *)pdubuf; + pdu_end = (char *)pdubuf + idlist_pdu->hdr.len; + + if (pdu_end - (char *)pdubuf < sizeof(idlist_t) - sizeof(pmID)) + return PM_ERR_IPC; + *sts = ntohl(idlist_pdu->sts); + nids = ntohl(idlist_pdu->numids); + if (nids <= 0 || nids != numids || nids > idlist_pdu->hdr.len) + return PM_ERR_IPC; + if (nids >= (INT_MAX - sizeof(idlist_t)) / sizeof(pmID)) + return PM_ERR_IPC; + if (sizeof(idlist_t) + (sizeof(pmID) * (nids-1)) > (size_t)(pdu_end - (char *)pdubuf)) + return PM_ERR_IPC; + + for (j = 0; j < numids; j++) + idlist[j] = __ntohpmID(idlist_pdu->idlist[j]); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "__pmDecodeIDList\n"); + __pmDumpIDList(stderr, numids, idlist); + } +#endif + + return 0; +} + +/*********************************************************************/ + +/* + * PDU for name list (PDU_PMNS_NAMES) + */ + +typedef struct { + int namelen; + char name[sizeof(__pmPDU)]; /* variable length */ +} name_t; + +typedef struct { + int status; + int namelen; + char name[sizeof(__pmPDU)]; /* variable length */ +} name_status_t; + +typedef struct { + __pmPDUHdr hdr; + int nstrbytes; /* number of str bytes including null terminators */ + int numstatus; /* = 0 if there is no status to be encoded */ + int numnames; + __pmPDU names[1]; /* list of variable length name_t or name_status_t */ +} namelist_t; + +/* + * NOTES: + * + * 1. + * name_t's are padded to a __pmPDU boundary (if needed) + * so that direct accesses of a following record + * can be made on a word boundary. + * i.e. the following "namelen" field will be accessible. + * + * 2. + * Names are sent length prefixed as opposed to null terminated. + * This can make copying at the decoding end simpler + * (and possibly more efficient using memcpy). + * + * 3. + * nstrbytes is used by the decoding function to know how many + * bytes to allocate. + * + * 4. + * name_status_t was added for pmGetChildrenStatus. + * It is a variant of the names pdu which encompasses status + * data as well. + */ + +#ifdef PCP_DEBUG +void +__pmDumpNameList(FILE *f, int numnames, char *namelist[]) +{ + int i; + + fprintf(f, "namelist dump: numnames = %d\n", numnames); + for (i = 0; i < numnames; i++) + fprintf(f, " name[%d]: \"%s\"\n", i, namelist[i]); +} + +void +__pmDumpStatusList(FILE *f, int numstatus, const int statuslist[]) +{ + int i; + + fprintf(f, "statuslist dump: numstatus = %d\n", numstatus); + for (i = 0; i < numstatus; i++) + fprintf(f, " status[%d]: %d\n", i, statuslist[i]); +} + +void +__pmDumpNameAndStatusList(FILE *f, int numnames, char *namelist[], int statuslist[]) +{ + int i; + + fprintf(f, "namelist & statuslist dump: numnames = %d\n", numnames); + for (i = 0; i < numnames; i++) + fprintf(f, " name[%d]: \"%s\" (%s)\n", i, namelist[i], + statuslist[i] == PMNS_LEAF_STATUS ? "leaf" : "non-leaf"); +} +#endif + +/* + * Send a PDU_PMNS_NAMES across socket. + */ +int +__pmSendNameList(int fd, int from, int numnames, char *namelist[], + const int statuslist[]) +{ + namelist_t *nlistp; + int need; + int nstrbytes=0; + int i; + name_t *nt; + name_status_t *nst; + int sts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "__pmSendNameList\n"); + __pmDumpNameList(stderr, numnames, namelist); + if (statuslist != NULL) + __pmDumpStatusList(stderr, numnames, statuslist); + } +#endif + + /* namelist_t + names rounded up to a __pmPDU boundary */ + need = sizeof(*nlistp) - sizeof(nlistp->names); + for (i = 0; i < numnames; i++) { + int len = (int)strlen(namelist[i]); + nstrbytes += len+1; + need += PM_PDU_SIZE_BYTES(len); + if (statuslist == NULL) + need += sizeof(*nt) - sizeof(nt->name); + else + need += sizeof(*nst) - sizeof(nst->name); + } + + if ((nlistp = (namelist_t *)__pmFindPDUBuf(need)) == NULL) + return -oserror(); + nlistp->hdr.len = need; + nlistp->hdr.type = PDU_PMNS_NAMES; + nlistp->hdr.from = from; + nlistp->nstrbytes = htonl(nstrbytes); + nlistp->numnames = htonl(numnames); + + if (statuslist == NULL) { + int i, j = 0, namelen; + nlistp->numstatus = htonl(0); + for(i=0; i<numnames; i++) { + nt = (name_t*)&nlistp->names[j/sizeof(__pmPDU)]; + namelen = (int)strlen(namelist[i]); + memcpy(nt->name, namelist[i], namelen); +#ifdef PCP_DEBUG + if ((namelen % sizeof(__pmPDU)) != 0) { + /* for Purify */ + int pad; + char *padp = nt->name + namelen; + for (pad = sizeof(__pmPDU) - 1; pad >= (namelen % sizeof(__pmPDU)); pad--) + *padp++ = '~'; /* buffer end */ + } +#endif + j += sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen); + nt->namelen = htonl(namelen); + } + } + else { /* include the status fields */ + int i, j = 0, namelen; + nlistp->numstatus = htonl(numnames); + for(i=0; i<numnames; i++) { + nst = (name_status_t*)&nlistp->names[j/sizeof(__pmPDU)]; + nst->status = htonl(statuslist[i]); + namelen = (int)strlen(namelist[i]); + memcpy(nst->name, namelist[i], namelen); +#ifdef PCP_DEBUG + if ((namelen % sizeof(__pmPDU)) != 0) { + /* for Purify */ + int pad; + char *padp = nst->name + namelen; + for (pad = sizeof(__pmPDU) - 1; pad >= (namelen % sizeof(__pmPDU)); pad--) + *padp++ = '~'; /* buffer end */ + } +#endif + j += sizeof(nst->status) + sizeof(namelen) + + PM_PDU_SIZE_BYTES(namelen); + nst->namelen = htonl(namelen); + } + } + + sts = __pmXmitPDU(fd, (__pmPDU *)nlistp); + __pmUnpinPDUBuf(nlistp); + return sts; +} + +/* + * Decode a PDU_PMNS_NAMES + * + * statuslist is optional ... if NULL, no status values will be returned + */ +int +__pmDecodeNameList(__pmPDU *pdubuf, int *numnamesp, + char*** namelist, int** statuslist) +{ + namelist_t *namelist_pdu; + char *pdu_end; + char **names; + char *dest, *dest_end; + int *status = NULL; + int namesize, numnames; + int statussize, numstatus; + int nstrbytes; + int namelen; + int i, j; + + namelist_pdu = (namelist_t *)pdubuf; + pdu_end = (char *)pdubuf + namelist_pdu->hdr.len; + + *namelist = NULL; + if (statuslist != NULL) + *statuslist = NULL; + + if (pdu_end - (char*)namelist_pdu < sizeof(namelist_t) - sizeof(__pmPDU)) + return PM_ERR_IPC; + + numnames = ntohl(namelist_pdu->numnames); + numstatus = ntohl(namelist_pdu->numstatus); + nstrbytes = ntohl(namelist_pdu->nstrbytes); + + if (numnames == 0) { + *numnamesp = 0; + return 0; + } + + /* validity checks - none of these conditions should happen */ + if (numnames < 0 || nstrbytes < 0) + return PM_ERR_IPC; + /* anti-DOS measure - limiting allowable memory allocations */ + if (numnames > namelist_pdu->hdr.len || nstrbytes > namelist_pdu->hdr.len) + return PM_ERR_IPC; + /* numstatus must be one (and only one) of zero or numnames */ + if (numstatus != 0 && numstatus != numnames) + return PM_ERR_IPC; + + /* need space for name ptrs and the name characters */ + if (numnames >= (INT_MAX - nstrbytes) / (int)sizeof(char *)) + return PM_ERR_IPC; + namesize = numnames * ((int)sizeof(char*)) + nstrbytes; + if ((names = (char**)malloc(namesize)) == NULL) + return -oserror(); + + /* need space for status values */ + if (statuslist != NULL && numstatus > 0) { + if (numstatus >= INT_MAX / (int)sizeof(int)) + goto corrupt; + statussize = numstatus * (int)sizeof(int); + if ((status = (int*)malloc(statussize)) == NULL) { + free(names); + return -oserror(); + } + } + + dest = (char*)&names[numnames]; + dest_end = (char*)names + namesize; + + /* copy over ptrs and characters */ + if (numstatus == 0) { + name_t *np; + + for (i = j = 0; i < numnames; i++) { + np = (name_t*)&namelist_pdu->names[j/sizeof(__pmPDU)]; + names[i] = dest; + + if (sizeof(name_t) > (size_t)(pdu_end - (char *)np)) + goto corrupt; + namelen = ntohl(np->namelen); + /* ensure source buffer contains everything that we copy over */ + if (sizeof(np->namelen) + namelen > (size_t)(pdu_end - (char *)np)) + goto corrupt; + /* ensure space in destination; note null-terminator is added */ + if (namelen < 0 || (namelen + 1) > (dest_end - dest)) + goto corrupt; + + memcpy(dest, np->name, namelen); + *(dest + namelen) = '\0'; + dest += namelen + 1; + + j += sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen); + } + } + else { /* status fields included in the PDU */ + name_status_t *np; + + for (i = j = 0; i < numnames; i++) { + np = (name_status_t*)&namelist_pdu->names[j/sizeof(__pmPDU)]; + names[i] = dest; + + if (sizeof(name_status_t) > (size_t)(pdu_end - (char *)np)) + goto corrupt; + namelen = ntohl(np->namelen); + /* ensure source buffer contains everything that we copy over */ + if (sizeof(np->namelen) + sizeof(np->status) + namelen > (size_t)(pdu_end - (char *)np)) + goto corrupt; + /* ensure space for null-terminated name in destination buffer */ + if (namelen < 0 || (namelen + 1) > (dest_end - dest)) + goto corrupt; + + if (status != NULL) + status[i] = ntohl(np->status); + + memcpy(dest, np->name, namelen); + *(dest + namelen) = '\0'; + dest += namelen + 1; + + j += sizeof(np->status) + sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen); + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "__pmDecodeNameList\n"); + __pmDumpNameList(stderr, numnames, names); + if (status != NULL) + __pmDumpStatusList(stderr, numstatus, status); + } +#endif + + *namelist = names; + if (statuslist != NULL) + *statuslist = status; + *numnamesp = numnames; + return numnames; + +corrupt: + if (status != NULL) + free(status); + free(names); + return PM_ERR_IPC; +} + + +/*********************************************************************/ + +/* + * name request + */ + +typedef struct { + __pmPDUHdr hdr; + int subtype; + int namelen; + char name[sizeof(int)]; +} namereq_t; + +/* + * Send a PDU_PMNS_CHILD_REQ across socket. + */ +static int +SendNameReq(int fd, int from, const char *name, int pdu_type, int subtype) +{ + namereq_t *nreq; + int need; + int namelen; + int alloc_len; /* length allocated for name */ + int sts; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + char strbuf[20]; + fprintf(stderr, "SendNameReq: from=%d name=\"%s\" pdu=%s subtype=%d\n", + from, name, __pmPDUTypeStr_r(pdu_type, strbuf, sizeof(strbuf)), subtype); + } +#endif + + namelen = (int)strlen(name); + alloc_len = (int)(sizeof(int)*((namelen-1 + sizeof(int))/sizeof(int))); + need = (int)(sizeof(*nreq) - sizeof(nreq->name) + alloc_len); + + if ((nreq = (namereq_t *)__pmFindPDUBuf(need)) == NULL) + return -oserror(); + nreq->hdr.len = need; + nreq->hdr.type = pdu_type; + nreq->hdr.from = from; + nreq->subtype = htonl(subtype); + nreq->namelen = htonl(namelen); + memcpy(&nreq->name[0], name, namelen); + + sts = __pmXmitPDU(fd, (__pmPDU *)nreq); + __pmUnpinPDUBuf(nreq); + return sts; +} + +/* + * Decode a name request + */ +static int +DecodeNameReq(__pmPDU *pdubuf, char **name_p, int *subtype) +{ + namereq_t *namereq_pdu; + char *pdu_end; + char *name; + int namelen; + + namereq_pdu = (namereq_t *)pdubuf; + pdu_end = (char *)pdubuf + namereq_pdu->hdr.len; + + if (pdu_end - (char *)namereq_pdu < sizeof(namereq_t) - sizeof(int)) + return PM_ERR_IPC; + + /* only set it if you want it */ + if (subtype != NULL) + *subtype = ntohl(namereq_pdu->subtype); + namelen = ntohl(namereq_pdu->namelen); + + if (namelen < 0 || namelen > namereq_pdu->hdr.len) + return PM_ERR_IPC; + if (sizeof(namereq_t) - sizeof(int) + namelen > (size_t)(pdu_end - (char *)namereq_pdu)) + return PM_ERR_IPC; + + name = malloc(namelen+1); + if (name == NULL) + return -oserror(); + memcpy(name, namereq_pdu->name, namelen); + name[namelen] = '\0'; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) + fprintf(stderr, "DecodeNameReq: name=\"%s\"\n", name); +#endif + + *name_p = name; + return 0; +} + +/*********************************************************************/ + +/* + * Send a PDU_PMNS_CHILD + */ +int +__pmSendChildReq(int fd, int from, const char *name, int subtype) +{ + return SendNameReq(fd, from, name, PDU_PMNS_CHILD, subtype); +} + + +/* + * Decode a PDU_PMNS_CHILD + */ +int +__pmDecodeChildReq(__pmPDU *pdubuf, char **name_p, int *subtype) +{ + return DecodeNameReq(pdubuf, name_p, subtype); +} + +/*********************************************************************/ + +/* + * Send a PDU_PMNS_TRAVERSE + */ +int +__pmSendTraversePMNSReq(int fd, int from, const char *name) +{ + return SendNameReq(fd, from, name, PDU_PMNS_TRAVERSE, 0); +} + + +/* + * Decode a PDU_PMNS_TRAVERSE + */ +int +__pmDecodeTraversePMNSReq(__pmPDU *pdubuf, char **name_p) +{ + return DecodeNameReq(pdubuf, name_p, 0); +} + +/*********************************************************************/ diff --git a/src/libpcp/src/p_profile.c b/src/libpcp/src/p_profile.c new file mode 100644 index 0000000..63acbfa --- /dev/null +++ b/src/libpcp/src/p_profile.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +#define LIMIT_CTXNUM 2048 + +/* + * PDU used to transmit __pmProfile prior to pmFetch (PDU_PROFILE) + */ +typedef struct { + pmInDom indom; + int state; /* include/exclude */ + int numinst; /* no. of instances to follow */ + int pad; /* protocol backward compatibility */ +} instprof_t; + +typedef struct { + __pmPDUHdr hdr; + int ctxnum; + int g_state; /* global include/exclude */ + int numprof; /* no. of elts to follow */ + int pad; /* protocol backward compatibility */ +} profile_t; + +int +__pmSendProfile(int fd, int from, int ctxnum, __pmProfile *instprof) +{ + __pmInDomProfile *prof, *p_end; + profile_t *pduProfile; + instprof_t *pduInstProf; + __pmPDU *p; + size_t need; + __pmPDU *pdubuf; + int sts; + + /* work out how much space we need and then alloc a pdu buf */ + need = sizeof(profile_t) + instprof->profile_len * sizeof(instprof_t); + for (prof = instprof->profile, p_end = prof + instprof->profile_len; + prof < p_end; + prof++) + need += prof->instances_len * sizeof(int); + + if ((pdubuf = __pmFindPDUBuf((int)need)) == NULL) + return -oserror(); + + p = (__pmPDU *)pdubuf; + + /* First the profile itself */ + pduProfile = (profile_t *)p; + pduProfile->hdr.len = (int)need; + pduProfile->hdr.type = PDU_PROFILE; + /* + * note: context id may be sent twice due to protocol evolution and + * backwards compatibility issues + */ + pduProfile->hdr.from = from; + pduProfile->ctxnum = htonl(ctxnum); + pduProfile->g_state = htonl(instprof->state); + pduProfile->numprof = htonl(instprof->profile_len); + pduProfile->pad = 0; + + p += sizeof(profile_t) / sizeof(__pmPDU); + + if (instprof->profile_len) { + /* Next all the profile entries (if any) in one block */ + for (prof = instprof->profile, p_end = prof + instprof->profile_len; + prof < p_end; + prof++) { + pduInstProf = (instprof_t *)p; + pduInstProf->indom = __htonpmInDom(prof->indom); + pduInstProf->state = htonl(prof->state); + pduInstProf->numinst = htonl(prof->instances_len); + pduInstProf->pad = 0; + p += sizeof(instprof_t) / sizeof(__pmPDU); + } + + /* and then all the instances */ + for (prof = instprof->profile, p_end = prof+instprof->profile_len; + prof < p_end; + prof++) { + int j; + + /* and then the instances themselves (if any) */ + for (j = 0; j < prof->instances_len; j++, p++) + *p = htonl(prof->instances[j]); + } + } + sts = __pmXmitPDU(fd, pdubuf); + __pmUnpinPDUBuf(pdubuf); + return sts; +} + +int +__pmDecodeProfile(__pmPDU *pdubuf, int *ctxnump, __pmProfile **resultp) +{ + __pmProfile *instprof; + __pmInDomProfile *prof, *p_end; + profile_t *pduProfile; + instprof_t *pduInstProf; + __pmPDU *p = (__pmPDU *)pdubuf; + char *pdu_end; + int ctxnum; + int sts = 0; + + /* First the profile */ + pduProfile = (profile_t *)pdubuf; + pdu_end = (char*)pdubuf + pduProfile->hdr.len; + if (pdu_end - (char*)pdubuf < sizeof(profile_t)) + return PM_ERR_IPC; + + ctxnum = ntohl(pduProfile->ctxnum); + if (ctxnum < 0 || ctxnum > LIMIT_CTXNUM) + return PM_ERR_IPC; + if ((instprof = (__pmProfile *)malloc(sizeof(__pmProfile))) == NULL) + return -oserror(); + instprof->state = ntohl(pduProfile->g_state); + instprof->profile = NULL; + instprof->profile_len = ntohl(pduProfile->numprof); + if (instprof->profile_len < 0) { + sts = PM_ERR_IPC; + goto fail; + } + + p += sizeof(profile_t) / sizeof(__pmPDU); + + if (instprof->profile_len > 0) { + if (instprof->profile_len >= INT_MAX / sizeof(__pmInDomProfile) || + instprof->profile_len >= pduProfile->hdr.len) { + sts = PM_ERR_IPC; + goto fail; + } + if ((instprof->profile = (__pmInDomProfile *)calloc( + instprof->profile_len, sizeof(__pmInDomProfile))) == NULL) { + sts = -oserror(); + goto fail; + } + + /* Next the profiles (if any) all together */ + for (prof = instprof->profile, p_end = prof + instprof->profile_len; + prof < p_end; + prof++) { + if ((char *)p >= pdu_end) { + sts = PM_ERR_IPC; + goto fail; + } + pduInstProf = (instprof_t *)p; + prof->indom = __ntohpmInDom(pduInstProf->indom); + prof->state = ntohl(pduInstProf->state); + prof->instances = NULL; + prof->instances_len = ntohl(pduInstProf->numinst); + p += sizeof(instprof_t) / sizeof(__pmPDU); + } + + /* Finally, all the instances for all profiles (if any) together */ + for (prof = instprof->profile, p_end = prof+instprof->profile_len; + prof < p_end; + prof++) { + int j; + + if (prof->instances_len > 0) { + if (prof->instances_len >= INT_MAX / sizeof(int) || + prof->instances_len >= pduProfile->hdr.len) { + sts = PM_ERR_IPC; + goto fail; + } + prof->instances = (int *)calloc(prof->instances_len, sizeof(int)); + if (prof->instances == NULL) { + sts = -oserror(); + goto fail; + } + for (j = 0; j < prof->instances_len; j++, p++) { + if ((char *)p >= pdu_end) { + sts = PM_ERR_IPC; + goto fail; + } + prof->instances[j] = ntohl(*p); + } + } else if (prof->instances_len < 0) { + sts = PM_ERR_IPC; + goto fail; + } else { + prof->instances = NULL; + } + } + } + else { + instprof->profile = NULL; + } + + *resultp = instprof; + *ctxnump = ctxnum; + return 0; + +fail: + if (instprof != NULL) { + if (instprof->profile != NULL) { + for (prof = instprof->profile, p_end = prof+instprof->profile_len; + prof < p_end; + prof++) { + if (prof->instances != NULL) + free(prof->instances); + } + free(instprof->profile); + } + free(instprof); + } + return sts; +} diff --git a/src/libpcp/src/p_result.c b/src/libpcp/src/p_result.c new file mode 100644 index 0000000..50d4850 --- /dev/null +++ b/src/libpcp/src/p_result.c @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe note + * + * Because __pmFindPDUBuf() returns with a pinned pdu buffer, the + * buffer passed back from __pmEncodeResult() must also remain pinned + * (otherwise another thread could clobber the buffer after returning + * from __pmEncodeResult()) ... it is the caller of __pmEncodeResult() + * who is responsible for (a) not pinning the buffer again, and (b) + * ensuring _someone_ will unpin the buffer when it is safe to do so. + * + * Similarly, __pmDecodeResult() accepts a pinned buffer and returns + * a pmResult that (on 64-bit pointer platforms) may contain pointers + * into a second underlying pinned buffer. The input buffer remains + * pinned, the second buffer will be pinned if it is used. The caller + * will typically call pmFreeResult(), but also needs to call + * __pmUnpinPDUBuf() for the input PDU buffer. When the result contains + * pointers back into the input PDU buffer, this will be pinned _twice_ + * so the pmFreeResult() and __pmUnpinPDUBuf() calls will still be + * required. + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for pmResult (PDU_RESULT) + */ + +typedef struct { + pmID pmid; + int numval; /* no. of vlist els to follow, or error */ + int valfmt; /* insitu or pointer */ + __pmValue_PDU vlist[1]; /* zero or more */ +} vlist_t; + +typedef struct { + __pmPDUHdr hdr; + __pmTimeval timestamp; /* when returned */ + int numpmid; /* no. of PMIDs to follow */ + __pmPDU data[1]; /* zero or more */ +} result_t; + +int +__pmEncodeResult(int targetfd, const pmResult *result, __pmPDU **pdubuf) +{ + int i; + int j; + size_t need; /* bytes for the PDU */ + size_t vneed; /* additional bytes for the pmValueBlocks on the end */ + __pmPDU *_pdubuf; + __pmPDU *vbp; + result_t *pp; + vlist_t *vlp; + + need = sizeof(result_t) - sizeof(__pmPDU); + vneed = 0; + /* now add space for each vlist_t (data in result_t) */ + for (i = 0; i < result->numpmid; i++) { + pmValueSet *vsp = result->vset[i]; + /* need space for PMID and count of values (defer valfmt until + * we know numval > 0, which means there should be a valfmt) + */ + need += sizeof(pmID) + sizeof(int); + for (j = 0; j < vsp->numval; j++) { + /* plus value, instance pair */ + need += sizeof(__pmValue_PDU); + if (vsp->valfmt != PM_VAL_INSITU) { + /* plus pmValueBlock */ + vneed += PM_PDU_SIZE_BYTES(vsp->vlist[j].value.pval->vlen); + } + } + if (j) + /* optional value format, if any values present */ + need += sizeof(int); + } + /* + * Need to reserve additonal space for trailer (an int) in case the + * PDU buffer is used by __pmLogPutResult2() + */ + if ((_pdubuf = __pmFindPDUBuf((int)(need+vneed+sizeof(int)))) == NULL) + return -oserror(); + pp = (result_t *)_pdubuf; + pp->hdr.len = (int)(need+vneed); + pp->hdr.type = PDU_RESULT; + pp->timestamp.tv_sec = htonl((__int32_t)(result->timestamp.tv_sec)); + pp->timestamp.tv_usec = htonl((__int32_t)(result->timestamp.tv_usec)); + pp->numpmid = htonl(result->numpmid); + vlp = (vlist_t *)pp->data; + /* + * Note: vbp, and hence offset in sent PDU is in units of __pmPDU + */ + vbp = _pdubuf + need/sizeof(__pmPDU); + for (i = 0; i < result->numpmid; i++) { + pmValueSet *vsp = result->vset[i]; + vlp->pmid = __htonpmID(vsp->pmid); + if (vsp->numval > 0) + vlp->valfmt = htonl(vsp->valfmt); + for (j = 0; j < vsp->numval; j++) { + vlp->vlist[j].inst = htonl(vsp->vlist[j].inst); + if (vsp->valfmt == PM_VAL_INSITU) + vlp->vlist[j].value.lval = htonl(vsp->vlist[j].value.lval); + else { + /* + * pmValueBlocks are harder! + * -- need to copy the len field (len) + len bytes (vbuf) + */ + int nb; + nb = vsp->vlist[j].value.pval->vlen; + memcpy((void *)vbp, (void *)vsp->vlist[j].value.pval, nb); +#ifdef PCP_DEBUG + if ((nb % sizeof(__pmPDU)) != 0) { + /* for Purify */ + int pad; + char *padp = (char *)vbp + nb; + for (pad = sizeof(__pmPDU) - 1; pad >= (nb % sizeof(__pmPDU)); pad--) + *padp++ = '~'; /* buffer end */ + } +#endif + __htonpmValueBlock((pmValueBlock *)vbp); + /* point to the value block at the end of the PDU */ + vlp->vlist[j].value.lval = htonl((int)(vbp - _pdubuf)); + vbp += PM_PDU_SIZE(nb); + } + } + vlp->numval = htonl(vsp->numval); + if (j > 0) + vlp = (vlist_t *)((__psint_t)vlp + sizeof(*vlp) + (j-1)*sizeof(vlp->vlist[0])); + else + vlp = (vlist_t *)((__psint_t)vlp + sizeof(vlp->pmid) + sizeof(vlp->numval)); + } + *pdubuf = _pdubuf; + + /* Note _pdubuf remains pinned ... see thread-safe comments above */ + return 0; +} + +int +__pmSendResult(int fd, int from, const pmResult *result) +{ + int sts; + __pmPDU *pdubuf; + result_t *pp; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) + __pmDumpResult(stderr, result); +#endif + if ((sts = __pmEncodeResult(fd, result, &pdubuf)) < 0) + return sts; + pp = (result_t *)pdubuf; + pp->hdr.from = from; + sts = __pmXmitPDU(fd, pdubuf); + __pmUnpinPDUBuf(pdubuf); + return sts; +} + +/* + * enter here with pdubuf already pinned ... result may point into + * _another_ pdu buffer that is pinned on exit + */ +int +__pmDecodeResult(__pmPDU *pdubuf, pmResult **result) +{ + int numpmid; /* number of metrics */ + int i; /* range of metrics */ + int j; /* range over values */ + int index; + int vsize; /* size of vlist_t's in PDU buffer */ + char *pduend; /* end pointer for incoming buffer */ + char *vsplit; /* vlist/valueblock division point */ + result_t *pp; + vlist_t *vlp; + pmResult *pr; +#if defined(HAVE_64BIT_PTR) + char *newbuf; + int valfmt; + int numval; + int need; +/* + * Note: all sizes are in units of bytes ... beware that pp->data is in + * units of __pmPDU + */ + int nvsize; /* size of pmValue's after decode */ + int offset; /* differences in sizes */ + int vbsize; /* size of pmValueBlocks */ + pmValueSet *nvsp; +#elif defined(HAVE_32BIT_PTR) + pmValueSet *vsp; /* vlist_t == pmValueSet */ +#else + Bozo - unexpected sizeof pointer!! +#endif + + pp = (result_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + if (pduend - (char *)pdubuf < sizeof(result_t) - sizeof(__pmPDU)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: len=%d smaller than min %d\n", pp->hdr.len, (int)(sizeof(result_t) - sizeof(__pmPDU))); + } +#endif + return PM_ERR_IPC; + } + + numpmid = ntohl(pp->numpmid); + if (numpmid < 0 || numpmid > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: numpmid=%d negative or not smaller than PDU len %d\n", numpmid, pp->hdr.len); + } +#endif + return PM_ERR_IPC; + } + if (numpmid >= (INT_MAX - sizeof(pmResult)) / sizeof(pmValueSet *)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: numpmid=%d larger than max %ld\n", numpmid, (long)(INT_MAX - sizeof(pmResult) / sizeof(pmValueSet *))); + } +#endif + return PM_ERR_IPC; + } + if ((pr = (pmResult *)malloc(sizeof(pmResult) + + (numpmid - 1) * sizeof(pmValueSet *))) == NULL) { + return -oserror(); + } + pr->numpmid = numpmid; + pr->timestamp.tv_sec = ntohl(pp->timestamp.tv_sec); + pr->timestamp.tv_usec = ntohl(pp->timestamp.tv_usec); + +#if defined(HAVE_64BIT_PTR) + vsplit = pduend; /* smallest observed value block pointer */ + nvsize = vsize = vbsize = 0; + for (i = 0; i < numpmid; i++) { + vlp = (vlist_t *)&pp->data[vsize/sizeof(__pmPDU)]; + + if (sizeof(*vlp) - sizeof(vlp->vlist) - sizeof(int) > (pduend - (char *)vlp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] outer vlp past end of PDU buffer\n", i); + } +#endif + goto corrupt; + } + + vsize += sizeof(vlp->pmid) + sizeof(vlp->numval); + nvsize += sizeof(pmValueSet); + numval = ntohl(vlp->numval); + valfmt = ntohl(vlp->valfmt); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + pmID pmid = __ntohpmID(vlp->pmid); + char strbuf[20]; + fprintf(stderr, "vlist[%d] pmid: %s numval: %d", + i, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), numval); + } +#endif + /* numval may be negative - it holds an error code in that case */ + if (numval > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > len=%d\n", i, numval, pp->hdr.len); + } +#endif + goto corrupt; + } + if (numval > 0) { + if (sizeof(*vlp) - sizeof(vlp->vlist) > (pduend - (char *)vlp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] inner vlp past end of PDU buffer\n", i); + } +#endif + goto corrupt; + } + if (numval >= (INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > max=%ld\n", i, numval, (long)((INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU))); + } +#endif + goto corrupt; + } + vsize += sizeof(vlp->valfmt) + numval * sizeof(__pmValue_PDU); + nvsize += (numval - 1) * sizeof(pmValue); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, " valfmt: %s", + valfmt == PM_VAL_INSITU ? "insitu" : "ptr"); + } +#endif + if (valfmt != PM_VAL_INSITU) { + for (j = 0; j < numval; j++) { + __pmValue_PDU *pduvp; + pmValueBlock *pduvbp; + + pduvp = &vlp->vlist[j]; + if (sizeof(__pmValue_PDU) > (size_t)(pduend - (char *)pduvp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] intial pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + index = ntohl(pduvp->value.lval); + if (index < 0 || index > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] index=%d\n", i, j, index); + } +#endif + goto corrupt; + } + pduvbp = (pmValueBlock *)&pdubuf[index]; + if (vsplit > (char *)pduvbp) + vsplit = (char *)pduvbp; + if (sizeof(unsigned int) > (size_t)(pduend - (char *)pduvbp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] second pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + __ntohpmValueBlock(pduvbp); + if (pduvbp->vlen < PM_VAL_HDR_SIZE || pduvbp->vlen > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] vlen=%d\n", i, j, pduvbp->vlen); + } +#endif + goto corrupt; + } + if (pduvbp->vlen > (size_t)(pduend - (char *)pduvbp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] third pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + vbsize += PM_PDU_SIZE_BYTES(pduvbp->vlen); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, " len: %d type: %d", + pduvbp->vlen - PM_VAL_HDR_SIZE, pduvbp->vtype); + } +#endif + } + } + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fputc('\n', stderr); + } +#endif + } + + need = nvsize + vbsize; + offset = sizeof(result_t) - sizeof(__pmPDU) + vsize; + +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "need: %d vsize: %d nvsize: %d vbsize: %d offset: %d hdr.len: %d pduend: %p vsplit: %p (diff %d) pdubuf: %p (diff %d)\n", need, vsize, nvsize, vbsize, offset, pp->hdr.len, pduend, vsplit, (int)(pduend-vsplit), pdubuf, (int)(pduend-(char *)pdubuf)); + } +#endif + + if (need < 0 || + vsize > INT_MAX / sizeof(__pmPDU) || + vbsize > INT_MAX / sizeof(pmValueBlock) || + offset != pp->hdr.len - (pduend - vsplit) || + offset + vbsize != pduend - (char *)pdubuf) { + goto corrupt; + } + + /* the original pdubuf is already pinned so we won't allocate that again */ + if ((newbuf = (char *)__pmFindPDUBuf(need)) == NULL) { + free(pr); + return -oserror(); + } + + /* + * At this point, we have verified the contents of the incoming PDU and + * the following is set up ... + * + * From the original PDU buffer ... + * :-----:---------:-----------:----------------:---------------------: + * : Hdr :timestamp: numpmid : ... vlists ... : .. pmValueBlocks .. : + * :-----:---------:-----------:----------------:---------------------: + * <--- vsize ---> <---- vbsize ----> + * bytes bytes + * + * and in the new PDU buffer we are going to build ... + * :---------------------:---------------------: + * : ... pmValueSets ... : .. pmValueBlocks .. : + * :---------------------:---------------------: + * <--- nvsize ---> <---- vbsize ----> + * bytes bytes + */ + + if (vbsize) { + /* pmValueBlocks (if any) are copied across "as is" */ + index = vsize / sizeof(__pmPDU); + memcpy((void *)&newbuf[nvsize], (void *)&pp->data[index], vbsize); + } + + /* + * offset is a bit tricky ... _add_ the expansion due to the + * different sizes of the vlist_t and pmValueSet, and _subtract_ + * the PDU header and pmResult fields ... + */ + offset = nvsize - vsize + - (int)sizeof(pp->hdr) - (int)sizeof(pp->timestamp) + - (int)sizeof(pp->numpmid); + nvsize = vsize = 0; + for (i = 0; i < numpmid; i++) { + vlp = (vlist_t *)&pp->data[vsize/sizeof(__pmPDU)]; + nvsp = (pmValueSet *)&newbuf[nvsize]; + pr->vset[i] = nvsp; + nvsp->pmid = __ntohpmID(vlp->pmid); + nvsp->numval = ntohl(vlp->numval); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + char strbuf[20]; + fprintf(stderr, "new vlist[%d] pmid: %s numval: %d", + i, pmIDStr_r(nvsp->pmid, strbuf, sizeof(strbuf)), nvsp->numval); + } +#endif + + vsize += sizeof(nvsp->pmid) + sizeof(nvsp->numval); + nvsize += sizeof(pmValueSet); + if (nvsp->numval > 0) { + nvsp->valfmt = ntohl(vlp->valfmt); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, " valfmt: %s", + nvsp->valfmt == PM_VAL_INSITU ? "insitu" : "ptr"); + } +#endif + vsize += sizeof(nvsp->valfmt) + nvsp->numval * sizeof(__pmValue_PDU); + nvsize += (nvsp->numval - 1) * sizeof(pmValue); + for (j = 0; j < nvsp->numval; j++) { + __pmValue_PDU *vp = &vlp->vlist[j]; + pmValue *nvp = &nvsp->vlist[j]; + + nvp->inst = ntohl(vp->inst); + if (nvsp->valfmt == PM_VAL_INSITU) { + nvp->value.lval = ntohl(vp->value.lval); +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, " value: %d", nvp->value.lval); + } +#endif + } + else { + /* + * in the input PDU buffer, pval is an index to the + * start of the pmValueBlock, in units of __pmPDU + */ + index = sizeof(__pmPDU) * ntohl(vp->value.pval) + offset; + nvp->value.pval = (pmValueBlock *)&newbuf[index]; +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + int k, len; + len = nvp->value.pval->vlen - PM_VAL_HDR_SIZE; + fprintf(stderr, " len: %d type: %d value: 0x", len, + nvp->value.pval->vtype); + for (k = 0; k < len; k++) + fprintf(stderr, "%02x", nvp->value.pval->vbuf[k]); + } +#endif + } + } + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fputc('\n', stderr); + } +#endif + } + if (numpmid == 0) + __pmUnpinPDUBuf(newbuf); + +#elif defined(HAVE_32BIT_PTR) + + pr->timestamp.tv_sec = ntohl(pp->timestamp.tv_sec); + pr->timestamp.tv_usec = ntohl(pp->timestamp.tv_usec); + vlp = (vlist_t *)pp->data; + vsplit = pduend; + vsize = 0; + + /* + * Now fix up any pointers in pmValueBlocks (currently offsets into + * the PDU buffer) by adding the base address of the PDU buffer. + */ + for (i = 0; i < numpmid; i++) { + if (sizeof(*vlp) - sizeof(vlp->vlist) - sizeof(int) > (pduend - (char *)vlp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] outer vlp past end of PDU buffer\n", i); + } +#endif + goto corrupt; + } + + vsp = pr->vset[i] = (pmValueSet *)vlp; + vsp->pmid = __ntohpmID(vsp->pmid); + vsp->numval = ntohl(vsp->numval); + /* numval may be negative - it holds an error code in that case */ + if (vsp->numval > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > len=%d\n", i, vsp->numval, pp->hdr.len); + } +#endif + goto corrupt; + } + + vsize += sizeof(vsp->pmid) + sizeof(vsp->numval); + if (vsp->numval > 0) { + if (sizeof(*vlp) - sizeof(vlp->vlist) > (pduend - (char *)vlp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] inner vlp past end of PDU buffer\n", i); + } +#endif + goto corrupt; + } + if (vsp->numval >= (INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > max=%ld\n", i, vsp->numval, (long)((INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU))); + } +#endif + goto corrupt; + } + vsp->valfmt = ntohl(vsp->valfmt); + vsize += sizeof(vsp->valfmt) + vsp->numval * sizeof(__pmValue_PDU); + for (j = 0; j < vsp->numval; j++) { + __pmValue_PDU *pduvp; + pmValueBlock *pduvbp; + + pduvp = &vsp->vlist[j]; + if (sizeof(__pmValue_PDU) > (size_t)(pduend - (char *)pduvp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] initial pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + + pduvp->inst = ntohl(pduvp->inst); + if (vsp->valfmt == PM_VAL_INSITU) { + pduvp->value.lval = ntohl(pduvp->value.lval); + } else { + /* salvage pmValueBlocks from end of PDU */ + index = ntohl(pduvp->value.lval); + if (index < 0 || index > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] index=%d\n", i, j, index); + } +#endif + goto corrupt; + } + pduvbp = (pmValueBlock *)&pdubuf[index]; + if (vsplit > (char *)pduvbp) + vsplit = (char *)pduvbp; + if (sizeof(unsigned int) > (size_t)(pduend - (char *)pduvbp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] second pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + __ntohpmValueBlock(pduvbp); + if (pduvbp->vlen < PM_VAL_HDR_SIZE || pduvbp->vlen > pp->hdr.len) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] vlen=%d\n", i, j, pduvbp->vlen); + } +#endif + goto corrupt; + } + if (pduvbp->vlen > (size_t)(pduend - (char *)pduvbp)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] third pduvp past end of PDU buffer\n", i, j); + } +#endif + goto corrupt; + } + pduvp->value.pval = pduvbp; + } + } + vlp = (vlist_t *)((__psint_t)vlp + sizeof(*vlp) + (vsp->numval-1)*sizeof(vlp->vlist[0])); + } + else { + vlp = (vlist_t *)((__psint_t)vlp + sizeof(vlp->pmid) + sizeof(vlp->numval)); + } + } + if (numpmid > 0) { + if (sizeof(result_t) - sizeof(__pmPDU) + vsize != pp->hdr.len - (pduend - vsplit)) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "__pmDecodeResult: Bad: vsplit past end of PDU buffer\n"); + } +#endif + goto corrupt; + } + /* + * PDU buffer already pinned on entry, pin again so that + * the caller can safely call _both_ pmFreeResult() and + * __pmUnpinPDUBuf() ... refer to thread-safe notes above. + */ + __pmPinPDUBuf(pdubuf); + } +#endif + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) + __pmDumpResult(stderr, pr); +#endif + + /* + * Note we return with the input buffer (pdubuf) still pinned and + * for the 64-bit pointer case the new buffer (newbuf) also pinned - + * if numpmid != 0 see the thread-safe comments above + */ + *result = pr; + return 0; + +corrupt: + free(pr); + return PM_ERR_IPC; +} diff --git a/src/libpcp/src/p_text.c b/src/libpcp/src/p_text.c new file mode 100644 index 0000000..739ef5a --- /dev/null +++ b/src/libpcp/src/p_text.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +/* + * PDU for pmLookupText request (PDU_TEXT_REQ) + */ +typedef struct { + __pmPDUHdr hdr; + int ident; + int type; /* one line or help, PMID or InDom */ +} text_req_t; + +int +__pmSendTextReq(int fd, int from, int ident, int type) +{ + text_req_t *pp; + int sts; + + if ((pp = (text_req_t *)__pmFindPDUBuf(sizeof(text_req_t))) == NULL) + return -oserror(); + pp->hdr.len = sizeof(text_req_t); + pp->hdr.type = PDU_TEXT_REQ; + pp->hdr.from = from; + + if (type & PM_TEXT_PMID) + pp->ident = __htonpmID((pmID)ident); + else if (type & PM_TEXT_INDOM) + pp->ident = __htonpmInDom((pmInDom)ident); + else + pp->ident = __htonpmInDom((pmInDom)ident); + + pp->type = htonl(type); + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeTextReq(__pmPDU *pdubuf, int *ident, int *type) +{ + text_req_t *pp; + char *pduend; + + pp = (text_req_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp < sizeof(text_req_t)) + return PM_ERR_IPC; + + *type = ntohl(pp->type); + if ((*type) & PM_TEXT_PMID) + *ident = __ntohpmID(pp->ident); + else if ((*type) & PM_TEXT_INDOM) + *ident = __ntohpmInDom(pp->ident); + else + *ident = PM_INDOM_NULL; + + return 0; +} + +/* + * PDU for pmLookupText result (PDU_TEXT) + */ +typedef struct { + __pmPDUHdr hdr; + int ident; + int buflen; /* no. of chars following */ + char buffer[sizeof(int)]; /* desired text */ +} text_t; + +int +__pmSendText(int fd, int ctx, int ident, const char *buffer) +{ + text_t *pp; + size_t need; + int sts; + + need = sizeof(text_t) - sizeof(pp->buffer) + PM_PDU_SIZE_BYTES(strlen(buffer)); + if ((pp = (text_t *)__pmFindPDUBuf((int)need)) == NULL) + return -oserror(); + pp->hdr.len = (int)need; + pp->hdr.type = PDU_TEXT; + pp->hdr.from = ctx; + /* + * Note: ident argument must already be in network byte order. + * The caller has to do this because the type of ident is not + * part of the transmitted PDU_TEXT pdu; ident may be either + * a pmID or pmInDom, and so the caller must use either + * __htonpmID or __htonpmInDom (respectfully). + */ + pp->ident = ident; + + pp->buflen = (int)strlen(buffer); + memcpy((void *)pp->buffer, (void *)buffer, pp->buflen); + pp->buflen = htonl(pp->buflen); + + sts = __pmXmitPDU(fd, (__pmPDU *)pp); + __pmUnpinPDUBuf(pp); + return sts; +} + +int +__pmDecodeText(__pmPDU *pdubuf, int *ident, char **buffer) +{ + text_t *pp; + char *pduend; + int buflen; + + pp = (text_t *)pdubuf; + pduend = (char *)pdubuf + pp->hdr.len; + + if (pduend - (char*)pp < sizeof(text_t) - sizeof(int)) + return PM_ERR_IPC; + + /* + * Note: ident argument is returned in network byte order. + * The caller has to convert it to host byte order because + * the type of ident is not part of the transmitted PDU_TEXT + * pdu (ident may be either a pmID or a pmInDom. The caller + * must use either __ntohpmID() or __ntohpmInDom(), respectfully. + */ + *ident = pp->ident; + buflen = ntohl(pp->buflen); + if (buflen < 0 || buflen >= INT_MAX - 1 || buflen > pp->hdr.len) + return PM_ERR_IPC; + if (pduend - (char *)pp < sizeof(text_t) - sizeof(pp->buffer) + buflen) + return PM_ERR_IPC; + if ((*buffer = (char *)malloc(buflen+1)) == NULL) + return -oserror(); + strncpy(*buffer, pp->buffer, buflen); + (*buffer)[buflen] = '\0'; + return 0; +} diff --git a/src/libpcp/src/pdu.c b/src/libpcp/src/pdu.c new file mode 100644 index 0000000..3036976 --- /dev/null +++ b/src/libpcp/src/pdu.c @@ -0,0 +1,574 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes: + * + * maxsize - monotonic increasing and rarely changes, so use global + * mutex to protect updates, but allow reads without locking + * as seeing an unexpected newly updated value is benign + * + * On success, the result parameter from __pmGetPDU() points into a PDU + * buffer that is pinned from the call to __pmFindPDUBuf(). It is the + * responsibility of the __pmGetPDU() caller to unpin the buffer when + * it is safe to do so. + * + * __pmPDUCntIn[] and __pmPDUCntOut[] are diagnostic counters that are + * maintained with non-atomic updates ... we've decided that it is + * acceptable for their values to be subject to possible (but unlikely) + * missed updates + */ + +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + +INTERN int pmDebug; /* the real McCoy */ + +/* + * Performance Instrumentation + * ... counts binary PDUs received and sent by low 4 bits of PDU type + */ + +static unsigned int inctrs[PDU_MAX+1]; +static unsigned int outctrs[PDU_MAX+1]; +INTERN unsigned int *__pmPDUCntIn = inctrs; +INTERN unsigned int *__pmPDUCntOut = outctrs; + +#ifdef PCP_DEBUG +static int mypid = -1; +#endif +static int ceiling = PDU_CHUNK * 64; + +static struct timeval def_wait = { 10, 0 }; +static double def_timeout = 10.0; + +#define HEADER -1 +#define BODY 0 + +const struct timeval * +__pmDefaultRequestTimeout(void) +{ + static int done_default = 0; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (!done_default) { + char *timeout_str; + char *end_ptr; + if ((timeout_str = getenv("PMCD_REQUEST_TIMEOUT")) != NULL) { + def_timeout = strtod(timeout_str, &end_ptr); + if (*end_ptr != '\0' || def_timeout < 0.0) { + __pmNotifyErr(LOG_WARNING, + "ignored bad PMCD_REQUEST_TIMEOUT = '%s'\n", + timeout_str); + } + else { + def_wait.tv_sec = (int)def_timeout; /* truncate -> secs */ + if (def_timeout > (double)def_wait.tv_sec) + def_wait.tv_usec = (long)((def_timeout - (double)def_wait.tv_sec) * 1000000); + else + def_wait.tv_usec = 0; + } + } + done_default = 1; + } + PM_UNLOCK(__pmLock_libpcp); + return (&def_wait); +} + +static int +pduread(int fd, char *buf, int len, int part, int timeout) +{ + int socketipc = __pmSocketIPC(fd); + int status = 0; + int have = 0; + int onetrip = 1; + struct timeval dead_hand; + struct timeval now; + + if (timeout == -2 /*TIMEOUT_ASYNC*/) + return -EOPNOTSUPP; + + /* + * Handle short reads that may split a PDU ... + * + * The original logic here assumed that recv() would only split a + * PDU at a word (__pmPDU) boundary ... with the introduction of + * secure connections, SSL and possibly compression all lurking + * below the socket covers, this is no longer a safe assumption. + * + * So, we keep nibbling at the input stream until we have all that + * we have requested, or we timeout, or error. + */ + while (len) { + struct timeval wait; + +#if defined(IS_MINGW) /* cannot select on a pipe on Win32 - yay! */ + if (!__pmSocketIPC(fd)) { + COMMTIMEOUTS cwait = { 0 }; + + if (timeout != TIMEOUT_NEVER) + cwait.ReadTotalTimeoutConstant = timeout * 1000.0; + else + cwait.ReadTotalTimeoutConstant = def_timeout * 1000.0; + SetCommTimeouts((HANDLE)_get_osfhandle(fd), &cwait); + } + else +#endif + + /* + * either never timeout (i.e. block forever), or timeout + */ + if (timeout != TIMEOUT_NEVER) { + if (timeout > 0) { + wait.tv_sec = timeout; + wait.tv_usec = 0; + } + else + wait = def_wait; + if (onetrip) { + /* + * Need all parts of the PDU to be received by dead_hand + * This enforces a low overall timeout for the whole PDU + * (as opposed to just a timeout for individual calls to + * recv). A more invasive alternative (better) approach + * would see all I/O performed in the main event loop, + * and I/O routines transformed to continuation-passing + * style. + */ + gettimeofday(&dead_hand, NULL); + dead_hand.tv_sec += wait.tv_sec; + dead_hand.tv_usec += wait.tv_usec; + while (dead_hand.tv_usec >= 1000000) { + dead_hand.tv_usec -= 1000000; + dead_hand.tv_sec++; + } + onetrip = 0; + } + + status = __pmSocketReady(fd, &wait); + if (status > 0) { + gettimeofday(&now, NULL); + if (now.tv_sec > dead_hand.tv_sec || + (now.tv_sec == dead_hand.tv_sec && + now.tv_usec > dead_hand.tv_usec)) + status = 0; + } + if (status == 0) { + if (__pmGetInternalState() != PM_STATE_APPL) { + /* special for PMCD and friends + * Note, on Linux select would return 'time remaining' + * in timeout value, so report the expected timeout + */ + int tosec, tomsec; + + if ( timeout != TIMEOUT_NEVER && timeout > 0 ) { + tosec = (int)timeout; + tomsec = 0; + } else { + tosec = (int)def_wait.tv_sec; + tomsec = 1000*(int)def_wait.tv_usec; + } + + __pmNotifyErr(LOG_WARNING, + "pduread: timeout (after %d.%03d " + "sec) while attempting to read %d " + "bytes out of %d in %s on fd=%d", + tosec, tomsec, len - have, len, + part == HEADER ? "HDR" : "BODY", fd); + } + return PM_ERR_TIMEOUT; + } + else if (status < 0) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, "pduread: select() on fd=%d: %s", + fd, netstrerror_r(errmsg, sizeof(errmsg))); + setoserror(neterror()); + return status; + } + } + if (socketipc) { + status = __pmRecv(fd, buf, len, 0); + setoserror(neterror()); + } else { + status = read(fd, buf, len); + } + __pmOverrideLastFd(fd); + if (status < 0) + /* error */ + return status; + else if (status == 0) + /* return what we have, or nothing */ + break; + + have += status; + buf += status; + len -= status; +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "pduread(%d, ...): have %d, last read %d, still need %d\n", + fd, have, status, len); + } +#endif + } + + return have; +} + +char * +__pmPDUTypeStr_r(int type, char *buf, int buflen) +{ + char *res = NULL; + if (type == PDU_ERROR) res = "ERROR"; + else if (type == PDU_RESULT) res = "RESULT"; + else if (type == PDU_PROFILE) res = "PROFILE"; + else if (type == PDU_FETCH) res = "FETCH"; + else if (type == PDU_DESC_REQ) res = "DESC_REQ"; + else if (type == PDU_DESC) res = "DESC"; + else if (type == PDU_INSTANCE_REQ) res = "INSTANCE_REQ"; + else if (type == PDU_INSTANCE) res = "INSTANCE"; + else if (type == PDU_TEXT_REQ) res = "TEXT_REQ"; + else if (type == PDU_TEXT) res = "TEXT"; + else if (type == PDU_CONTROL_REQ) res = "CONTROL_REQ"; + else if (type == PDU_CREDS) res = "CREDS"; + else if (type == PDU_PMNS_IDS) res = "PMNS_IDS"; + else if (type == PDU_PMNS_NAMES) res = "PMNS_NAMES"; + else if (type == PDU_PMNS_CHILD) res = "PMNS_CHILD"; + else if (type == PDU_PMNS_TRAVERSE) res = "PMNS_TRAVERSE"; + else if (type == PDU_LOG_CONTROL) res = "LOG_CONTROL"; + else if (type == PDU_LOG_STATUS) res = "LOG_STATUS"; + else if (type == PDU_LOG_REQUEST) res = "LOG_REQUEST"; + else if (type == PDU_AUTH) res = "AUTH"; + if (res == NULL) + snprintf(buf, buflen, "TYPE-%d?", type); + else + snprintf(buf, buflen, "%s", res); + + return buf; +} + +const char * +__pmPDUTypeStr(int type) +{ + static char tbuf[20]; + __pmPDUTypeStr_r(type, tbuf, sizeof(tbuf)); + return tbuf; +} + +#if defined(HAVE_SIGPIPE) +/* + * Because the default handler for SIGPIPE is to exit, we always want a handler + * installed to override that so that the write() just returns an error. The + * problem is that the user might have installed one prior to the first write() + * or may install one at some later stage. This doesn't matter. As long as a + * handler other than SIG_DFL is there, all will be well. The first time that + * __pmXmitPDU is called, install SIG_IGN as the handler for SIGPIPE. If the + * user had already changed the handler from SIG_DFL, put back what was there + * before. + */ +static int sigpipe_done = 0; /* First time check for installation of + non-default SIGPIPE handler */ +static void setup_sigpipe() +{ + if (!sigpipe_done) { /* Make sure SIGPIPE is handled */ + SIG_PF user_onpipe; + user_onpipe = signal(SIGPIPE, SIG_IGN); + if (user_onpipe != SIG_DFL) /* Put user handler back */ + signal(SIGPIPE, user_onpipe); + sigpipe_done = 1; + } +} +#else +static void setup_sigpipe() { } +#endif + +int +__pmXmitPDU(int fd, __pmPDU *pdubuf) +{ + int socketipc = __pmSocketIPC(fd); + int off = 0; + int len; + __pmPDUHdr *php = (__pmPDUHdr *)pdubuf; + + setup_sigpipe(); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int j; + char *p; + int jend = PM_PDU_SIZE(php->len); + char strbuf[20]; + + /* for Purify ... */ + p = (char *)pdubuf + php->len; + while (p < (char *)pdubuf + jend*sizeof(__pmPDU)) + *p++ = '~'; /* buffer end */ + + if (mypid == -1) + mypid = (int)getpid(); + fprintf(stderr, "[%d]pmXmitPDU: %s fd=%d len=%d", + mypid, __pmPDUTypeStr_r(php->type, strbuf, sizeof(strbuf)), fd, php->len); + for (j = 0; j < jend; j++) { + if ((j % 8) == 0) + fprintf(stderr, "\n%03d: ", j); + fprintf(stderr, "%8x ", pdubuf[j]); + } + putc('\n', stderr); + } +#endif + len = php->len; + + php->len = htonl(php->len); + php->from = htonl(php->from); + php->type = htonl(php->type); + while (off < len) { + char *p = (char *)pdubuf; + int n; + + p += off; + + n = socketipc ? __pmSend(fd, p, len-off, 0) : write(fd, p, len-off); + if (n < 0) + break; + off += n; + } + php->len = ntohl(php->len); + php->from = ntohl(php->from); + php->type = ntohl(php->type); + + if (off != len) { + if (socketipc) { + if (__pmSocketClosed()) + return PM_ERR_IPC; + return neterror() ? -neterror() : PM_ERR_IPC; + } + return oserror() ? -oserror() : PM_ERR_IPC; + } + + __pmOverrideLastFd(fd); + if (php->type >= PDU_START && php->type <= PDU_FINISH) + __pmPDUCntOut[php->type-PDU_START]++; + + return off; +} + +/* result is pinned on successful return */ +int +__pmGetPDU(int fd, int mode, int timeout, __pmPDU **result) +{ + int need; + int len; + static int maxsize = PDU_CHUNK; + char *handle; + __pmPDU *pdubuf; + __pmPDU *pdubuf_prev; + __pmPDUHdr *php; + + if ((pdubuf = __pmFindPDUBuf(maxsize)) == NULL) + return -oserror(); + + /* First read - try to read the header */ + len = pduread(fd, (void *)pdubuf, sizeof(__pmPDUHdr), HEADER, timeout); + php = (__pmPDUHdr *)pdubuf; + + if (len < (int)sizeof(__pmPDUHdr)) { + if (len == -1) { + if (__pmSocketClosed()) { + len = 0; + } else { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg))); + } + } + else if (len >= (int)sizeof(php->len)) { + /* + * Have part of a PDU header. Enough for the "len" + * field to be valid, but not yet all of it - save + * what we have received and try to read some more. + * Note this can only happen once per PDU, so the + * ntohl() below will _only_ be done once per PDU. + */ + goto check_read_len; /* continue, do not return */ + } + else if (len == PM_ERR_TIMEOUT) { + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_TIMEOUT; + } + else if (len < 0) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(len, errmsg, sizeof(errmsg))); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_IPC; + } + else if (len > 0) { + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: bad len=%d", fd, len); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_IPC; + } + + /* + * end-of-file with no data + */ + __pmUnpinPDUBuf(pdubuf); + return 0; + } + +check_read_len: + php->len = ntohl(php->len); + if (php->len < (int)sizeof(__pmPDUHdr)) { + /* + * PDU length indicates insufficient bytes for a PDU header + * ... looks like DOS attack like PV 935490 + */ + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU len=%d in hdr", fd, php->len); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_IPC; + } + else if (mode == LIMIT_SIZE && php->len > ceiling) { + /* + * Guard against denial of service attack ... don't accept PDUs + * from clients that are larger than 64 Kbytes (ceiling) + * (note, pmcd and pmdas have to be able to _send_ large PDUs, + * e.g. for a pmResult or instance domain enquiry) + */ + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d bad PDU len=%d in hdr exceeds maximum client PDU size (%d)", + fd, php->len, ceiling); + + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_TOOBIG; + } + + if (len < php->len) { + /* + * need to read more ... + */ + int tmpsize; + int have = len; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (php->len > maxsize) { + tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK); + maxsize = tmpsize; + } + else + tmpsize = maxsize; + PM_UNLOCK(__pmLock_libpcp); + + pdubuf_prev = pdubuf; + if ((pdubuf = __pmFindPDUBuf(tmpsize)) == NULL) { + __pmUnpinPDUBuf(pdubuf_prev); + return -oserror(); + } + + memmove((void *)pdubuf, (void *)php, len); + __pmUnpinPDUBuf(pdubuf_prev); + + php = (__pmPDUHdr *)pdubuf; + need = php->len - have; + handle = (char *)pdubuf; + /* block until all of the PDU is received this time */ + len = pduread(fd, (void *)&handle[len], need, BODY, timeout); + if (len != need) { + if (len == PM_ERR_TIMEOUT) { + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_TIMEOUT; + } + else if (len < 0) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg))); + } + else + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: have %d, want %d, got %d", fd, have, need, len); + /* + * only report header fields if you've read enough bytes + */ + if (len > 0) + have += len; + if (have >= (int)(sizeof(php->len)+sizeof(php->type)+sizeof(php->from))) + __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x from=0x%x", php->len, (unsigned)ntohl(php->type), (unsigned)ntohl(php->from)); + else if (have >= (int)(sizeof(php->len)+sizeof(php->type))) + __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x", php->len, (unsigned)ntohl(php->type)); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_IPC; + } + } + + *result = (__pmPDU *)php; + php->type = ntohl((unsigned int)php->type); + if (php->type < 0) { + /* + * PDU type is bad ... could be a possible mem leak attack like + * https://bugzilla.redhat.com/show_bug.cgi?id=841319 + */ + __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU type=%d in hdr", fd, php->type); + __pmUnpinPDUBuf(pdubuf); + return PM_ERR_IPC; + } + php->from = ntohl((unsigned int)php->from); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDU) { + int j; + char *p; + int jend = PM_PDU_SIZE(php->len); + char strbuf[20]; + + /* for Purify ... */ + p = (char *)*result + php->len; + while (p < (char *)*result + jend*sizeof(__pmPDU)) + *p++ = '~'; /* buffer end */ + + if (mypid == -1) + mypid = (int)getpid(); + fprintf(stderr, "[%d]pmGetPDU: %s fd=%d len=%d from=%d", + mypid, __pmPDUTypeStr_r(php->type, strbuf, sizeof(strbuf)), fd, php->len, php->from); + for (j = 0; j < jend; j++) { + if ((j % 8) == 0) + fprintf(stderr, "\n%03d: ", j); + fprintf(stderr, "%8x ", (*result)[j]); + } + putc('\n', stderr); + } +#endif + if (php->type >= PDU_START && php->type <= PDU_FINISH) + __pmPDUCntIn[php->type-PDU_START]++; + + /* + * Note php points into the PDU buffer pdubuf that remains pinned + * and php is returned via the result parameter ... see the + * thread-safe comments above + */ + return php->type; +} + +int +__pmGetPDUCeiling(void) +{ + return ceiling; +} + +int +__pmSetPDUCeiling(int newceiling) +{ + if (newceiling > 0) + return (ceiling = newceiling); + return ceiling; +} + +void +__pmSetPDUCntBuf(unsigned *in, unsigned *out) +{ + __pmPDUCntIn = in; + __pmPDUCntOut = out; +} diff --git a/src/libpcp/src/pdubuf.c b/src/libpcp/src/pdubuf.c new file mode 100644 index 0000000..ac522e0 --- /dev/null +++ b/src/libpcp/src/pdubuf.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * To avoid buffer trampling, on success __pmFindPDUBuf() now returns + * a pinned PDU buffer. It is the caller's responsibility to unpin the + * PDU buffer when safe to do so. + * + * TODO now that buffers always pinned on return, we can do away + * with buf_pin and buf_pin_tail and maintain one list? + */ + +#include "pmapi.h" +#include "impl.h" +#include <assert.h> + +#define PDU_CHUNK 1024 /* unit of space allocation for PDU buffer */ + +typedef struct bufctl { + struct bufctl *bc_next; + int bc_size; + int bc_pincnt; + char *bc_buf; + char *bc_bufend; +} bufctl_t; + +static bufctl_t *buf_free; +static bufctl_t *buf_pin; +static bufctl_t *buf_pin_tail; + +#ifdef PCP_DEBUG +static void +pdubufdump(void) +{ + bufctl_t *pcp; + + PM_LOCK(__pmLock_libpcp); + if (buf_free != NULL) { + fprintf(stderr, " free pdubuf[size]:"); + for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) + fprintf(stderr, " " PRINTF_P_PFX "%p[%d]", pcp->bc_buf, pcp->bc_size); + fputc('\n', stderr); + } + + if (buf_pin != NULL) { + fprintf(stderr, " pinned pdubuf[size](pincnt):"); + for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) + fprintf(stderr, " " PRINTF_P_PFX "%p...%p[%d](%d)", pcp->bc_buf, &pcp->bc_buf[pcp->bc_size-1], pcp->bc_size, pcp->bc_pincnt); + fputc('\n', stderr); + } + PM_UNLOCK(__pmLock_libpcp); +} +#endif + +__pmPDU * +__pmFindPDUBuf(int need) +{ + bufctl_t *pcp; + __pmPDU *sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (need < 0) { + /* special diagnostic case ... dump buffer state */ +#ifdef PCP_DEBUG + fprintf(stderr, "__pmFindPDUBuf(DEBUG)\n"); + pdubufdump(); +#endif + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_size >= need) + break; + } + if (pcp == NULL) { + if ((pcp = (bufctl_t *)malloc(sizeof(*pcp))) == NULL) { + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + pcp->bc_pincnt = 0; + pcp->bc_size = PDU_CHUNK * (1 + need/PDU_CHUNK); + if ((pcp->bc_buf = (char *)valloc(pcp->bc_size)) == NULL) { + free(pcp); + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + pcp->bc_next = buf_free; + pcp->bc_bufend = &pcp->bc_buf[pcp->bc_size]; + buf_free = pcp; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDUBUF) { + fprintf(stderr, "__pmFindPDUBuf(%d) -> " PRINTF_P_PFX "%p\n", need, pcp->bc_buf); + pdubufdump(); + } +#endif + + __pmPinPDUBuf(pcp->bc_buf); + sts = (__pmPDU *)pcp->bc_buf; + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +void +__pmPinPDUBuf(void *handle) +{ + bufctl_t *pcp; + bufctl_t *prior = NULL; + + assert(((__psint_t)handle % sizeof(int)) == 0); + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend) + break; + prior = pcp; + } + + if (pcp != NULL) { + /* first pin for this buffer, move between lists */ + if (prior == NULL) + buf_free = pcp->bc_next; + else + prior->bc_next = pcp->bc_next; + pcp->bc_next = NULL; + if (buf_pin_tail != NULL) + buf_pin_tail->bc_next = pcp; + buf_pin_tail = pcp; + if (buf_pin == NULL) + buf_pin = pcp; + pcp->bc_pincnt = 1; + } + else { + for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend) + break; + } + if (pcp != NULL) + pcp->bc_pincnt++; + else { + __pmNotifyErr(LOG_WARNING, "__pmPinPDUBuf: 0x%lx not in pool!", + (unsigned long)handle); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDUBUF) + pdubufdump(); +#endif + PM_UNLOCK(__pmLock_libpcp); + return; + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "__pmPinPDUBuf(" PRINTF_P_PFX "%p) -> pdubuf=" PRINTF_P_PFX "%p, pincnt=%d\n", + handle, pcp->bc_buf, pcp->bc_pincnt); +#endif + + PM_UNLOCK(__pmLock_libpcp); + return; +} + +int +__pmUnpinPDUBuf(void *handle) +{ + bufctl_t *pcp; + bufctl_t *prior = NULL; + + assert(((__psint_t)handle % sizeof(int)) == 0); + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_buf <= (char *)handle && (char *)handle < &pcp->bc_buf[pcp->bc_size]) + break; + prior = pcp; + } + if (pcp == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDUBUF) { + fprintf(stderr, "__pmUnpinPDUBuf(" PRINTF_P_PFX "%p) -> fails\n", handle); + pdubufdump(); + } +#endif + PM_UNLOCK(__pmLock_libpcp); + return 0; + } + + if (--pcp->bc_pincnt == 0) { + if (prior == NULL) + buf_pin = pcp->bc_next; + else + prior->bc_next = pcp->bc_next; + if (buf_pin_tail == pcp) + buf_pin_tail = prior; + + pcp->bc_next = buf_free; + buf_free = pcp; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PDUBUF) + fprintf(stderr, "__pmUnpinPDUBuf(" PRINTF_P_PFX "%p) -> pdubuf=" PRINTF_P_PFX "%p, pincnt=%d\n", + handle, pcp->bc_buf, pcp->bc_pincnt); +#endif + + PM_UNLOCK(__pmLock_libpcp); + return 1; +} + +void +__pmCountPDUBuf(int need, int *alloc, int *free) +{ + bufctl_t *pcp; + int count; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + count = 0; + for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_size >= need) + count++; + } + *alloc = count; + + count = 0; + for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) { + if (pcp->bc_size >= need) + count++; + } + *free = count; + *alloc += count; + + PM_UNLOCK(__pmLock_libpcp); + return; +} diff --git a/src/libpcp/src/pmns.c b/src/libpcp/src/pmns.c new file mode 100644 index 0000000..7602922 --- /dev/null +++ b/src/libpcp/src/pmns.c @@ -0,0 +1,2559 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * locerr - no serious side-effects, most unlikely to be used, and + * repeated calls are likely to produce the same result, so don't bother + * to make thread-safe + */ + +#include <sys/stat.h> +#include <stddef.h> +#include <assert.h> +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +/* token types */ +#define NAME 1 +#define PATH 2 +#define PMID 3 +#define LBRACE 4 +#define RBRACE 5 +#define BOGUS 10 + +#define UNKNOWN_MARK_STATE -1 /* tree not all marked the same way */ +/* + * Note: bit masks below are designed to clear and set the "flag" field + * of a __pmID_int (i.e. a PMID) + */ +#define PMID_MASK 0x7fffffff /* 31 bits of PMID */ +#define MARK_BIT 0x80000000 /* mark bit */ + + +static int lineno; +static char linebuf[256]; +static char *linep; +static char fname[256]; +static char tokbuf[256]; +static pmID tokpmid; +static int seenpmid; + +static __pmnsNode *seen; /* list of pass-1 subtree nodes */ + +/* Last modification time for loading main_pmns file. */ +#if defined(HAVE_STAT_TIMESTRUC) +static timestruc_t last_mtim; +#elif defined(HAVE_STAT_TIMESPEC) +static struct timespec last_mtim; +#elif defined(HAVE_STAT_TIMESPEC_T) +static timespec_t last_mtim; +#elif defined(HAVE_STAT_TIME_T) +static time_t last_mtim; +#else +!bozo! +#endif + +/* The curr_pmns points to PMNS to use for API ops. + * Curr_pmns will point to either the main_pmns or + * a pmns from a version 2 archive context. + */ +static __pmnsTree *curr_pmns; + +/* The main_pmns points to the loaded PMNS (not from archive). */ +static __pmnsTree *main_pmns; + + +/* == 1 if PMNS loaded and __pmExportPMNS has been called */ +static int export; + +static int havePmLoadCall; +static int useExtPMNS; /* set by __pmUsePMNS() */ + +static int load(const char *filename, int dupok); +static __pmnsNode *locate(const char *name, __pmnsNode *root); + + +/* + * Set current pmns to an externally supplied PMNS. + * Useful for testing the API routines during debugging. + */ +void +__pmUsePMNS(__pmnsTree *t) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + useExtPMNS = 1; + curr_pmns = t; + PM_UNLOCK(__pmLock_libpcp); +} + +static char * +pmPMNSLocationStr(int location) +{ + if (location < 0) { + /* see thread-safe note above */ + static char locerr[PM_MAXERRMSGLEN]; + return pmErrStr_r(location, locerr, sizeof(locerr)); + } + + switch(location) { + case PMNS_LOCAL: return "Local"; + case PMNS_REMOTE: return "Remote"; + case PMNS_ARCHIVE: return "Archive"; + } + return "Internal Error"; +} + + +static int +LoadDefault(char *reason_msg) +{ + if (main_pmns == NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, + "pmGetPMNSLocation: Loading local PMNS for %s PMAPI context\n", + reason_msg); + } +#endif + if (load(PM_NS_DEFAULT, 0) < 0) + return PM_ERR_NOPMNS; + else + return PMNS_LOCAL; + } + return PMNS_LOCAL; +} + +/* + * Return the pmns_location. Possibly load the default PMNS. + */ +int +pmGetPMNSLocation(void) +{ + int pmns_location = PM_ERR_NOPMNS; + int n; + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (useExtPMNS) { + PM_UNLOCK(__pmLock_libpcp); + pmns_location = PMNS_LOCAL; + goto done; + } + PM_UNLOCK(__pmLock_libpcp); + + /* + * Determine if we are to use PDUs or local PMNS file. + * Load PMNS if necessary. + */ + if (!havePmLoadCall) { + __pmContext *ctxp; + int version; + + if ((n = pmWhichContext()) >= 0 && (ctxp = __pmHandleToPtr(n)) != NULL) { + switch(ctxp->c_type) { + case PM_CONTEXT_HOST: + if (ctxp->c_pmcd->pc_fd == -1) { + pmns_location = PM_ERR_IPC; + goto done; + } + if ((sts = version = __pmVersionIPC(ctxp->c_pmcd->pc_fd)) < 0) { + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(LOG_ERR, + "pmGetPMNSLocation: version lookup failed " + "(context=%d, fd=%d): %s", + n, ctxp->c_pmcd->pc_fd, pmErrStr_r(sts, errmsg, sizeof(errmsg))); + pmns_location = PM_ERR_NOPMNS; + } + else if (version == PDU_VERSION2) { + pmns_location = PMNS_REMOTE; + } + else { + __pmNotifyErr(LOG_ERR, + "pmGetPMNSLocation: bad host PDU version " + "(context=%d, fd=%d, ver=%d)", + n, ctxp->c_pmcd->pc_fd, version); + pmns_location = PM_ERR_NOPMNS; + } + break; + + case PM_CONTEXT_LOCAL: + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) + /* Local context requires single-threaded applications */ + pmns_location = PM_ERR_THREAD; + else + pmns_location = LoadDefault("local"); + break; + + case PM_CONTEXT_ARCHIVE: + version = ctxp->c_archctl->ac_log->l_label.ill_magic & 0xff; + if (version == PM_LOG_VERS02) { + pmns_location = PMNS_ARCHIVE; + PM_LOCK(__pmLock_libpcp); + curr_pmns = ctxp->c_archctl->ac_log->l_pmns; + PM_UNLOCK(__pmLock_libpcp); + } + else { + __pmNotifyErr(LOG_ERR, "pmGetPMNSLocation: bad archive " + "version (context=%d, fd=%d, ver=%d)", + n, ctxp->c_pmcd->pc_fd, version); + pmns_location = PM_ERR_NOPMNS; + } + break; + + default: + __pmNotifyErr(LOG_ERR, "pmGetPMNSLocation: bogus context " + "type: %d", ctxp->c_type); + pmns_location = PM_ERR_NOPMNS; + break; + } + PM_UNLOCK(ctxp->c_lock); + } + else { + pmns_location = PM_ERR_NOPMNS; /* no context for client */ + } + } + else { /* have explicit external load call */ + if (main_pmns == NULL) + pmns_location = PM_ERR_NOPMNS; + else + pmns_location = PMNS_LOCAL; + } + + PM_LOCK(__pmLock_libpcp); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + static int last_pmns_location = -1; + + if (pmns_location != last_pmns_location) { + fprintf(stderr, "pmGetPMNSLocation() -> %s\n", + pmPMNSLocationStr(pmns_location)); + last_pmns_location = pmns_location; + } + } +#endif + + /* fix up curr_pmns for API ops */ + if (pmns_location == PMNS_LOCAL) + curr_pmns = main_pmns; + PM_UNLOCK(__pmLock_libpcp); + +done: + return pmns_location; +} + +/* + * Our own PMNS locator. Don't distinguish between ARCHIVE or LOCAL. + */ +static int +GetLocation(void) +{ + int loc = pmGetPMNSLocation(); + + if (loc == PMNS_ARCHIVE) + return PMNS_LOCAL; + return loc; +} + +/* + * For debugging, call via __pmDumpNameSpace() or __pmDumpNameNode() + * + * verbosity is 0 (name), 1 (names and pmids) or 2 (names, pmids and + * linked-list structures) + */ +static void +dumptree(FILE *f, int level, __pmnsNode *rp, int verbosity) +{ + int i; + __pmID_int *pp; + + if (rp != NULL) { + if (verbosity > 1) + fprintf(f, "" PRINTF_P_PFX "%p", rp); + for (i = 0; i < level; i++) { + fprintf(f, " "); + } + fprintf(f, " %-16.16s", rp->name); + pp = (__pmID_int *)&rp->pmid; + if (verbosity > 0 && rp->first == NULL) + fprintf(f, " %d %d.%d.%d 0x%08x", rp->pmid, + pp->domain, pp->cluster, pp->item, + rp->pmid); + if (verbosity > 1) { + fprintf(f, "\t[first: "); + if (rp->first) fprintf(f, "" PRINTF_P_PFX "%p", rp->first); + else fprintf(f, "<null>"); + fprintf(f, " next: "); + if (rp->next) fprintf(f, "" PRINTF_P_PFX "%p", rp->next); + else fprintf(f, "<null>"); + fprintf(f, " parent: "); + if (rp->parent) fprintf(f, "" PRINTF_P_PFX "%p", rp->parent); + else fprintf(f, "<null>"); + fprintf(f, " hash: "); + if (rp->hash) fprintf(f, "" PRINTF_P_PFX "%p", rp->hash); + else fprintf(f, "<null>"); + } + fputc('\n', f); + dumptree(f, level+1, rp->first, verbosity); + dumptree(f, level, rp->next, verbosity); + } +} + +static void +err(char *s) +{ + if (lineno > 0) + pmprintf("[%s:%d] ", fname, lineno); + pmprintf("Error Parsing ASCII PMNS: %s\n", s); + if (lineno > 0) { + char *p; + pmprintf(" %s", linebuf); + for (p = linebuf; *p; p++) + ; + if (p[-1] != '\n') + pmprintf("\n"); + if (linep) { + p = linebuf; + for (p = linebuf; p < linep; p++) { + if (!isspace((int)*p)) + *p = ' '; + } + *p++ = '^'; + *p++ = '\n'; + *p = '\0'; + pmprintf(" %s", linebuf); + } + } + pmflush(); +} + +/* + * lexical analyser for loading the ASCII pmns + */ +static int +lex(int reset) +{ + static int first = 1; + static FILE *fin; + static char *lp; + char *tp; + int colon; + int type; + int d, c, i; + __pmID_int pmid_int; + + if (reset) { + /* reset! */ + linep = NULL; + first = 1; + return 0; + } + + if (first) { + char *alt; + char cmd[80+MAXPATHLEN]; + + first = 0; + if ((alt = getenv("PCP_ALT_CPP")) != NULL) { + /* $PCP_ALT_CPP used in the build before pmcpp installed */ + snprintf(cmd, sizeof(cmd), "%s %s", alt, fname); + } + else { + /* the normal case ... */ + int sep = __pmPathSeparator(); + char *bin_dir = pmGetConfig("PCP_BINADM_DIR"); + snprintf(cmd, sizeof(cmd), "%s%c%s %s", bin_dir, sep, "pmcpp" EXEC_SUFFIX, fname); + } + + fin = popen(cmd, "r"); + if (fin == NULL) + return -oserror(); + + lp = linebuf; + *lp = '\0'; + } + + while (*lp && isspace((int)*lp)) lp++; + + while (*lp == '\0') { + for ( ; ; ) { + char *p; + char *q; + int inspace = 0; + + if (fgets(linebuf, sizeof(linebuf), fin) == NULL) { + if (pclose(fin) != 0) { + lineno = -1; /* We're outside of line counting range now */ + err("pmcpp returned non-zero exit status"); + return PM_ERR_PMNS; + } else { + return 0; + } + } + for (q = p = linebuf; *p; p++) { + if (isspace((int)*p)) { + if (!inspace) { + if (q > linebuf && q[-1] != ':') + *q++ = *p; + inspace = 1; + } + } + else if (*p == ':') { + if (inspace) { + q--; + inspace = 0; + } + *q++ = *p; + } + else { + *q++ = *p; + inspace = 0; + } + } + if (p[-1] != '\n') { + err("Absurdly long line, cannot recover"); + return PM_ERR_PMNS; + } + *q = '\0'; + if (linebuf[0] == '#') { + /* pmcpp line number control line */ + if (sscanf(linebuf, "# %d \"%s", &lineno, fname) != 2) { + err("Illegal line number control number"); + return PM_ERR_PMNS; + } + --lineno; + for (p = fname; *p; p++) + ; + *--p = '\0'; + continue; + } + else + lineno++; + lp = linebuf; + while (*lp && isspace((int)*lp)) lp++; + break; + } + } + + linep = lp; + tp = tokbuf; + while (!isspace((int)*lp)) + *tp++ = *lp++; + *tp = '\0'; + + if (tokbuf[0] == '{' && tokbuf[1] == '\0') return LBRACE; + else if (tokbuf[0] == '}' && tokbuf[1] == '\0') return RBRACE; + else if (isalpha((int)tokbuf[0])) { + type = NAME; + for (tp = &tokbuf[1]; *tp; tp++) { + if (*tp == '.') + type = PATH; + else if (!isalpha((int)*tp) && !isdigit((int)*tp) && *tp != '_') + break; + } + if (*tp == '\0') return type; + } + colon = 0; + for (tp = tokbuf; *tp; tp++) { + if (*tp == ':') { + if (++colon > 3) return BOGUS; + } + else if (!isdigit((int)*tp) && *tp != '*') return BOGUS; + } + + /* + * Internal PMID format + * domain 9 bits + * cluster 12 bits + * item 10 bits + */ + if (sscanf(tokbuf, "%d:%d:%d", &d, &c, &i) == 3) { + if (d > 510) { + err("Illegal domain field in PMID"); + return BOGUS; + } + else if (c > 4095) { + err("Illegal cluster field in PMID"); + return BOGUS; + } + else if (i > 1023) { + err("Illegal item field in PMID"); + return BOGUS; + } + pmid_int.flag = 0; + pmid_int.domain = d; + pmid_int.cluster = c; + pmid_int.item = i; + } + else { + for (tp = tokbuf; *tp; tp++) { + if (*tp == ':') { + if (strcmp("*:*", ++tp) != 0) { + err("Illegal PMID"); + return BOGUS; + } + break; + } + } + if (sscanf(tokbuf, "%d:", &d) != 1) { + err("Illegal PMID"); + return BOGUS; + } + if (d > 510) { + err("Illegal domain field in dynamic PMID"); + return BOGUS; + } + else { + /* + * this node is the base of a dynamic subtree in the PMNS + * ... identified by setting the domain field to the reserved + * value DYNAMIC_PMID and storing the real domain of the PMDA + * that can enumerate the subtree in the cluster field, while + * the item field is not used (and set to zero) + */ + pmid_int.flag = 0; + pmid_int.domain = DYNAMIC_PMID; + pmid_int.cluster = d; + pmid_int.item = 0; + } + } + tokpmid = *(pmID *)&pmid_int; + + return PMID; +} + +/* + * Remove the named node from the seen list and return it. + * The seen-list is a list of subtrees from pass 1. + */ + +static __pmnsNode * +findseen(char *name) +{ + __pmnsNode *np; + __pmnsNode *lnp; /* last np */ + + for (np = seen, lnp = NULL; np != NULL; lnp = np, np = np->next) { + if (strcmp(np->name, name) == 0) { + if (np == seen) + seen = np->next; + else + lnp->next = np->next; + np->next = NULL; + return np; + } + } + return NULL; +} + +/* + * Attach the subtrees from pass-1 to form a whole + * connected tree. + */ +static int +attach(char *base, __pmnsNode *rp) +{ + int i; + __pmnsNode *np; + __pmnsNode *xp; + char *path; + + if (rp != NULL) { + for (np = rp->first; np != NULL; np = np->next) { + if (np->pmid == PM_ID_NULL) { + /* non-terminal node ... */ + if (*base == '\0') { + if ((path = (char *)malloc(strlen(np->name)+1)) == NULL) + return -oserror(); + strcpy(path, np->name); + } + else { + if ((path = (char *)malloc(strlen(base)+strlen(np->name)+2)) == NULL) + return -oserror(); + strcpy(path, base); + strcat(path, "."); + strcat(path, np->name); + } + if ((xp = findseen(path)) == NULL) { + snprintf(linebuf, sizeof(linebuf), "Cannot find definition for non-terminal node \"%s\" in name space", + path); + err(linebuf); + free(path); + return PM_ERR_PMNS; + } + np->first = xp->first; + /* node xp and name no longer needed */ + free(xp->name); + free(xp); + seenpmid--; + i = attach(path, np); + free(path); + if (i != 0) + return i; + } + } + } + return 0; +} + +/* + * Create a fullpath name by walking from the current + * tree node up to the root. + */ +static int +backname(__pmnsNode *np, char **name) +{ + __pmnsNode *xp; + char *p; + int nch; + + nch = 0; + xp = np; + while (xp->parent != NULL) { + nch += (int)strlen(xp->name)+1; + xp = xp->parent; + } + + if ((p = (char *)malloc(nch)) == NULL) + return -oserror(); + + p[--nch] = '\0'; + xp = np; + while (xp->parent != NULL) { + int xl; + + xl = (int)strlen(xp->name); + nch -= xl; + strncpy(&p[nch], xp->name, xl); + xp = xp->parent; + if (xp->parent == NULL) + break; + else + p[--nch] = '.'; + } + *name = p; + + return 0; +} + +/* + * Fixup the parent pointers of the tree. + * Fill in the hash table with nodes from the tree. + * Hashing is done on pmid. + */ +static int +backlink(__pmnsTree *tree, __pmnsNode *root, int dupok) +{ + __pmnsNode *np; + int status; + + for (np = root->first; np != NULL; np = np->next) { + np->parent = root; + if (np->pmid != PM_ID_NULL) { + int i; + __pmnsNode *xp; + i = np->pmid % tree->htabsize; + for (xp = tree->htab[i]; xp != NULL; xp = xp->hash) { + if (xp->pmid == np->pmid && !dupok && + (pmid_domain(np->pmid) != DYNAMIC_PMID || pmid_item(np->pmid) != 0)) { + char *nn, *xn; + char strbuf[20]; + backname(np, &nn); + backname(xp, &xn); + snprintf(linebuf, sizeof(linebuf), "Duplicate metric id (%s) in name space for metrics \"%s\" and \"%s\"\n", + pmIDStr_r(np->pmid, strbuf, sizeof(strbuf)), nn, xn); + err(linebuf); + free(nn); + free(xn); + return PM_ERR_PMNS; + } + } + np->hash = tree->htab[i]; + tree->htab[i] = np; + } + if ((status = backlink(tree, np, dupok))) + return status; + } + return 0; +} + +/* + * Build up the whole tree by attaching the subtrees + * from the seen list. + * Create the hash table keyed on pmid. + * + */ +static int +pass2(int dupok) +{ + __pmnsNode *np; + int status; + + lineno = -1; + + main_pmns = (__pmnsTree*)malloc(sizeof(*main_pmns)); + if (main_pmns == NULL) { + return -oserror(); + } + + /* Get the root subtree out of the seen list */ + if ((main_pmns->root = findseen("root")) == NULL) { + err("No name space entry for \"root\""); + return PM_ERR_PMNS; + } + + if (findseen("root") != NULL) { + err("Multiple name space entries for \"root\""); + return PM_ERR_PMNS; + } + + /* Build up main tree from subtrees in seen-list */ + if ((status = attach("", main_pmns->root))) + return status; + + /* Make sure all subtrees have been used in the main tree */ + for (np = seen; np != NULL; np = np->next) { + snprintf(linebuf, sizeof(linebuf), "Disconnected subtree (\"%s\") in name space", np->name); + err(linebuf); + status = PM_ERR_PMNS; + } + if (status) + return status; + + main_pmns->symbol = NULL; + main_pmns->contiguous = 0; + main_pmns->mark_state = UNKNOWN_MARK_STATE; + + return __pmFixPMNSHashTab(main_pmns, seenpmid, dupok); +} + + +/* + * clear/set the "mark" bit used by pmTrimNameSpace, for all pmids + */ +static void +mark_all(__pmnsTree *pmns, int bit) +{ + int i; + __pmnsNode *np; + __pmnsNode *pp; + + if (pmns->mark_state == bit) + return; + + pmns->mark_state = bit; + for (i = 0; i < pmns->htabsize; i++) { + for (np = pmns->htab[i]; np != NULL; np = np->hash) { + for (pp = np ; pp != NULL; pp = pp->parent) { + if (bit) + pp->pmid |= MARK_BIT; + else + pp->pmid &= ~MARK_BIT; + } + } + } +} + +/* + * clear/set the "mark" bit used by pmTrimNameSpace, for one pmid, and + * for all parent nodes on the path to the root of the PMNS + */ +static void +mark_one(__pmnsTree *pmns, pmID pmid, int bit) +{ + __pmnsNode *np; + + if (pmns->mark_state == bit) + return; + + pmns->mark_state = UNKNOWN_MARK_STATE; + for (np = pmns->htab[pmid % pmns->htabsize]; np != NULL; np = np->hash) { + if ((np->pmid & PMID_MASK) == (pmid & PMID_MASK)) { + for ( ; np != NULL; np = np->parent) { + if (bit) + np->pmid |= MARK_BIT; + else + np->pmid &= ~MARK_BIT; + } + return; + } + } +} + + +/* + * Create a new empty PMNS for Adding nodes to. + * Use with __pmAddPMNSNode() and __pmFixPMNSHashTab() + */ +int +__pmNewPMNS(__pmnsTree **pmns) +{ + __pmnsTree *t = NULL; + __pmnsNode *np = NULL; + + t = (__pmnsTree*)malloc(sizeof(*main_pmns)); + if (t == NULL) + return -oserror(); + + /* Insert the "root" node first */ + if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL) { + free(t); + return -oserror(); + } + np->pmid = PM_ID_NULL; + np->parent = np->first = np->hash = np->next = NULL; + np->name = strdup("root"); + if (np->name == NULL) { + free(t); + free(np); + return -oserror(); + } + + t->root = np; + t->htab = NULL; + t->htabsize = 0; + t->symbol = NULL; + t->contiguous = 0; + t->mark_state = UNKNOWN_MARK_STATE; + + *pmns = t; + return 0; +} + +/* + * Go through the tree and build a hash table. + * Fix up parent links while we're there. + * Unmark all nodes. + */ +int +__pmFixPMNSHashTab(__pmnsTree *tree, int numpmid, int dupok) +{ + int sts; + int htabsize = numpmid/5; + + /* + * make the average hash list no longer than 5, and the number + * of hash table entries not a multiple of 2, 3 or 5 + */ + if (htabsize % 2 == 0) htabsize++; + if (htabsize % 3 == 0) htabsize += 2; + if (htabsize % 5 == 0) htabsize += 2; + tree->htabsize = htabsize; + tree->htab = (__pmnsNode **)calloc(htabsize, sizeof(__pmnsNode *)); + if (tree->htab == NULL) + return -oserror(); + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((sts = backlink(tree, tree->root, dupok)) < 0) { + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + mark_all(tree, 0); + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + + +/* + * Add a new node for fullpath, name, with pmid. + * Does NOT update the hash table; + * need to call __pmFixPMNSHashTab() for that. + * Recursive routine. + */ + +static int +AddPMNSNode(__pmnsNode *root, int pmid, const char *name) +{ + __pmnsNode *np = NULL; + const char *tail; + int nch; + + /* Traverse until '.' or '\0' */ + for (tail = name; *tail && *tail != '.'; tail++) + ; + + nch = (int)(tail - name); + + /* Compare name with all the child nodes */ + for (np = root->first; np != NULL; np = np->next) { + if (strncmp(name, np->name, (int)nch) == 0 && np->name[(int)nch] == '\0') + break; + } + + if (np == NULL) { /* no match with child */ + __pmnsNode *parent_np = root; + const char *name_p = name; + int is_first = 1; + + /* create nodes until reach leaf */ + + for ( ; ; ) { + if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL) + return -oserror(); + + /* fixup name */ + if ((np->name = (char *)malloc(nch+1)) == NULL) { + free(np); + return -oserror(); + } + strncpy(np->name, name_p, nch); + np->name[nch] = '\0'; + + /* fixup some links */ + np->first = np->hash = np->next = NULL; + np->parent = parent_np; + if (is_first) { + is_first = 0; + if (root->first != NULL) { + /* chuck new node at front of list */ + np->next = root->first; + } + } + parent_np->first = np; + + /* at this stage, assume np is a non-leaf */ + np->pmid = PM_ID_NULL; + + parent_np = np; + if (*tail == '\0') + break; + name_p += nch+1; /* skip over node + dot */ + for (tail = name_p; *tail && *tail != '.'; tail++) + ; + nch = (int)(tail - name_p); + } + + np->pmid = pmid; /* set pmid of leaf node */ + return 0; + } + else if (*tail == '\0') { /* matched with whole path */ + if (np->pmid != pmid) + return PM_ERR_PMID; + else + return 0; + } + else { + return AddPMNSNode(np, pmid, tail+1); /* try matching with rest of pathname */ + } + +} + + +/* + * Add a new node for fullpath, name, with pmid. + * NOTE: Need to call __pmFixPMNSHashTab() to update hash table + * when have finished adding nodes. + */ +int +__pmAddPMNSNode(__pmnsTree *tree, int pmid, const char *name) +{ + if (tree->contiguous) { + /* Cannot add node to contiguously allocated tree! */ + return -EINVAL; + } + + return AddPMNSNode(tree->root, pmid, name); +} + +/* + * fsa for parser + * + * old token new + * 0 NAME 1 + * 0 PATH 1 + * 1 LBRACE 2 + * 2 NAME 3 + * 2 RBRACE 0 + * 3 NAME 3 + * 3 PMID 2 + * 3 RBRACE 0 + */ +static int +loadascii(int dupok) +{ + int state = 0; + int type; + __pmnsNode *np = NULL; /* pander to gcc */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) + fprintf(stderr, "loadascii(file=%s)\n", fname); +#endif + + + /* do some resets */ + lex(1); /* reset analyzer */ + seen = NULL; /* make seen-list empty */ + seenpmid = 0; + + + if (access(fname, R_OK) == -1) { + snprintf(linebuf, sizeof(linebuf), "Cannot open \"%s\"", fname); + err(linebuf); + return -oserror(); + } + lineno = 1; + + while ((type = lex(0)) > 0) { + switch (state) { + + case 0: + if (type != NAME && type != PATH) { + err("Expected NAME or PATH"); + return PM_ERR_PMNS; + } + state = 1; + break; + + case 1: + if (type != LBRACE) { + err("{ expected"); + return PM_ERR_PMNS; + } + state = 2; + break; + + case 2: + if (type == NAME) { + state = 3; + } + else if (type == RBRACE) { + state = 0; + } + else { + err("Expected NAME or }"); + return PM_ERR_PMNS; + } + break; + + case 3: + if (type == NAME) { + state = 3; + } + else if (type == PMID) { + np->pmid = tokpmid; + state = 2; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + char strbuf[20]; + fprintf(stderr, "pmLoadNameSpace: %s -> %s\n", + np->name, pmIDStr_r(np->pmid, strbuf, sizeof(strbuf))); + } +#endif + } + else if (type == RBRACE) { + state = 0; + } + else { + err("Expected NAME, PMID or }"); + return PM_ERR_PMNS; + } + break; + + } + + if (state == 1 || state == 3) { + if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL) + return -oserror(); + seenpmid++; + if ((np->name = (char *)malloc(strlen(tokbuf)+1)) == NULL) { + free(np); + return -oserror(); + } + strcpy(np->name, tokbuf); + np->first = np->hash = np->next = np->parent = NULL; + np->pmid = PM_ID_NULL; + if (state == 1) { + np->next = seen; + seen = np; + } + else { + if (seen->hash) + seen->hash->next = np; + else + seen->first = np; + seen->hash = np; + } + } + else if (state == 0) { + if (seen) { + __pmnsNode *xp; + + for (np = seen->first; np != NULL; np = np->next) { + for (xp = np->next; xp != NULL; xp = xp->next) { + if (strcmp(xp->name, np->name) == 0) { + snprintf(linebuf, sizeof(linebuf), "Duplicate name \"%s\" in subtree for \"%s\"\n", + np->name, seen->name); + err(linebuf); + return PM_ERR_PMNS; + } + } + } + } + } + } + + if (type == 0) + type = pass2(dupok); + + + if (type == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) + fprintf(stderr, "Loaded ASCII PMNS\n"); +#endif + } + + return type; +} + +static const char * +getfname(const char *filename) +{ + /* + * 0xffffffff is there to maintain backwards compatibility with PCP 1.0 + */ + if (filename == PM_NS_DEFAULT || (__psint_t)filename == 0xffffffff) { + char *def_pmns; + + def_pmns = getenv("PMNS_DEFAULT"); + if (def_pmns != NULL) { + /* get default PMNS name from environment */ + return def_pmns; + } + else { + static char repname[MAXPATHLEN]; + int sep = __pmPathSeparator(); + snprintf(repname, sizeof(repname), "%s%c" "pmns" "%c" "root", + pmGetConfig("PCP_VAR_DIR"), sep, sep); + return repname; + } + } + return filename; +} + +int +__pmHasPMNSFileChanged(const char *filename) +{ + const char *f; + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + f = getfname(filename); + if (f == NULL) { + /* error encountered -> must have changed :) */ + sts = 1; + goto done; + } + + /* if still using same filename ... */ + if (strcmp(f, fname) == 0) { + struct stat statbuf; + + if (stat(f, &statbuf) == 0) { + /* If the modification times have changed */ +#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, + "__pmHasPMNSFileChanged(%s) -> %s last=%d now=%d\n", + filename == PM_NS_DEFAULT || + (__psint_t)filename == 0xffffffff ? + "PM_NS_DEFAULT" : filename, + f, (int)last_mtim, (int)statbuf.st_mtime); + } +#endif + sts = (statbuf.st_mtime == last_mtim) ? 0 : 1; + goto done; +#elif defined(HAVE_ST_MTIME_WITH_SPEC) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, + "__pmHasPMNSFileChanged(%s) -> %s last=%d.%09ld now=%d.%09ld\n", + filename == PM_NS_DEFAULT || + (__psint_t)filename == 0xffffffff ? + "PM_NS_DEFAULT" : filename, + f, (int)last_mtim.tv_sec, last_mtim.tv_nsec, + (int)statbuf.st_mtimespec.tv_sec, + statbuf.st_mtimespec.tv_nsec); + } +#endif + sts = (statbuf.st_mtimespec.tv_sec == last_mtim.tv_sec && + statbuf.st_mtimespec.tv_nsec == last_mtim.tv_nsec) ? 0 : 1; + goto done; +#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, + "__pmHasPMNSFileChanged(%s) -> %s last=%d.%09ld now=%d.%09ld\n", + filename == PM_NS_DEFAULT || + (__psint_t)filename == 0xffffffff ? + "PM_NS_DEFAULT" : filename, + f, (int)last_mtim.tv_sec, last_mtim.tv_nsec, + (int)statbuf.st_mtim.tv_sec, statbuf.st_mtim.tv_nsec); + } +#endif + sts = (statbuf.st_mtim.tv_sec == last_mtim.tv_sec && + (statbuf.st_mtim.tv_nsec == last_mtim.tv_nsec)) ? 0 : 1; + goto done; +#else +!bozo! +#endif + } + else { + /* error encountered -> must have changed */ + sts = 1; + goto done; + } + } + /* different filenames at least */ + sts = 1; + +done: + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +static int +load(const char *filename, int dupok) +{ + int i = 0; + + if (main_pmns != NULL) { + if (export) { + export = 0; + + /* + * drop the loaded PMNS ... huge memory leak, but it is + * assumed the caller has saved the previous PMNS after calling + * __pmExportPMNS() ... only user of this service is pmnsmerge + */ + main_pmns = NULL; + } + else { + return PM_ERR_DUPPMNS; + } + } + + strcpy(fname, getfname(filename)); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) + fprintf(stderr, "load(name=%s, dupok=%d) lic case=%d fname=%s\n", + filename, dupok, i, fname); +#endif + + /* Note modification time of pmns file */ + { + struct stat statbuf; + + if (stat(fname, &statbuf) == 0) { +#if defined(HAVE_ST_MTIME_WITH_E) + last_mtim = statbuf.st_mtime; /* possible struct assignment */ +#elif defined(HAVE_ST_MTIME_WITH_SPEC) + last_mtim = statbuf.st_mtimespec; /* possible struct assignment */ +#else + last_mtim = statbuf.st_mtim; /* possible struct assignment */ +#endif + } + } + + /* + * load ASCII PMNS + */ + return loadascii(dupok); +} + +/* + * just for pmnsmerge to use + */ +__pmnsTree* +__pmExportPMNS(void) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + export = 1; + PM_UNLOCK(__pmLock_libpcp); + + /* + * Warning: this is _not_ thread-safe, and cannot be guarded/protected + */ + return main_pmns; +} + +/* + * Find and return the named node in the tree, root. + */ +static __pmnsNode * +locate(const char *name, __pmnsNode *root) +{ + const char *tail; + ptrdiff_t nch; + __pmnsNode *np; + + /* Traverse until '.' or '\0' */ + for (tail = name; *tail && *tail != '.'; tail++) + ; + + nch = tail - name; + + /* Compare name with all the child nodes */ + for (np = root->first; np != NULL; np = np->next) { + if (strncmp(name, np->name, (int)nch) == 0 && np->name[(int)nch] == '\0' && + (np->pmid & MARK_BIT) == 0) + break; + } + + if (np == NULL) /* no match with child */ + return NULL; + else if (*tail == '\0') /* matched with whole path */ + return np; + else + return locate(tail+1, np); /* try matching with rest of pathname */ +} + +/* + * PMAPI routines from here down + */ + +/* + * As of PCP 3.6, there is _only_ the ASCII version of the PMNS + */ +int +pmLoadNameSpace(const char *filename) +{ + return pmLoadASCIINameSpace(filename, 0); +} + +int +pmLoadASCIINameSpace(const char *filename, int dupok) +{ + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + havePmLoadCall = 1; + sts = load(filename, dupok); + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +/* + * Assume that each node has been malloc'ed separately. + * This is the case for an ASCII loaded PMNS. + * Traverse entire tree and free each node. + */ +static void +FreeTraversePMNS(__pmnsNode *this) +{ + __pmnsNode *np, *next; + + if (this == NULL) + return; + + /* Free child sub-trees */ + for (np = this->first; np != NULL; np = next) { + next = np->next; + FreeTraversePMNS(np); + } + + free(this->name); + free(this); +} + +void +__pmFreePMNS(__pmnsTree *pmns) +{ + if (pmns != NULL) { + if (pmns->contiguous) { + free(pmns->root); + free(pmns->htab); + free(pmns->symbol); + } + else { + free(pmns->htab); + FreeTraversePMNS(pmns->root); + } + + free(pmns); + } +} + +void +pmUnloadNameSpace(void) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + havePmLoadCall = 0; + __pmFreePMNS(main_pmns); + main_pmns = NULL; + PM_UNLOCK(__pmLock_libpcp); +} + +int +pmLookupName(int numpmid, char *namelist[], pmID pmidlist[]) +{ + int pmns_location = GetLocation(); + int sts = 0; + __pmContext *ctxp; + int c_type; + int lsts; + int ctx; + int i; + int nfail = 0; + + if (numpmid < 1) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "pmLookupName(%d, ...) bad numpmid!\n", numpmid); + } +#endif + return PM_ERR_TOOSMALL; + } + + PM_INIT_LOCKS(); + + ctx = lsts = pmWhichContext(); + if (lsts >= 0) { + ctxp = __pmHandleToPtr(ctx); + c_type = ctxp->c_type; + } + else { + ctxp = NULL; + /* + * set c_type to be NONE of PM_CONTEXT_HOST, PM_CONTEXT_ARCHIVE + * nor PM_CONTEXT_LOCAL + */ + c_type = 0; + } + if (ctxp != NULL && c_type == PM_CONTEXT_LOCAL && PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) { + /* Local context requires single-threaded applications */ + PM_UNLOCK(ctxp->c_lock); + return PM_ERR_THREAD; + } + + /* + * Guarantee that derived metrics preparation is done, for all possible + * paths through this routine which might end at "Try derived metrics.." + * below. + */ + memset(pmidlist, PM_ID_NULL, numpmid * sizeof(pmID)); + + if (pmns_location < 0) { + if (ctxp != NULL) + PM_UNLOCK(ctxp->c_lock); + sts = pmns_location; + /* only hope is derived metrics ... set up for this */ + nfail += numpmid; + } + else if (pmns_location == PMNS_LOCAL) { + char *xname; + char *xp; + __pmnsNode *np; + + if (ctxp != NULL) + PM_UNLOCK(ctxp->c_lock); + for (i = 0; i < numpmid; i++) { + /* + * if we locate the name and it is a leaf in the PMNS + * this is good + */ + PM_LOCK(__pmLock_libpcp); + np = locate(namelist[i], curr_pmns->root); + PM_UNLOCK(__pmLock_libpcp); + if (np != NULL ) { + if (np->first == NULL) + pmidlist[i] = np->pmid; + else { + sts = PM_ERR_NONLEAF; + nfail++; + } + continue; + } + nfail++; + /* + * did not match name in PMNS ... try for prefix matching + * the name to the root of a dynamic subtree of the PMNS, + * or possibly we're using a local context and then we may + * be able to ship request to PMDA + */ + xname = strdup(namelist[i]); + if (xname == NULL) { + __pmNoMem("pmLookupName", strlen(namelist[i])+1, PM_RECOV_ERR); + sts = -oserror(); + continue; + } + while ((xp = rindex(xname, '.')) != NULL) { + *xp = '\0'; + lsts = 0; + PM_LOCK(__pmLock_libpcp); + np = locate(xname, curr_pmns->root); + PM_UNLOCK(__pmLock_libpcp); + if (np != NULL && np->first == NULL && + pmid_domain(np->pmid) == DYNAMIC_PMID && + pmid_item(np->pmid) == 0) { + /* root of dynamic subtree */ + if (c_type == PM_CONTEXT_LOCAL) { + /* have PM_CONTEXT_LOCAL ... ship request to PMDA */ + int domain = ((__pmID_int *)&np->pmid)->cluster; + __pmDSO *dp; + if ((dp = __pmLookupDSO(domain)) == NULL) { + if (sts >= 0) sts = PM_ERR_NOAGENT; + break; + } + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + lsts = dp->dispatch.version.four.pmid(namelist[i], &pmidlist[i], dp->dispatch.version.four.ext); + if (lsts >= 0) + nfail--; + + break; + } + } + else { + /* No PM_LOCAL_CONTEXT, use PMID from PMNS */ + pmidlist[i] = np->pmid; + nfail--; + break; + } + } + } + free(xname); + } + + sts = (sts == 0 ? numpmid - nfail : sts); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + int i; + char strbuf[20]; + fprintf(stderr, "pmLookupName(%d, ...) using local PMNS returns %d and ...\n", + numpmid, sts); + for (i = 0; i < numpmid; i++) { + fprintf(stderr, " name[%d]: \"%s\"", i, namelist[i]); + if (sts >= 0) + fprintf(stderr, " PMID: 0x%x %s", + pmidlist[i], pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf))); + fputc('\n', stderr); + } + } +#endif + } + else { + /* + * PMNS_REMOTE so there must be a current host context + */ + assert(c_type == PM_CONTEXT_HOST); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "pmLookupName: request_names ->"); + for (i = 0; i < numpmid; i++) + fprintf(stderr, " [%d] %s", i, namelist[i]); + fputc('\n', stderr); + } +#endif + PM_LOCK(ctxp->c_pmcd->pc_lock); + sts = __pmSendNameList(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + numpmid, namelist, NULL); + if (sts < 0) + sts = __pmMapErrno(sts); + else { + __pmPDU *pb; + int pinpdu; + + pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (sts == PDU_PMNS_IDS) { + /* Note: + * pmLookupName may return an error even though + * it has a valid list of ids. + * This is why we need op_status. + */ + int op_status; + sts = __pmDecodeIDList(pb, numpmid, pmidlist, &op_status); + if (sts >= 0) + sts = op_status; + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + } + else if (sts != PM_ERR_TIMEOUT) { + sts = PM_ERR_IPC; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + if (sts >= 0) + nfail = numpmid - sts; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "pmLookupName: receive_names <-"); + if (sts >= 0) { + for (i = 0; i < numpmid; i++) + fprintf(stderr, " [%d] %s", i, pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf))); + fputc('\n', stderr); + } + else + fprintf(stderr, " %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } +#endif + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + PM_UNLOCK(ctxp->c_lock); + } + + if (sts < 0 || nfail > 0) { + /* + * Try derived metrics for any remaining unknown pmids. + * The return status is a little tricky ... prefer the status + * from above unless all of the remaining unknown PMIDs are + * resolved by __dmgetpmid() in which case success (numpmid) + * is the right return status + */ + nfail = 0; + for (i = 0; i < numpmid; i++) { + if (pmidlist[i] == PM_ID_NULL) { + lsts = __dmgetpmid(namelist[i], &pmidlist[i]); + if (lsts < 0) { + nfail++; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char strbuf[20]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__dmgetpmid: metric \"%s\" -> ", namelist[i]); + if (lsts < 0) + fprintf(stderr, "%s\n", pmErrStr_r(lsts, errmsg, sizeof(errmsg))); + else + fprintf(stderr, "PMID %s\n", pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf))); + } +#endif + } + } + if (nfail == 0) + sts = numpmid; + } + + /* + * special case for a single metric, PM_ERR_NAME is more helpful than + * returning 0 and having one PM_ID_NULL pmid + */ + if (sts == 0 && numpmid == 1) + sts = PM_ERR_NAME; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "pmLookupName(%d, ...) -> ", numpmid); + if (sts < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + } + else + fprintf(stderr, "%d\n", sts); + } +#endif + + return sts; +} + +static int +GetChildrenStatusRemote(__pmContext *ctxp, const char *name, + char ***offspring, int **statuslist) +{ + int n; + + PM_LOCK(ctxp->c_pmcd->pc_lock); + n = __pmSendChildReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), + name, statuslist == NULL ? 0 : 1); + if (n < 0) + n = __pmMapErrno(n); + else { + __pmPDU *pb; + int pinpdu; + + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (n == PDU_PMNS_NAMES) { + int numnames; + n = __pmDecodeNameList(pb, &numnames, offspring, statuslist); + if (n >= 0) + n = numnames; + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + + return n; +} + +static void +stitch_list(int *num, char ***offspring, int **statuslist, int x_num, char **x_offspring, int *x_statuslist) +{ + /* + * so this gets tricky ... need to stitch the additional metrics + * (derived metrics or dynamic metrics) at the end of the existing + * metrics (if any) after removing any duplicates (!) ... and honour + * the bizarre pmGetChildren contract in terms of malloc'ing the + * result arrays + */ + int n_num; + char **n_offspring; + int *n_statuslist = NULL; + int i; + int j; + char *q; + size_t need; + + if (x_num == 0) { + /* nothing to do */ + return; + } + if (*num > 0) { + /* appending */ + n_num = *num + x_num; + } + else { + /* initializing */ + n_num = x_num; + } + + for (i = 0; i < x_num; i++) { + for (j = 0; j < *num; j++) { + if (strcmp(x_offspring[i], (*offspring)[j]) == 0) { + /* duplicate ... bugger */ + n_num--; + free(x_offspring[i]); + x_offspring[i] = NULL; + break; + } + } + } + + need = n_num*sizeof(char *); + for (j = 0; j < *num; j++) { + need += strlen((*offspring)[j]) + 1; + } + for (i = 0; i < x_num; i++) { + if (x_offspring[i] != NULL) { + need += strlen(x_offspring[i]) + 1; + } + } + if ((n_offspring = (char **)malloc(need)) == NULL) { + __pmNoMem("pmGetChildrenStatus: n_offspring", need, PM_FATAL_ERR); + /*NOTREACHED*/ + } + if (statuslist != NULL) { + if ((n_statuslist = (int *)malloc(n_num*sizeof(n_statuslist[0]))) == NULL) { + __pmNoMem("pmGetChildrenStatus: n_statuslist", n_num*sizeof(n_statuslist[0]), PM_FATAL_ERR); + /*NOTREACHED*/ + } + } + q = (char *)&n_offspring[n_num]; + for (j = 0; j < *num; j++) { + n_offspring[j] = q; + strcpy(q, (*offspring)[j]); + q += strlen(n_offspring[j]) + 1; + if (statuslist != NULL) + n_statuslist[j] = (*statuslist)[j]; + } + for (i = 0; i < x_num; i++) { + if (x_offspring[i] != NULL) { + n_offspring[j] = q; + strcpy(q, x_offspring[i]); + q += strlen(n_offspring[j]) + 1; + if (statuslist != NULL) + n_statuslist[j] = x_statuslist[i]; + j++; + } + } + if (*num > 0) { + free(*offspring); + if (statuslist != NULL) + free(*statuslist); + } + *num = n_num; + if (statuslist != NULL) + *statuslist = n_statuslist; + *offspring = n_offspring; +} + +/* + * It is allowable to pass in a statuslist arg of NULL. It is therefore + * important to check that this is not NULL before accessing it. + */ +int +pmGetChildrenStatus(const char *name, char ***offspring, int **statuslist) +{ + int *status = NULL; + int pmns_location = GetLocation(); + int num; + int dm_num; + char **dm_offspring; + int *dm_statuslist; + int sts; + int ctx; + __pmContext *ctxp; + + if (pmns_location < 0) + return pmns_location; + + if (name == NULL) + return PM_ERR_NAME; + + PM_INIT_LOCKS(); + + ctx = sts = pmWhichContext(); + if (sts >= 0) + ctxp = __pmHandleToPtr(sts); + else + ctxp = NULL; + + if (pmns_location == PMNS_LOCAL) { + __pmnsNode *np; + __pmnsNode *tnp; + int i; + int need; + char **result; + char *p; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "pmGetChildren(name=\"%s\") [local]\n", name); + } +#endif + + /* avoids ambiguity, for errors and leaf nodes */ + *offspring = NULL; + num = 0; + if (statuslist) + *statuslist = NULL; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (*name == '\0') + np = curr_pmns->root; /* use "" to name the root of the PMNS */ + else + np = locate(name, curr_pmns->root); + if (np == NULL) { + if (ctxp != NULL && ctxp->c_type == PM_CONTEXT_LOCAL) { + /* + * No match in PMNS and using PM_CONTEXT_LOCAL so for + * dynamic metrics, need to consider prefix matches back to + * the root on the PMNS to find a possible root of a dynamic + * subtree, and hence the domain of the responsible PMDA + */ + char *xname = strdup(name); + char *xp; + if (xname == NULL) { + __pmNoMem("pmGetChildrenStatus", strlen(name)+1, PM_RECOV_ERR); + num = -oserror(); + PM_UNLOCK(__pmLock_libpcp); + goto report; + } + while ((xp = rindex(xname, '.')) != NULL) { + *xp = '\0'; + np = locate(xname, curr_pmns->root); + if (np != NULL && np->first == NULL && + pmid_domain(np->pmid) == DYNAMIC_PMID && + pmid_item(np->pmid) == 0) { + int domain = ((__pmID_int *)&np->pmid)->cluster; + __pmDSO *dp; + if ((dp = __pmLookupDSO(domain)) == NULL) { + num = PM_ERR_NOAGENT; + free(xname); + PM_UNLOCK(__pmLock_libpcp); + goto check; + } + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + char **x_offspring = NULL; + int *x_statuslist = NULL; + int x_num; + x_num = dp->dispatch.version.four.children( + name, 0, &x_offspring, &x_statuslist, + dp->dispatch.version.four.ext); + if (x_num < 0) + num = x_num; + else if (x_num > 0) { + stitch_list(&num, offspring, statuslist, + x_num, x_offspring, x_statuslist); + free(x_offspring); + free(x_statuslist); + } + free(xname); + PM_UNLOCK(__pmLock_libpcp); + goto check; + } + else { + /* Not PMDA_INTERFACE_4 or later */ + num = PM_ERR_NAME; + free(xname); + PM_UNLOCK(__pmLock_libpcp); + goto check; + } + } + } + free(xname); + } + num = PM_ERR_NAME; + PM_UNLOCK(__pmLock_libpcp); + goto check; + } + PM_UNLOCK(__pmLock_libpcp); + + if (np != NULL && np->first == NULL) { + /* + * this is a leaf node ... if it is the root of a dynamic + * subtree of the PMNS and we have an existing context + * of type PM_CONTEXT_LOCAL than we should chase the + * relevant PMDA to provide the details + */ + if (pmid_domain(np->pmid) == DYNAMIC_PMID && + pmid_item(np->pmid) == 0) { + if (ctxp != NULL && ctxp->c_type == PM_CONTEXT_LOCAL) { + int domain = ((__pmID_int *)&np->pmid)->cluster; + __pmDSO *dp; + if ((dp = __pmLookupDSO(domain)) == NULL) { + num = PM_ERR_NOAGENT; + goto check; + } + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + char **x_offspring = NULL; + int *x_statuslist = NULL; + int x_num; + x_num = dp->dispatch.version.four.children(name, 0, + &x_offspring, &x_statuslist, + dp->dispatch.version.four.ext); + if (x_num < 0) + num = x_num; + else if (x_num > 0) { + stitch_list(&num, offspring, statuslist, + x_num, x_offspring, x_statuslist); + free(x_offspring); + free(x_statuslist); + } + goto check; + } + else { + /* Not PMDA_INTERFACE_4 or later */ + num = PM_ERR_NAME; + goto check; + } + } + } + num = 0; + goto check; + } + + need = 0; + num = 0; + + if (np != NULL) { + for (i = 0, tnp = np->first; tnp != NULL; tnp = tnp->next, i++) { + if ((tnp->pmid & MARK_BIT) == 0) { + num++; + need += sizeof(**offspring) + strlen(tnp->name) + 1; + } + } + } + + if ((result = (char **)malloc(need)) == NULL) { + num = -oserror(); + goto report; + } + + if (statuslist != NULL) { + if ((status = (int *)malloc(num*sizeof(int))) == NULL) { + num = -oserror(); + free(result); + goto report; + } + } + + p = (char *)&result[num]; + + if (np != NULL) { + for (i = 0, tnp = np->first; tnp != NULL; tnp = tnp->next) { + if ((tnp->pmid & MARK_BIT) == 0) { + result[i] = p; + /* + * a name at the root of a dynamic metrics subtree + * needs some special handling ... they will have a + * "special" PMID, but need the status set to indicate + * they are not a leaf node of the PMNS + */ + if (statuslist != NULL) { + if (pmid_domain(tnp->pmid) == DYNAMIC_PMID && + pmid_item(tnp->pmid) == 0) { + status[i] = PMNS_NONLEAF_STATUS; + } + else + /* node has children? */ + status[i] = (tnp->first == NULL ? PMNS_LEAF_STATUS : PMNS_NONLEAF_STATUS); + } + strcpy(result[i], tnp->name); + p += strlen(tnp->name) + 1; + i++; + } + } + } + + *offspring = result; + if (statuslist != NULL) + *statuslist = status; + } + else { + /* + * PMNS_REMOTE so there must be a current host context + */ + assert(ctxp != NULL && ctxp->c_type == PM_CONTEXT_HOST); + num = GetChildrenStatusRemote(ctxp, name, offspring, statuslist); + } + +check: + if (ctxp != NULL) + PM_UNLOCK(ctxp->c_lock); + /* + * see if there are derived metrics that qualify + */ + dm_num = __dmchildren(name, &dm_offspring, &dm_statuslist); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_DERIVE) { + char errmsg[PM_MAXERRMSGLEN]; + if (num < 0) + fprintf(stderr, "pmGetChildren(name=\"%s\") no regular children (%s)", name, pmErrStr_r(num, errmsg, sizeof(errmsg))); + else + fprintf(stderr, "pmGetChildren(name=\"%s\") %d regular children", name, num); + if (dm_num < 0) + fprintf(stderr, ", no derived children (%s)\n", pmErrStr_r(dm_num, errmsg, sizeof(errmsg))); + else if (dm_num == 0) + fprintf(stderr, ", derived leaf\n"); + else + fprintf(stderr, ", %d derived children\n", dm_num); + } +#endif + if (dm_num > 0) { + stitch_list(&num, offspring, statuslist, dm_num, dm_offspring, dm_statuslist); + free(dm_offspring); + free(dm_statuslist); + } + else if (dm_num == 0 && num < 0) { + /* leaf node and derived metric */ + num = 0; + } + +report: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "pmGetChildren(name=\"%s\") -> ", name); + if (num == 0) + fprintf(stderr, "leaf\n"); + else if (num > 0) { + if (statuslist != NULL) + __pmDumpNameAndStatusList(stderr, num, *offspring, *statuslist); + else + __pmDumpNameList(stderr, num, *offspring); + } + else { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s\n", pmErrStr_r(num, errmsg, sizeof(errmsg))); + } + } +#endif + + return num; +} + +int +pmGetChildren(const char *name, char ***offspring) +{ + return pmGetChildrenStatus(name, offspring, NULL); +} + +static int +request_namebypmid(__pmContext *ctxp, pmID pmid) +{ + int n; + + n = __pmSendIDList(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), 1, &pmid, 0); + if (n < 0) + n = __pmMapErrno(n); + return n; +} + +static int +receive_namesbyid(__pmContext *ctxp, char ***namelist) +{ + int n; + __pmPDU *pb; + int pinpdu; + + pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + + if (n == PDU_PMNS_NAMES) { + int numnames; + + n = __pmDecodeNameList(pb, &numnames, namelist, NULL); + if (n >= 0) + n = numnames; + } + else if (n == PDU_ERROR) + __pmDecodeError(pb, &n); + else if (n != PM_ERR_TIMEOUT) + n = PM_ERR_IPC; + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + return n; +} + +static int +receive_a_name(__pmContext *ctxp, char **name) +{ + int n; + char **namelist; + + if ((n = receive_namesbyid(ctxp, &namelist)) >= 0) { + char *newname = strdup(namelist[0]); + free(namelist); + if (newname == NULL) { + n = -oserror(); + } else { + *name = newname; + n = 0; + } + } + + return n; +} + +int +pmNameID(pmID pmid, char **name) +{ + int pmns_location = GetLocation(); + + if (pmns_location < 0) + return pmns_location; + + PM_INIT_LOCKS(); + + if (pmns_location == PMNS_LOCAL) { + __pmnsNode *np; + PM_LOCK(__pmLock_libpcp); + for (np = curr_pmns->htab[pmid % curr_pmns->htabsize]; np != NULL; np = np->hash) { + if (np->pmid == pmid) { + int sts; + if (pmid_domain(np->pmid) != DYNAMIC_PMID || + pmid_item(np->pmid) != 0) + sts = backname(np, name); + else + sts = PM_ERR_PMID; + PM_UNLOCK(__pmLock_libpcp); + return sts; + } + } + PM_UNLOCK(__pmLock_libpcp); + /* not found so far, try derived metrics ... */ + return __dmgetname(pmid, name); + } + + else { + /* assume PMNS_REMOTE */ + int n; + __pmContext *ctxp; + + /* As we have PMNS_REMOTE there must be a current host context */ + if ((n = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(n)) == NULL) + return PM_ERR_NOCONTEXT; + PM_LOCK(ctxp->c_pmcd->pc_lock); + if ((n = request_namebypmid(ctxp, pmid)) >= 0) { + n = receive_a_name(ctxp, name); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + PM_UNLOCK(ctxp->c_lock); + if (n >= 0) return n; + return __dmgetname(pmid, name); + } +} + +int +pmNameAll(pmID pmid, char ***namelist) +{ + int pmns_location = GetLocation(); + char **tmp = NULL; + int n = 0; + int len = 0; + char *sp; + + if (pmns_location < 0) + return pmns_location; + + PM_INIT_LOCKS(); + + if (pmns_location == PMNS_LOCAL) { + __pmnsNode *np; + int sts = 0; + int i; + + if (pmid_domain(pmid) == DYNAMIC_PMID && pmid_item(pmid) == 0) { + /* + * pmid is for the root of a dynamic subtree in the PMNS ... + * there is no matching leaf name + */ + return PM_ERR_PMID; + } + PM_LOCK(__pmLock_libpcp); + for (np = curr_pmns->htab[pmid % curr_pmns->htabsize]; np != NULL; np = np->hash) { + if (np->pmid == pmid) { + n++; + if ((tmp = (char **)realloc(tmp, n * sizeof(tmp[0]))) == NULL) { + sts = -oserror(); + break; + } + if ((sts = backname(np, &tmp[n-1])) < 0) { + /* error, ... free any partial allocations */ + for (i = n-2; i >= 0; i--) + free(tmp[i]); + free(tmp); + break; + } + len += strlen(tmp[n-1])+1; + } + } + PM_UNLOCK(__pmLock_libpcp); + if (sts < 0) + return sts; + + if (n == 0) + goto try_derive; + + len += n * sizeof(tmp[0]); + if ((tmp = (char **)realloc(tmp, len)) == NULL) + return -oserror(); + + sp = (char *)&tmp[n]; + for (i = 0; i < n; i++) { + strcpy(sp, tmp[i]); + free(tmp[i]); + tmp[i] = sp; + sp += strlen(sp)+1; + } + + *namelist = tmp; + return n; + } + + else { + /* assume PMNS_REMOTE */ + int n; + __pmContext *ctxp; + + /* As we have PMNS_REMOTE there must be a current host context */ + if ((n = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(n)) == NULL) + return PM_ERR_NOCONTEXT; + PM_LOCK(ctxp->c_pmcd->pc_lock); + if ((n = request_namebypmid (ctxp, pmid)) >= 0) { + n = receive_namesbyid (ctxp, namelist); + } + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + PM_UNLOCK(ctxp->c_lock); + if (n == 0) + goto try_derive; + return n; + } + +try_derive: + if ((tmp = (char **)malloc(sizeof(tmp[0]))) == NULL) + return -oserror(); + n = __dmgetname(pmid, tmp); + if (n < 0) { + free(tmp); + return n; + } + len = sizeof(tmp[0]) + strlen(tmp[0])+1; + if ((tmp = (char **)realloc(tmp, len)) == NULL) + return -oserror(); + sp = (char *)&tmp[1]; + strcpy(sp, tmp[0]); + free(tmp[0]); + tmp[0] = sp; + *namelist = tmp; + return 1; +} + + +/* + * generic depth-first recursive descent of the PMNS + */ +static int +TraversePMNS_local(const char *name, void(*func)(const char *), void(*func_r)(const char *, void *), void *closure) +{ + int sts = 0; + int nchildren; + char **enfants; + + if ((nchildren = pmGetChildren(name, &enfants)) < 0) + return nchildren; + + if (nchildren > 0) { + int j; + char *newname; + + for (j = 0; j < nchildren; j++) { + size_t size = strlen(name) + 1 + strlen(enfants[j]) + 1; + if ((newname = (char *)malloc(size)) == NULL) + __pmNoMem("pmTraversePMNS", size, PM_FATAL_ERR); + if (*name == '\0') + strcpy(newname, enfants[j]); + else { + strcpy(newname, name); + strcat(newname, "."); + strcat(newname, enfants[j]); + } + sts = TraversePMNS_local(newname, func, func_r, closure); + free(newname); + if (sts < 0) + break; + } + free(enfants); + } + else { + /* leaf node, name is full name of a metric */ + if (func_r == NULL) + (*func)(name); + else + (*func_r)(name, closure); + } + + return sts; +} + +static int +TraversePMNS(const char *name, void(*func)(const char *), void(*func_r)(const char *, void *), void *closure) +{ + int sts; + int pmns_location = GetLocation(); + + if (pmns_location < 0) + return pmns_location; + + if (name == NULL) + return PM_ERR_NAME; + + PM_INIT_LOCKS(); + + if (pmns_location == PMNS_LOCAL) { + PM_LOCK(__pmLock_libpcp); + sts = TraversePMNS_local(name, func, func_r, closure); + PM_UNLOCK(__pmLock_libpcp); + } + else { + __pmPDU *pb; + __pmContext *ctxp; + + /* As we have PMNS_REMOTE there must be a current host context */ + if ((sts = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(sts)) == NULL) + return PM_ERR_NOCONTEXT; + PM_LOCK(ctxp->c_pmcd->pc_lock); + sts = __pmSendTraversePMNSReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), name); + if (sts < 0) { + sts = __pmMapErrno(sts); + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + PM_UNLOCK(ctxp->c_lock); + } + else { + int numnames; + int i; + int xtra; + char **namelist; + int pinpdu; + + pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + TIMEOUT_DEFAULT, &pb); + PM_UNLOCK(ctxp->c_pmcd->pc_lock); + PM_UNLOCK(ctxp->c_lock); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, + &namelist, NULL); + if (sts > 0) { + for (i=0; i<numnames; i++) { + /* + * Do not process anonymous metrics here, we'll + * pick them up with the derived metrics later on + */ + if (strncmp(namelist[i], "anon.", 5) != 0) { + if (func_r == NULL) + (*func)(namelist[i]); + else + (*func_r)(namelist[i], closure); + } + } + numnames = sts; + free(namelist); + } + else { + __pmUnpinPDUBuf(pb); + return sts; + } + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + if (sts != PM_ERR_NAME) { + __pmUnpinPDUBuf(pb); + return sts; + } + numnames = 0; + } + else { + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + return (sts == PM_ERR_TIMEOUT) ? sts : PM_ERR_IPC; + } + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + /* + * add any derived metrics that have "name" as + * their prefix + */ + xtra = __dmtraverse(name, &namelist); + if (xtra > 0) { + sts = 0; + for (i=0; i<xtra; i++) { + if (func_r == NULL) + (*func)(namelist[i]); + else + (*func_r)(namelist[i], closure); + } + numnames += xtra; + free(namelist); + } + + if (sts > 0) + return numnames; + } + } + return sts; +} + +int +pmTraversePMNS(const char *name, void(*func)(const char *)) +{ + return TraversePMNS(name, func, NULL, NULL); +} + +int +pmTraversePMNS_r(const char *name, void(*func)(const char *, void *), void *closure) +{ + return TraversePMNS(name, NULL, func, closure); +} + +int +pmTrimNameSpace(void) +{ + int i; + __pmContext *ctxp; + __pmHashCtl *hcp; + __pmHashNode *hp; + int pmns_location = GetLocation(); + + if (pmns_location < 0) + return pmns_location; + else if (pmns_location == PMNS_REMOTE) + return 0; + + /* for PMNS_LOCAL ... */ + PM_INIT_LOCKS(); + + if ((ctxp = __pmHandleToPtr(pmWhichContext())) == NULL) + return PM_ERR_NOCONTEXT; + + if (ctxp->c_type != PM_CONTEXT_ARCHIVE) { + /* unset all of the marks */ + PM_LOCK(__pmLock_libpcp); + mark_all(curr_pmns, 0); + PM_UNLOCK(__pmLock_libpcp); + PM_UNLOCK(ctxp->c_lock); + return 0; + } + + /* Don't do any trimming for archives. + * Exception: if an explicit load PMNS call was made. + */ + PM_LOCK(__pmLock_libpcp); + if (havePmLoadCall) { + /* + * (1) set all of the marks, and + * (2) clear the marks for those metrics defined in the archive + */ + mark_all(curr_pmns, 1); + hcp = &ctxp->c_archctl->ac_log->l_hashpmid; + + for (i = 0; i < hcp->hsize; i++) { + for (hp = hcp->hash[i]; hp != NULL; hp = hp->next) { + mark_one(curr_pmns, (pmID)hp->key, 0); + } + } + } + PM_UNLOCK(__pmLock_libpcp); + PM_UNLOCK(ctxp->c_lock); + + return 0; +} + +void +__pmDumpNameSpace(FILE *f, int verbosity) +{ + int pmns_location = GetLocation(); + + if (pmns_location < 0) + fprintf(f, "__pmDumpNameSpace: Unable to determine PMNS location\n"); + else if (pmns_location == PMNS_REMOTE) + fprintf(f, "__pmDumpNameSpace: Name Space is remote !\n"); + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + dumptree(f, 0, curr_pmns->root, verbosity); + PM_UNLOCK(__pmLock_libpcp); +} + +void +__pmDumpNameNode(FILE *f, __pmnsNode *node, int verbosity) +{ + dumptree(f, 0, node, verbosity); +} diff --git a/src/libpcp/src/probe.c b/src/libpcp/src/probe.c new file mode 100644 index 0000000..ee67576 --- /dev/null +++ b/src/libpcp/src/probe.c @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2014 Red Hat. + * + * 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. + */ +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#include "probe.h" + +#if !defined(PTHREAD_STACK_MIN) +#if defined(IS_SOLARIS) +#define PTHREAD_STACK_MIN ((size_t)_sysconf(_SC_THREAD_STACK_MIN)) +#else +#define PTHREAD_STACK_MIN 16384 +#endif +#endif + +/* + * Service discovery by active probing. The given subnet is probed for the + * requested service(s). + */ +typedef struct connectionOptions { + __pmSockAddr *netAddress; /* Address of the subnet */ + int maskBits; /* Number of bits in the subnet */ + unsigned maxThreads; /* Max number of threads to use. */ + struct timeval timeout; /* Connection timeout */ + const __pmServiceDiscoveryOptions *globalOptions; /* Global discover options */ +} connectionOptions; + +/* Context for each thread. */ +typedef struct connectionContext { + const char *service; /* Service spec */ + __pmSockAddr *nextAddress; /* Next available address */ + int nports; /* Number of ports per address */ + int portIx; /* Index of next available port */ + const int *ports; /* The actual ports */ + int *numUrls; /* Size of the results */ + char ***urls; /* The results */ + const connectionOptions *options; /* Connection options */ +#if PM_MULTI_THREAD + __pmMutex addrLock; /* lock for the above address/port */ + __pmMutex urlLock; /* lock for the above results */ +#endif +} connectionContext; + +#if ! PM_MULTI_THREAD +/* Make these disappear. */ +#undef PM_LOCK +#undef PM_UNLOCK +#define PM_LOCK(lock) do { } while (0) +#define PM_UNLOCK(lock) do { } while (0) +#endif + +/* + * Attempt connection based on the given context until there are no more + * addresses+ports to try. + */ +static void * +attemptConnections(void *arg) +{ + int s; + int flags; + int sts; + __pmFdSet wfds; + __pmServiceInfo serviceInfo; + __pmSockAddr *addr; + const __pmServiceDiscoveryOptions *globalOptions; + int port; + int attempt; + struct timeval againWait = {0, 100000}; /* 0.1 seconds */ + connectionContext *context = arg; + + /* + * Keep trying to secure an address+port until there are no more + * or until we are interrupted. + */ + globalOptions = context->options->globalOptions; + while (! globalOptions->timedOut && + (! globalOptions->flags || + (*globalOptions->flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0)) { + /* Obtain the address lock while securing the next address, if any. */ + PM_LOCK(context->addrLock); + if (context->nextAddress == NULL) { + /* No more addresses. */ + PM_UNLOCK(context->addrLock); + break; + } + + /* + * There is an address+port remaining. Secure them. If we cannot + * obtain our own copy of the address, then give up the lock and + * try again. Another thread will try this address+port. + */ + addr = __pmSockAddrDup(context->nextAddress); + if (addr == NULL) { + PM_UNLOCK(context->addrLock); + continue; + } + port = context->ports[context->portIx]; + __pmSockAddrSetPort(addr, port); + + /* + * Advance the port index for the next thread. If we took the + * final port, then advance the address and reset the port index. + * The address may become NULL which is the signal for all + * threads to exit. + */ + ++context->portIx; + if (context->portIx == context->nports) { + context->portIx = 0; + context->nextAddress = + __pmSockAddrNextSubnetAddr(context->nextAddress, + context->options->maskBits); + } + PM_UNLOCK(context->addrLock); + + /* + * Create a socket. There is a limit on open fds, not just from + * the OS, but also in the IPC table. If we get EAGAIN, + * then wait 0.1 seconds and try again. We must have a limit in case + * something goes wrong. Make it 5 seconds (50 * 100,000 usecs). + */ + for (attempt = 0; attempt < 50; ++attempt) { + if (__pmSockAddrIsInet(addr)) + s = __pmCreateSocket(); + else /* address family already checked */ + s = __pmCreateIPv6Socket(); + if (s != -EAGAIN) + break; + __pmtimevalSleep(againWait); + } + if (pmDebug & DBG_TRACE_DISCOVERY) { + if (attempt > 0) { + __pmNotifyErr(LOG_INFO, + "Waited for %d attempts for an available fd\n", + attempt); + } + } + if (s < 0) { + char *addrString = __pmSockAddrToString(addr); + __pmNotifyErr(LOG_WARNING, + "__pmProbeDiscoverServices: Unable to create socket for address %s", + addrString); + free(addrString); + __pmSockAddrFree(addr); + continue; + } + + /* + * Attempt to connect. If flags comes back as less than zero, then the + * socket has already been closed by __pmConnectTo(). + */ + sts = -1; + flags = __pmConnectTo(s, addr, port); + if (flags >= 0) { + /* + * FNDELAY and we're in progress - wait on __pmSelectWrite. + * __pmSelectWrite may alter the contents of the timeout so make a + * copy. + */ + struct timeval timeout = context->options->timeout; + __pmFD_ZERO(&wfds); + __pmFD_SET(s, &wfds); + sts = __pmSelectWrite(s+1, &wfds, &timeout); + + /* Was the connection successful? */ + if (sts == 0) + sts = -1; /* Timed out */ + else if (sts > 0) + sts = __pmConnectCheckError(s); + + __pmCloseSocket(s); + } + + /* If connection was successful, add this service to the list. */ + if (sts == 0) { + serviceInfo.spec = context->service; + serviceInfo.address = addr; + if (strcmp(context->service, PM_SERVER_SERVICE_SPEC) == 0) + serviceInfo.protocol = SERVER_PROTOCOL; + else if (strcmp(context->service, PM_SERVER_PROXY_SPEC) == 0) + serviceInfo.protocol = PROXY_PROTOCOL; + else if (strcmp(context->service, PM_SERVER_WEBD_SPEC) == 0) + serviceInfo.protocol = PMWEBD_PROTOCOL; + + PM_LOCK(context->urlLock); + *context->numUrls = + __pmAddDiscoveredService(&serviceInfo, globalOptions, + *context->numUrls, context->urls); + PM_UNLOCK(context->urlLock); + } + + __pmSockAddrFree(addr); + } /* Loop over connection attempts. */ + + return NULL; +} + +static int +probeForServices( + const char *service, + const connectionOptions *options, + int numUrls, + char ***urls +) +{ + int *ports = NULL; + int nports; + int prevNumUrls = numUrls; + connectionContext context; +#if PM_MULTI_THREAD + int sts; + pthread_t *threads = NULL; + unsigned threadIx; + unsigned nThreads; + pthread_attr_t threadAttr; +#endif + + /* Determine the port numbers for this service. */ + ports = NULL; + nports = 0; + nports = __pmServiceAddPorts(service, &ports, nports); + if (nports <= 0) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: could not find ports for service '%s'", + service); + return 0; + } + + /* + * Initialize the shared probing context. This will be shared among all of + * the worker threads. + */ + context.service = service; + context.ports = ports; + context.nports = nports; + context.numUrls = &numUrls; + context.urls = urls; + context.portIx = 0; + context.options = options; + + /* + * Initialize the first address of the subnet. This pointer will become + * NULL and the mempry freed by __pmSockAddrNextSubnetAddr() when the + * final address+port has been probed. + */ + context.nextAddress = + __pmSockAddrFirstSubnetAddr(options->netAddress, options->maskBits); + if (context.nextAddress == NULL) { + char *addrString = __pmSockAddrToString(options->netAddress); + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: unable to determine the first address of the subnet: %s/%d", + addrString, options->maskBits); + free(addrString); + goto done; + } + +#if PM_MULTI_THREAD + /* + * Set up the concurrrency controls. These locks will be tested + * even if we fail to allocate/use the thread table below. + */ + pthread_mutex_init(&context.addrLock, NULL); + pthread_mutex_init(&context.urlLock, NULL); + + if (options->maxThreads > 0) { + /* + * Allocate the thread table. We have a maximum for the number of + * threads, so that will be the size. + */ + threads = malloc(options->maxThreads * sizeof(*threads)); + if (threads == NULL) { + /* + * Unable to allocate the thread table, however, We can still do the + * probing on the main thread. + */ + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: unable to allocate %u threads", + options->maxThreads); + } + else { + /* We want our worker threads to be joinable. */ + pthread_attr_init(&threadAttr); + pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_JOINABLE); + + /* + * Our worker threads don't need much stack. PTHREAD_STACK_MIN is + * enough except when resolving addresses, where twice that much is + * sufficient. + */ + if (options->globalOptions->resolve || + (options->globalOptions->flags && + (*options->globalOptions->flags & PM_SERVICE_DISCOVERY_RESOLVE) + != 0)) + pthread_attr_setstacksize(&threadAttr, 2 * PTHREAD_STACK_MIN); + else + pthread_attr_setstacksize(&threadAttr, PTHREAD_STACK_MIN); + + /* Dispatch the threads. */ + for (nThreads = 0; nThreads < options->maxThreads; ++nThreads) { + sts = pthread_create(&threads[nThreads], &threadAttr, + attemptConnections, &context); + /* + * If we failed to create a thread, then we've reached the OS + * limit. + */ + if (sts != 0) + break; + } + + /* We no longer need this. */ + pthread_attr_destroy(&threadAttr); + } + } +#endif + + /* + * In addition to any threads we've dispatched, this thread can also + * participate in the probing. + */ + attemptConnections(&context); + +#if PM_MULTI_THREAD + if (threads) { + /* Wait for all the connection attempts to finish. */ + for (threadIx = 0; threadIx < nThreads; ++threadIx) + pthread_join(threads[threadIx], NULL); + } + + /* These must not be destroyed until all of the threads have finished. */ + pthread_mutex_destroy(&context.addrLock); + pthread_mutex_destroy(&context.urlLock); +#endif + + done: + free(ports); + if (context.nextAddress) + __pmSockAddrFree(context.nextAddress); +#if PM_MULTI_THREAD + if (threads) + free(threads); +#endif + + /* Return the number of new urls. */ + return numUrls - prevNumUrls; +} + +/* + * Parse the mechanism string for options. The first option will be of the form + * + * probe=<net-address>/<maskSize> + * + * Subsequent options, if any, will be separated by commas. Currently supported: + * + * maxThreads=<integer> -- specifies a hard limit on the number of active + * threads. + */ +static int +parseOptions(const char *mechanism, connectionOptions *options) +{ + const char *addressString; + const char *maskString; + size_t len; + char *buf; + char *end; + const char *option; + int family; + int sts; + long longVal; + unsigned subnetBits; + unsigned subnetSize; + + /* Nothing to probe? */ + if (mechanism == NULL) + return -1; + + /* First extract the subnet argument, parse it and check it. */ + addressString = strchr(mechanism, '='); + if (addressString == NULL || addressString[1] == '\0') { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: No argument provided"); + return -1; + } + ++addressString; + maskString = strchr(addressString, '/'); + if (maskString == NULL || maskString[1] == '\0') { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: No subnet mask provided"); + return -1; + } + ++maskString; + + /* Convert the address string to a socket address. */ + len = maskString - addressString; /* enough space for the nul */ + buf = malloc(len); + memcpy(buf, addressString, len - 1); + buf[len - 1] = '\0'; + options->netAddress = __pmStringToSockAddr(buf); + if (options->netAddress == NULL) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: Address '%s' is not valid", + buf); + free(buf); + return -1; + } + free(buf); + + /* Convert the mask string to an integer */ + options->maskBits = strtol(maskString, &end, 0); + if (*end != '\0' && *end != ',') { + __pmNotifyErr(LOG_ERR, "__pmProbeDiscoverServices: Subnet mask '%s' is not valid", + maskString); + return -1; + } + + /* Check the number of bits in the mask against the address family. */ + if (options->maskBits < 0) { + __pmNotifyErr(LOG_ERR, "__pmProbeDiscoverServices: Inet subnet mask must be >= 0 bits"); + return -1; + } + family = __pmSockAddrGetFamily(options->netAddress); + switch (family) { + case AF_INET: + if (options->maskBits > 32) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: Inet subnet mask must be <= 32 bits"); + return -1; + } + break; + case AF_INET6: + if (options->maskBits > 128) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: Inet subnet mask must be <= 128 bits"); + return -1; + } + break; + default: + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: Unsupported address family, %d", + family); + return -1; + } + + /* + * Parse any remaining options. + * Initialize to defaults first. + * + * FD_SETSIZE is the most open fds that __pmFD*() + * and __pmSelect() can deal with, so it's a decent default for maxThreads. + * The main thread also participates, so subtract 1. + */ + options->maxThreads = FD_SETSIZE - 1; + + /* + * Set a default for the connection timeout. 20ms allows us to scan 50 + * addresses per second per thread. + */ + options->timeout.tv_sec = 0; + options->timeout.tv_usec = 20 * 1000; + + /* Now parse the options. */ + sts = 0; + for (option = end; *option != '\0'; /**/) { + /* + * All additional options begin with a separating comma. + * Make sure something has been specified. + */ + ++option; + if (*option == '\0') { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: Missing option after ','"); + return -1; + } + + /* Examine the option. */ + if (strncmp(option, "maxThreads=", sizeof("maxThreads=") - 1) == 0) { + option += sizeof("maxThreads=") - 1; + longVal = strtol(option, &end, 0); + if (*end != '\0' && *end != ',') { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: maxThreads value '%s' is not valid", + option); + sts = -1; + } + else { + option = end; + /* + * Make sure the value is positive. Make sure that the given + * value does not exceed the existing value which is also the + * hard limit. + */ + if (longVal > options->maxThreads) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: maxThreads value %ld must not exceed %u", + longVal, options->maxThreads); + sts = -1; + } + else if (longVal <= 0) { + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: maxThreads value %ld must be positive", + longVal); + sts = -1; + } + else { +#if PM_MULTI_THREAD + /* The main thread participates, so reduce this by one. */ + options->maxThreads = longVal - 1; +#else + __pmNotifyErr(LOG_WARNING, + "__pmProbeDiscoverServices: no thread support. Ignoring maxThreads value %ld", + longVal); +#endif + } + } + } + else if (strncmp(option, "timeout=", sizeof("timeout=") - 1) == 0) { + option += sizeof("timeout=") - 1; + option = __pmServiceDiscoveryParseTimeout(option, &options->timeout); + } + else { + /* An invalid option. Skip it. */ + __pmNotifyErr(LOG_ERR, + "__pmProbeDiscoverServices: option '%s' is not valid", + option); + sts = -1; + ++option; + } + /* Locate the next option, if any. */ + for (/**/; *option != '\0' && *option != ','; ++option) + ; + } /* Parse additional options */ + + /* + * We now have a maximum for the number of threads + * but there's no point in creating more threads than the number of + * addresses in the subnet (less 1 for the main thread). + * + * We already know that the address is inet or ipv6 and that the + * number of mask bits is appropriate. + * + * Beware of overflow!!! If the calculation would have overflowed, + * then it means that the subnet is extremely large and therefore + * much larger than maxThreads anyway. + */ + if (__pmSockAddrIsInet(options->netAddress)) + subnetBits = 32 - options->maskBits; + else + subnetBits = 128 - options->maskBits; + if (subnetBits < sizeof(subnetSize) * 8) { + subnetSize = 1 << subnetBits; + if (subnetSize - 1 < options->maxThreads) + options->maxThreads = subnetSize - 1; + } + + return sts; +} + +int +__pmProbeDiscoverServices(const char *service, + const char *mechanism, + const __pmServiceDiscoveryOptions *globalOptions, + int numUrls, + char ***urls) +{ + connectionOptions options; + int sts; + + /* Interpret the mechanism string. */ + sts = parseOptions(mechanism, &options); + if (sts != 0) + return 0; + options.globalOptions = globalOptions; + + /* Everything checks out. Now do the actual probing. */ + numUrls = probeForServices(service, &options, numUrls, urls); + + /* Clean up */ + __pmSockAddrFree(options.netAddress); + + return numUrls; +} diff --git a/src/libpcp/src/probe.h b/src/libpcp/src/probe.h new file mode 100644 index 0000000..fd04f56 --- /dev/null +++ b/src/libpcp/src/probe.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 Red Hat. + * + * 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. + */ +#ifndef PROBE_H +#define PROBE_H + +int __pmProbeDiscoverServices(const char *, + const char *, + const __pmServiceDiscoveryOptions *, + int, + char ***) _PCP_HIDDEN; + +#endif /* PROBE_H */ diff --git a/src/libpcp/src/profile.c b/src/libpcp/src/profile.c new file mode 100644 index 0000000..0127ea3 --- /dev/null +++ b/src/libpcp/src/profile.c @@ -0,0 +1,385 @@ +/* + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" + +static int * +_subtract(int *list, int *list_len, int *arg, int arg_len) +{ + int *new; + int len = *list_len; + int new_len = 0; + int i, j; + + if (list == NULL) + /* noop */ + return NULL; + + new = (int *)malloc(len * sizeof(int)); + if (new == NULL) + return NULL; + + for (i=0; i < len; i++) { + for (j=0; j < arg_len; j++) + if (list[i] == arg[j]) + break; + if (j == arg_len) + /* this instance survived */ + new[new_len++] = list[i]; + } + free(list); + *list_len = new_len; + return new; +} + +static int * +_union(int *list, int *list_len, int *arg, int arg_len) +{ + int *new; + int len = *list_len; + int new_len = *list_len; + int i, j; + + if (list == NULL) { + list = (int *)malloc(arg_len * sizeof(int)); + memcpy((void *)list, (void *)arg, arg_len * sizeof(int)); + *list_len = arg_len; + return list; + } + + new = (int *)realloc((void *)list, (len + arg_len) * sizeof(int)); + if (new == NULL) + return NULL; + + for (i=0; i < arg_len; i++) { + for (j=0; j < new_len; j++) { + if (arg[i] == new[j]) + break; + } + if (j == new_len) + /* instance is not already in the list */ + new[new_len++] = arg[i]; + } + *list_len = new_len; + return new; +} + +static void +_setGlobalState(__pmContext *ctxp, int state) +{ + __pmInDomProfile *p, *p_end; + + /* free everything and set the global state */ + if (ctxp->c_instprof->profile) { + for (p=ctxp->c_instprof->profile, p_end = p + ctxp->c_instprof->profile_len; + p < p_end; p++) { + if (p->instances) + free(p->instances); + p->instances_len = 0; + } + + free(ctxp->c_instprof->profile); + ctxp->c_instprof->profile = NULL; + ctxp->c_instprof->profile_len = 0; + } + + ctxp->c_instprof->state = state; + ctxp->c_sent = 0; +} + +static __pmInDomProfile * +_newprof(pmInDom indom, __pmContext *ctxp) +{ + __pmInDomProfile *p; + + if (ctxp->c_instprof->profile == NULL) { + /* create a new profile for this inDom in the default state */ + p = ctxp->c_instprof->profile = (__pmInDomProfile *)malloc( + sizeof(__pmInDomProfile)); + if (p == NULL) + /* fail, no changes */ + return NULL; + ctxp->c_instprof->profile_len = 1; + } + else { + /* append a new profile to the end of the list */ + ctxp->c_instprof->profile_len++; + p = (__pmInDomProfile *)realloc((void *)ctxp->c_instprof->profile, + ctxp->c_instprof->profile_len * sizeof(__pmInDomProfile)); + if (p == NULL) + /* fail, no changes */ + return NULL; + ctxp->c_instprof->profile = p; + p = ctxp->c_instprof->profile + ctxp->c_instprof->profile_len - 1; + } + + /* initialise a new profile entry : default = include all instances */ + p->indom = indom; + p->instances = NULL; + p->instances_len = 0; + p->state = PM_PROFILE_INCLUDE; + return p; +} + +__pmInDomProfile * +__pmFindProfile(pmInDom indom, const __pmProfile *prof) +{ + __pmInDomProfile *p, *p_end; + + if (prof != NULL && prof->profile_len > 0) + /* search for the profile entry for this instance domain */ + for (p=prof->profile, p_end=p+prof->profile_len; p < p_end; p++) { + if (p->indom == indom) + /* found : an entry for this instance domain already exists */ + return p; + } + + /* not found */ + return NULL; +} + +int +__pmInProfile(pmInDom indom, const __pmProfile *prof, int inst) +{ + __pmInDomProfile *p; + int *in, *in_end; + + if (prof == NULL) + /* default if no profile for any instance domains */ + return 1; + + if ((p = __pmFindProfile(indom, prof)) == NULL) + /* no profile for this indom => use global default */ + return (prof->state == PM_PROFILE_INCLUDE) ? 1 : 0; + + for (in=p->instances, in_end=in+p->instances_len; in < in_end; in++) + if (*in == inst) + /* present in the list => inverse of default for this indom */ + return (p->state == PM_PROFILE_INCLUDE) ? 0 : 1; + + /* not in the list => use default for this indom */ + return (p->state == PM_PROFILE_INCLUDE) ? 1 : 0; +} + +void +__pmFreeProfile(__pmProfile *prof) +{ + __pmInDomProfile *p, *p_end; + + if (prof != NULL) { + if (prof->profile != NULL) { + for (p=prof->profile, p_end = p+prof->profile_len; p < p_end; p++) { + if (p->instances) + free(p->instances); + } + if (prof->profile_len) + free(prof->profile); + } + free(prof); + } +} + +int +pmAddProfile(pmInDom indom, int instlist_len, int instlist[]) +{ + int sts; + __pmContext *ctxp; + __pmInDomProfile *prof; + + if (indom == PM_INDOM_NULL && instlist != NULL) + /* semantic disconnect! */ + return PM_ERR_PROFILESPEC; + + if ((sts = pmWhichContext()) < 0) + return sts; + ctxp = __pmHandleToPtr(sts); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + + if (indom == PM_INDOM_NULL && instlist_len == 0) { + _setGlobalState(ctxp, PM_PROFILE_INCLUDE); + goto SUCCESS; + } + + if ((prof = __pmFindProfile(indom, ctxp->c_instprof)) == NULL) { + if ((prof = _newprof(indom, ctxp)) == NULL) { + /* fail */ + PM_UNLOCK(ctxp->c_lock); + return -oserror(); + } + else { + /* starting state: exclude all except the supplied list */ + prof->state = PM_PROFILE_EXCLUDE; + } + } + + /* include all instances? */ + if (instlist_len == 0 || instlist == NULL) { + /* include all instances in this domain */ + if (prof->instances) + free(prof->instances); + prof->instances = NULL; + prof->instances_len = 0; + prof->state = PM_PROFILE_INCLUDE; + goto SUCCESS; + } + + switch (prof->state) { + case PM_PROFILE_INCLUDE: + /* + * prof->instances is an exclusion list (all else included) + * => traverse and remove the specified instances (if present) + */ + prof->instances = _subtract( + prof->instances, &prof->instances_len, + instlist, instlist_len); + break; + + case PM_PROFILE_EXCLUDE: + /* + * prof->instances is an inclusion list (all else excluded) + * => traverse and add the specified instances (if not already present) + */ + prof->instances = _union( + prof->instances, &prof->instances_len, + instlist, instlist_len); + break; + } + +SUCCESS: + ctxp->c_sent = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PROFILE) { + char strbuf[20]; + fprintf(stderr, "pmAddProfile() indom: %s\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + __pmDumpProfile(stderr, indom, ctxp->c_instprof); + } +#endif + PM_UNLOCK(ctxp->c_lock); + return 0; +} + +int +pmDelProfile(pmInDom indom, int instlist_len, int instlist[]) +{ + int sts; + __pmContext *ctxp; + __pmInDomProfile *prof; + + if (indom == PM_INDOM_NULL && instlist != NULL) + /* semantic disconnect! */ + return PM_ERR_PROFILESPEC; + + if ((sts = pmWhichContext()) < 0) + return sts; + ctxp = __pmHandleToPtr(sts); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + + if (indom == PM_INDOM_NULL && instlist_len == 0) { + _setGlobalState(ctxp, PM_PROFILE_EXCLUDE); + goto SUCCESS; + } + + if ((prof = __pmFindProfile(indom, ctxp->c_instprof)) == NULL) { + if ((prof = _newprof(indom, ctxp)) == NULL) { + /* fail */ + PM_UNLOCK(ctxp->c_lock); + return -oserror(); + } + else { + /* starting state: include all except the supplied list */ + prof->state = PM_PROFILE_EXCLUDE; + } + } + + /* include all instances? */ + if (instlist_len == 0 || instlist == NULL) { + /* include all instances in this domain */ + if (prof->instances) + free(prof->instances); + prof->instances = NULL; + prof->instances_len = 0; + prof->state = PM_PROFILE_EXCLUDE; + goto SUCCESS; + } + + switch (prof->state) { + case PM_PROFILE_INCLUDE: + /* + * prof->instances is an exclusion list (all else included) + * => traverse and add the specified instances (if not already present) + */ + prof->instances = _union( + prof->instances, &prof->instances_len, + instlist, instlist_len); + break; + + case PM_PROFILE_EXCLUDE: + /* + * prof->instances is an inclusion list (all else excluded) + * => traverse and remove the specified instances (if present) + */ + prof->instances = _subtract( + prof->instances, &prof->instances_len, + instlist, instlist_len); + break; + } + +SUCCESS: + ctxp->c_sent = 0; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PROFILE) { + char strbuf[20]; + fprintf(stderr, "pmDelProfile() indom: %s\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + __pmDumpProfile(stderr, indom, ctxp->c_instprof); + } +#endif + PM_UNLOCK(ctxp->c_lock); + return 0; +} + +void +__pmDumpProfile(FILE *f, int indom, const __pmProfile *pp) +{ + int j; + int k; + __pmInDomProfile *prof; + char strbuf[20]; + + fprintf(f, "Dump Instance Profile state=%s, %d profiles", + pp->state == PM_PROFILE_INCLUDE ? "INCLUDE" : "EXCLUDE", + pp->profile_len); + if (indom != PM_INDOM_NULL) + fprintf(f, ", dump restricted to indom=%d [%s]", + indom, pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + fprintf(f, "\n"); + + for (prof=pp->profile, j=0; j < pp->profile_len; j++, prof++) { + if (indom != PM_INDOM_NULL && indom != prof->indom) + continue; + fprintf(f, "\tProfile [%d] indom=%d [%s] state=%s %d instances\n", + j, prof->indom, pmInDomStr_r(prof->indom, strbuf, sizeof(strbuf)), + (prof->state == PM_PROFILE_INCLUDE) ? "INCLUDE" : "EXCLUDE", + prof->instances_len); + + if (prof->instances_len) { + fprintf(f, "\t\tInstances:"); + for (k=0; k < prof->instances_len; k++) + fprintf(f, " [%d]", prof->instances[k]); + fprintf(f, "\n"); + } + } +} diff --git a/src/libpcp/src/rtime.c b/src/libpcp/src/rtime.c new file mode 100644 index 0000000..b62cd9c --- /dev/null +++ b/src/libpcp/src/rtime.c @@ -0,0 +1,761 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <limits.h> +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" + + +#define CODE3(a,b,c) ((__uint32_t)(a)|((__uint32_t)(b)<<8)|((__uint32_t)(c)<<16)) +#define whatmsg "unexpected value" +#define moremsg "more information expected" +#define alignmsg "alignment specified by -A switch could not be applied" + +#define N_WDAYS 7 +static const __uint32_t wdays[N_WDAYS] = { + CODE3('s', 'u', 'n'), + CODE3('m', 'o', 'n'), + CODE3('t', 'u', 'e'), + CODE3('w', 'e', 'd'), + CODE3('t', 'h', 'u'), + CODE3('f', 'r', 'i'), + CODE3('s', 'a', 't') +}; + +#define N_MONTHS 12 +static const __uint32_t months[N_MONTHS] = { + CODE3('j', 'a', 'n'), + CODE3('f', 'e', 'b'), + CODE3('m', 'a', 'r'), + CODE3('a', 'p', 'r'), + CODE3('m', 'a', 'y'), + CODE3('j', 'u', 'n'), + CODE3('j', 'u', 'l'), + CODE3('a', 'u', 'g'), + CODE3('s', 'e', 'p'), + CODE3('o', 'c', 't'), + CODE3('n', 'o', 'v'), + CODE3('d', 'e', 'c') +}; + +#define N_AMPM 2 +static const __uint32_t ampm[N_AMPM] = { + CODE3('a', 'm', 0 ), + CODE3('p', 'm', 0 ) +}; + +static const struct { + char *name; /* pmParseInterval scale name */ + int len; /* length of scale name */ + int scale; /* <0 -divisor, else multiplier */ +} int_tab[] = { + { "millisecond", 11, -1000 }, + { "second", 6, 1 }, + { "minute", 6, 60 }, + { "hour", 4, 3600 }, + { "day", 3, 86400 }, + { "msec", 4, -1000 }, + { "sec", 3, 1 }, + { "min", 3, 60 }, + { "hr", 2, 3600 }, + { "s", 1, 1 }, + { "m", 1, 60 }, + { "h", 1, 3600 }, + { "d", 1, 86400 }, +}; +static const int numint = sizeof(int_tab) / sizeof(int_tab[0]); + +#define NO_OFFSET 0 +#define PLUS_OFFSET 1 +#define NEG_OFFSET 2 + + +/* Compare struct timevals */ +static int /* 0 -> equal, -1 -> tv1 < tv2, 1 -> tv1 > tv2 */ +tvcmp(struct timeval tv1, struct timeval tv2) +{ + if (tv1.tv_sec < tv2.tv_sec) + return -1; + if (tv1.tv_sec > tv2.tv_sec) + return 1; + if (tv1.tv_usec < tv2.tv_usec) + return -1; + if (tv1.tv_usec > tv2.tv_usec) + return 1; + return 0; +} + +/* Recognise three character string as one of the given codes. + return: 1 == ok, 0 <= *rslt <= ncodes-1, *spec points to following char + 0 == not found, *spec updated to strip blanks */ +static int +parse3char(const char **spec, const __uint32_t *codes, int ncodes, int *rslt) +{ + const char *scan = *spec; + __uint32_t code = 0; + int i; + + while (isspace((int)*scan)) + scan++; + *spec = scan; + if (! isalpha((int)*scan)) + return 0; + for (i = 0; i <= 16; i += 8) { + code |= (tolower((int)*scan) << i); + scan++; + if (! isalpha((int)*scan)) + break; + } + for (i = 0; i < ncodes; i++) { + if (code == codes[i]) { + *spec = scan; + *rslt = i; + return 1; + } + } + return 0; +} + +/* Recognise single char (with optional leading space) */ +static int +parseChar(const char **spec, char this) +{ + const char *scan = *spec; + + while (isspace((int)*scan)) + scan++; + *spec = scan; + if (*scan != this) + return 0; + *spec = scan + 1; + return 1; +} + +/* Recognise integer in range min..max + return: 1 == ok, min <= *rslt <= max, *spec points to following char + 0 == not found, *spec updated to strip blanks */ +static int +parseInt(const char **spec, int min, int max, int *rslt) +{ + const char *scan = *spec; + char *tmp; + long r; + + while (isspace((int)*scan)) + scan++; + tmp = (char *)scan; + r = strtol(scan, &tmp, 10); + *spec = tmp; + if (scan == *spec || r < min || r > max) { + *spec = scan; + return 0; + } + *rslt = (int)r; + return 1; +} + +/* Recognise double precision float in the range min..max + return: 1 == ok, min <= *rslt <= max, *spec points to following char + 0 == not found, *spec updated to strip blanks */ +static double +parseDouble(const char **spec, double min, double max, double *rslt) +{ + const char *scan = *spec; + char *tmp; + double r; + + while (isspace((int)*scan)) + scan++; + tmp = (char *)scan; + r = strtod(scan, &tmp); + *spec = tmp; + if (scan == *spec || r < min || r > max) { + *spec = scan; + return 0; + } + *rslt = r; + return 1; +} + +/* Construct error message buffer for syntactic error */ +static void +parseError(const char *spec, const char *point, char *msg, char **rslt) +{ + int need = 2 * (int)strlen(spec) + (int)strlen(msg) + 8; + const char *p; + char *q; + + if ((*rslt = malloc(need)) == NULL) + __pmNoMem("__pmParseTime", need, PM_FATAL_ERR); + q = *rslt; + + for (p = spec; *p != '\0'; p++) + *q++ = *p; + *q++ = '\n'; + for (p = spec; p != point; p++) + *q++ = isgraph((int)*p) ? ' ' : *p; + sprintf(q, "^ -- "); + q += 5; + for (p = msg; *p != '\0'; p++) + *q++ = *p; + *q++ = '\n'; + *q = '\0'; +} + + +int /* 0 -> ok, -1 -> error */ +pmParseInterval( + const char *spec, /* interval to parse */ + struct timeval *rslt, /* result stored here */ + char **errmsg) /* error message */ +{ + const char *scan = spec; + double d; + double sec = 0.0; + int i; + const char *p; + int len; + + if (scan == NULL || *scan == '\0') { + const char *empty = ""; + parseError(empty, empty, "Null or empty specification", errmsg); + return -1; + } + + /* parse components of spec */ + while (*scan != '\0') { + if (! parseDouble(&scan, 0.0, (double)INT_MAX, &d)) + break; + while (*scan != '\0' && isspace((int)*scan)) + scan++; + if (*scan == '\0') { + /* no scale, seconds is the default */ + sec += d; + break; + } + for (p = scan; *p && isalpha((int)*p); p++) + ; + len = (int)(p - scan); + if (len == 0) + /* no scale, seconds is the default */ + sec += d; + else { + if (len > 1 && (p[-1] == 's' || p[-1] == 'S')) + /* ignore any trailing 's' */ + len--; + for (i = 0; i < numint; i++) { + if (len != int_tab[i].len) + continue; + if (strncasecmp(scan, int_tab[i].name, len) == 0) + break; + } + if (i == numint) + break; + if (int_tab[i].scale < 0) + sec += d / (-int_tab[i].scale); + else + sec += d * int_tab[i].scale; + } + scan = p; + } + + /* error detection */ + if (*scan != '\0') { + parseError(spec, scan, whatmsg, errmsg); + return -1; + } + + /* convert into seconds and microseconds */ + rslt->tv_sec = (time_t)sec; + rslt->tv_usec = (int)(1000000.0 * (sec - (double)rslt->tv_sec)); + return 0; +} + + +int /* 0 -> ok, -1 -> error */ +__pmParseCtime( + const char *spec, /* ctime string to parse */ + struct tm *rslt, /* result stored here */ + char **errmsg) /* error message */ +{ + struct tm tm = {-1, -1, -1, -1, -1, -1, NO_OFFSET, -1, -1}; + double d; + const char *scan = spec; + int pm = -1; + int ignored = -1; + int dflt; + + /* parse time spec */ + parse3char(&scan, wdays, N_WDAYS, &ignored); + parse3char(&scan, months, N_MONTHS, &tm.tm_mon); + + parseInt(&scan, 0, 31, &tm.tm_mday); + parseInt(&scan, 0, 23, &tm.tm_hour); + if (tm.tm_mday == 0 && tm.tm_hour != -1) { + tm.tm_mday = -1; + } + if (tm.tm_hour == -1 && tm.tm_mday >= 0 && tm.tm_mday <= 23 && + (tm.tm_mon == -1 || *scan == ':')) { + tm.tm_hour = tm.tm_mday; + tm.tm_mday = -1; + } + if (parseChar(&scan, ':')) { + if (tm.tm_hour == -1) + tm.tm_hour = 0; + tm.tm_min = 0; /* for moreError checking */ + parseInt(&scan, 0, 59, &tm.tm_min); + if (parseChar(&scan, ':')) { + if (parseDouble(&scan, 0.0, 61.0, &d)) { + tm.tm_sec = (time_t)d; + tm.tm_yday = (int)(1000000.0 * (d - tm.tm_sec)); + } + } + } + if (parse3char(&scan, ampm, N_AMPM, &pm)) { + if (tm.tm_hour > 12 || tm.tm_hour == -1) + scan -= 2; + else { + if (pm) { + if (tm.tm_hour < 12) + tm.tm_hour += 12; + } + else { + if (tm.tm_hour == 12) + tm.tm_hour = 0; + } + } + } + /* + * parse range forces tm_year to be >= 1900, so this is Y2K safe + */ + if (parseInt(&scan, 1900, 9999, &tm.tm_year)) + tm.tm_year -= 1900; + + /* + * error detection and reporting + * + * in the code below, tm_year is either years since 1900 or + * -1 (a sentinel), so this is is Y2K safe + */ + while (isspace((int)*scan)) + scan++; + if (*scan != '\0') { + parseError(spec, scan, whatmsg, errmsg); + return -1; + } + if ((ignored != -1 && tm.tm_mon == -1 && tm.tm_mday == -1) || + (tm.tm_hour != -1 && tm.tm_min == -1 && tm.tm_mday == -1 && + tm.tm_mon == -1 && tm.tm_year == -1)) { + parseError(spec, scan, moremsg, errmsg); + return -1; + } + + /* fill in elements of tm from spec */ + dflt = (tm.tm_year != -1); + if (tm.tm_mon != -1) + dflt = 1; + else if (dflt) + tm.tm_mon = 0; + if (tm.tm_mday != -1) + dflt = 1; + else if (dflt) + tm.tm_mday = 1; + if (tm.tm_hour != -1) + dflt = 1; + else if (dflt) + tm.tm_hour = 0; + if (tm.tm_min != -1) + dflt = 1; + else if (dflt) + tm.tm_min = 0; + if (tm.tm_sec == -1 && dflt) { + tm.tm_sec = 0; + tm.tm_yday = 0; + } + + *rslt = tm; + return 0; +} + + +int /* 0 ok, -1 error */ +__pmConvertTime( + struct tm *tmin, /* absolute or +ve or -ve offset time */ + struct timeval *origin, /* defaults and origin for offset */ + struct timeval *rslt) /* result stored here */ +{ + time_t t; + struct timeval tval = *origin; + struct tm tm; + + /* positive offset interval */ + if (tmin->tm_wday == PLUS_OFFSET) { + tval.tv_usec += tmin->tm_yday; + if (tval.tv_usec > 1000000) { + tval.tv_usec -= 1000000; + tval.tv_sec++; + } + tval.tv_sec += tmin->tm_sec; + } + + /* negative offset interval */ + else if (tmin->tm_wday == NEG_OFFSET) { + if (tval.tv_usec < tmin->tm_yday) { + tval.tv_usec += 1000000; + tval.tv_sec--; + } + tval.tv_usec -= tmin->tm_yday; + tval.tv_sec -= tmin->tm_sec; + } + + /* absolute time */ + else { + /* tmin completely specified */ + if (tmin->tm_year != -1) { + tm = *tmin; + tval.tv_usec = tmin->tm_yday; + } + + /* tmin partially specified */ + else { + t = (time_t)tval.tv_sec; + pmLocaltime(&t, &tm); + tm.tm_isdst = -1; + + /* fill in elements of tm from spec */ + if (tmin->tm_mon != -1) { + if (tmin->tm_mon < tm.tm_mon) + /* + * tm_year is years since 1900 and the tm_year++ is + * adjusting for the specified month being before the + * current month, so this is Y2K safe + */ + tm.tm_year++; + tm.tm_mon = tmin->tm_mon; + tm.tm_mday = tmin->tm_mday; + tm.tm_hour = tmin->tm_hour; + tm.tm_min = tmin->tm_min; + tm.tm_sec = tmin->tm_sec; + tval.tv_usec = tmin->tm_yday; + } + else if (tmin->tm_mday != -1) { + if (tmin->tm_mday < tm.tm_mday) + tm.tm_mon++; + tm.tm_mday = tmin->tm_mday; + tm.tm_hour = tmin->tm_hour; + tm.tm_min = tmin->tm_min; + tm.tm_sec = tmin->tm_sec; + tval.tv_usec = tmin->tm_yday; + } + else if (tmin->tm_hour != -1) { + if (tmin->tm_hour < tm.tm_hour) + tm.tm_mday++; + tm.tm_hour = tmin->tm_hour; + tm.tm_min = tmin->tm_min; + tm.tm_sec = tmin->tm_sec; + tval.tv_usec = tmin->tm_yday; + } + else if (tmin->tm_min != -1) { + if (tmin->tm_min < tm.tm_min) + tm.tm_hour++; + tm.tm_min = tmin->tm_min; + tm.tm_sec = tmin->tm_sec; + tval.tv_usec = tmin->tm_yday; + } + else if (tmin->tm_sec != -1) { + if (tmin->tm_sec < tm.tm_sec) + tm.tm_min++; + tm.tm_sec = tmin->tm_sec; + tval.tv_usec = tmin->tm_yday; + } + } + tval.tv_sec = __pmMktime(&tm); + } + + *rslt = tval; + return 0; +} + + +/* + * Use heuristics to determine the presence of a relative date time + * and its direction + */ +static int +glib_relative_date(const char *date_string) +{ + /* + * Time terms most commonly used with an adjective modifier are + * relative to the start/end time + * e.g. last year, 2 year ago, next hour, -1 minute + */ + char * const startend_relative_terms[] = { + " YEAR", + " MONTH", + " FORTNIGHT", + " WEEK", + " DAY", + " HOUR", + " MINUTE", + " MIN", + " SECOND", + " SEC" + }; + + /* + * Time terms for a specific day are relative to the current time + * TOMORROW, YESTERDAY, TODAY, NOW, MONDAY-SUNDAY + */ + int rtu_bound = sizeof(startend_relative_terms) / sizeof(void *); + int rtu_idx; + + while (isspace((int)*date_string)) + date_string++; + for (rtu_idx = 0; rtu_idx < rtu_bound; rtu_idx++) + if (strcasestr(date_string, startend_relative_terms[rtu_idx]) != NULL) + break; + if (rtu_idx < rtu_bound) { + if (strcasestr(date_string, "last") != NULL || + strcasestr(date_string, "ago") != NULL || + date_string[0] == '-') + return NEG_OFFSET; + else + return PLUS_OFFSET; + } + return NO_OFFSET; +} + +/* + * Helper interface to wrap calls to the __pmGlibGetDate interface + */ +static int +glib_get_date( + const char *scan, + struct timeval *start, + struct timeval *end, + struct timeval *rslt) +{ + int sts; + int rel_type; + struct timespec tsrslt; + + rel_type = glib_relative_date(scan); + + if (rel_type == NO_OFFSET) + sts = __pmGlibGetDate(&tsrslt, scan, NULL); + else if (rel_type == NEG_OFFSET && end->tv_sec < INT_MAX) { + struct timespec tsend; + tsend.tv_sec = end->tv_sec; + tsend.tv_nsec = end->tv_usec * 1000; + sts = __pmGlibGetDate(&tsrslt, scan, &tsend); + } + else { + struct timespec tsstart; + tsstart.tv_sec = start->tv_sec; + tsstart.tv_nsec = start->tv_usec * 1000; + sts = __pmGlibGetDate(&tsrslt, scan, &tsstart); + } + if (sts < 0) + return sts; + + rslt->tv_sec = tsrslt.tv_sec; + rslt->tv_usec = tsrslt.tv_nsec / 1000; + return 0; +} + +int /* 0 -> ok, -1 -> error */ +__pmParseTime( + const char *string, /* string to be parsed */ + struct timeval *logStart, /* start of log or current time */ + struct timeval *logEnd, /* end of log or tv_sec == INT_MAX */ + /* assumes sizeof(t_time) == sizeof(int) */ + struct timeval *rslt, /* if parsed ok, result filled in */ + char **errMsg) /* error message, please free */ +{ + struct tm tm; + const char *scan; + struct timeval start; + struct timeval end; + struct timeval tval; + + *errMsg = NULL; + start = *logStart; + end = *logEnd; + if (end.tv_sec == INT_MAX) + end.tv_usec = 999999; + scan = string; + + /* ctime string */ + if (parseChar(&scan, '@')) { + if (__pmParseCtime(scan, &tm, errMsg) >= 0) { + tm.tm_wday = NO_OFFSET; + __pmConvertTime(&tm, &start, rslt); + return 0; + } + } + + /* relative to end of archive */ + else if (end.tv_sec < INT_MAX && parseChar(&scan, '-')) { + if (pmParseInterval(scan, &tval, errMsg) >= 0) { + tm.tm_wday = NEG_OFFSET; + tm.tm_sec = (int)tval.tv_sec; + tm.tm_yday = (int)tval.tv_usec; + __pmConvertTime(&tm, &end, rslt); + return 0; + } + } + + /* relative to start of archive or current time */ + else { + parseChar(&scan, '+'); + if (pmParseInterval(scan, &tval, errMsg) >= 0) { + tm.tm_wday = PLUS_OFFSET; + tm.tm_sec = (int)tval.tv_sec; + tm.tm_yday = (int)tval.tv_usec; + __pmConvertTime(&tm, &start, rslt); + return 0; + } + } + + /* datetime is not recognised, try the glib_get_date method */ + parseChar(&scan, '@'); /* ignore; glib_relative_date determines type */ + if (glib_get_date(scan, &start, &end, rslt) < 0) + return -1; + + if (*errMsg) + free(*errMsg); + return 0; +} + + +/* This function is designed to encapsulate the interpretation of + the -S, -T, -A and -O command line switches for use by the PCP + client tools. */ +int /* 1 -> ok, 0 -> warning, -1 -> error */ +pmParseTimeWindow( + const char *swStart, /* argument of -S switch, may be NULL */ + const char *swEnd, /* argument of -T switch, may be NULL */ + const char *swAlign, /* argument of -A switch, may be NULL */ + const char *swOffset, /* argument of -O switch, may be NULL */ + const struct timeval *logStart, /* start of log or current time */ + const struct timeval *logEnd, /* end of log or tv_sec == INT_MAX */ + struct timeval *rsltStart, /* start time returned here */ + struct timeval *rsltEnd, /* end time returned here */ + struct timeval *rsltOffset,/* offset time returned here */ + char **errMsg) /* error message, please free */ +{ + struct timeval astart; + struct timeval start; + struct timeval end; + struct timeval offset; + struct timeval aoffset; + struct timeval tval; + const char *scan; + __int64_t delta = 0; /* initialize to pander to gcc */ + __int64_t align; + __int64_t blign; + int sts = 1; + + /* default values for start and end */ + start = *logStart; + end = *logEnd; + if (end.tv_sec == INT_MAX) + end.tv_usec = 999999; + + /* parse -S argument and adjust start accordingly */ + if (swStart) { + if (__pmParseTime(swStart, &start, &end, &start, errMsg) < 0) + return -1; + } + + /* sanity check -S */ + if (tvcmp(start, *logStart) < 0) + /* move start forwards to the beginning of the archive */ + start = *logStart; + + /* parse -A argument and adjust start accordingly */ + if (swAlign) { + scan = swAlign; + if (pmParseInterval(scan, &tval, errMsg) < 0) + return -1; + delta = tval.tv_usec + 1000000 * (__int64_t)tval.tv_sec; + align = start.tv_usec + 1000000 * (__int64_t)start.tv_sec; + blign = (align / delta) * delta; + if (blign < align) + blign += delta; + astart.tv_sec = (time_t)(blign / 1000000); + astart.tv_usec = (int)(blign % 1000000); + + /* sanity check -S after alignment */ + if (tvcmp(astart, *logStart) >= 0 && tvcmp(astart, *logEnd) <= 0) + start = astart; + else { + parseError(swAlign, swAlign, alignmsg, errMsg); + sts = 0; + } + } + + /* parse -T argument and adjust end accordingly */ + if (swEnd) { + if (__pmParseTime(swEnd, &start, &end, &end, errMsg) < 0) + return -1; + } + + /* sanity check -T */ + if (tvcmp(end, *logEnd) > 0) + /* move end backwards to the end of the archive */ + end = *logEnd; + + /* parse -O argument and align if required */ + offset = start; + if (swOffset) { + if (__pmParseTime(swOffset, &start, &end, &offset, errMsg) < 0) + return -1; + + /* sanity check -O */ + if (tvcmp(offset, start) < 0) + offset = start; + else if (tvcmp(offset, end) > 0) + offset = end; + + if (swAlign) { + align = offset.tv_usec + 1000000 * (__int64_t)offset.tv_sec; + blign = (align / delta) * delta; + if (blign < align) + blign += delta; + align = end.tv_usec + 1000000 * (__int64_t)end.tv_sec; + if (blign > align) + blign -= delta; + aoffset.tv_sec = (time_t)(blign / 1000000); + aoffset.tv_usec = (int)(blign % 1000000); + + /* sanity check -O after alignment */ + if (tvcmp(aoffset, start) >= 0 && tvcmp(aoffset, end) <= 0) + offset = aoffset; + else { + parseError(swAlign, swAlign, alignmsg, errMsg); + sts = 0; + } + } + } + + /* return results */ + *rsltStart = start; + *rsltEnd = end; + *rsltOffset = offset; + return sts; +} diff --git a/src/libpcp/src/secureconnect.c b/src/libpcp/src/secureconnect.c new file mode 100644 index 0000000..3391329 --- /dev/null +++ b/src/libpcp/src/secureconnect.c @@ -0,0 +1,1575 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Security and Authentication (NSS and SASL) support. Client side. + * + * 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. + */ + +#include "pmapi.h" +#include "impl.h" +#define SOCKET_INTERNAL +#include "internal.h" +#include <assert.h> +#include <hasht.h> +#include <certdb.h> +#include <secerr.h> +#include <sslerr.h> +#include <pk11pub.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_TERMIOS_H +#include <sys/termios.h> +#endif + +/* + * We shift NSS/NSPR/SSL/SASL errors below the valid range for other + * PCP error codes, in order to avoid conflicts. pmErrStr can then + * detect and decode. PM_ERR_NYI is the PCP error code sentinel. + */ +int +__pmSecureSocketsError(int code) +{ + int sts = (PM_ERR_NYI + code); /* encode, negative value */ + setoserror(-sts); + return sts; +} + +int +__pmSocketClosed(void) +{ + int error = oserror(); + + if (PM_ERR_NYI > -error) + error = -(error + PM_ERR_NYI); + + switch (error) { + /* + * Treat this like end of file on input. + * + * failed as a result of pmcd exiting and the connection + * being reset, or as a result of the kernel ripping + * down the connection (most likely because the host at + * the other end just took a dive) + * + * from IRIX BDS kernel sources, seems like all of the + * following are peers here: + * ECONNRESET (pmcd terminated?) + * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH + * ECONNREFUSED + * peers for BDS but not here: + * ENETRESET ENONET ESHUTDOWN (cache_fs only?) + * ECONNABORTED (accept, user req only?) + * ENOTCONN (udp?) + * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?) + */ + case ECONNRESET: + case EPIPE: + case ETIMEDOUT: + case ENETDOWN: + case ENETUNREACH: + case EHOSTDOWN: + case EHOSTUNREACH: + case ECONNREFUSED: + case PR_IO_TIMEOUT_ERROR: + case PR_NETWORK_UNREACHABLE_ERROR: + case PR_CONNECT_TIMEOUT_ERROR: + case PR_NOT_CONNECTED_ERROR: + case PR_CONNECT_RESET_ERROR: + case PR_PIPE_ERROR: + case PR_NETWORK_DOWN_ERROR: + case PR_SOCKET_SHUTDOWN_ERROR: + case PR_HOST_UNREACHABLE_ERROR: + return 1; + } + return 0; +} + +/* + * For every connection when operating under secure socket mode, we need + * the following auxillary structure associated with the socket. It holds + * critical information that each piece of the security pie can make use + * of (NSS/SSL/NSPR/SASL). This is allocated once a connection is upgraded + * from insecure to secure. + */ +typedef struct { + PRFileDesc *nsprFd; + PRFileDesc *sslFd; + sasl_conn_t *saslConn; + sasl_callback_t *saslCB; +} __pmSecureSocket; + +int +__pmDataIPCSize(void) +{ + return sizeof(__pmSecureSocket); +} + +int +__pmInitSecureSockets(void) +{ + /* Make sure that NSPR has been initialized */ + if (PR_Initialized() != PR_TRUE) + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + return 0; +} + +int +__pmShutdownSecureSockets(void) +{ + if (PR_Initialized() == PR_TRUE) + PR_Cleanup(); + return 0; +} + +static int +__pmSetupSecureSocket(int fd, __pmSecureSocket *socket) +{ + /* Is this socket already set up? */ + if (socket->nsprFd) + return 0; + + /* Import the fd into NSPR. */ + socket->nsprFd = PR_ImportTCPSocket(fd); + if (! socket->nsprFd) + return -1; + + return 0; +} + +void +__pmCloseSocket(int fd) +{ + __pmSecureSocket socket; + int sts; + + sts = __pmDataIPC(fd, (void *)&socket); + __pmResetIPC(fd); + + if (sts == 0) { + if (socket.saslConn) { + sasl_dispose(&socket.saslConn); + socket.saslConn = NULL; + } + if (socket.saslCB) { + free(socket.saslCB); + socket.saslCB = NULL; + } + if (socket.nsprFd) { + PR_Close(socket.nsprFd); + socket.nsprFd = NULL; + socket.sslFd = NULL; + fd = -1; + } + } + + if (fd != -1) { +#if defined(IS_MINGW) + closesocket(fd); +#else + close(fd); +#endif + } +} + +static char * +dbpath(char *path, size_t size, char *db_method) +{ + int sep = __pmPathSeparator(); + const char *empty_homedir = ""; + char *homedir = getenv("HOME"); + char *nss_method = getenv("PCP_SECURE_DB_METHOD"); + + if (homedir == NULL) + homedir = (char *)empty_homedir; + if (nss_method == NULL) + nss_method = db_method; + + /* + * Fill in a buffer with the users NSS database specification. + * Return a pointer to the filesystem path component - without + * the <method>:-prefix - for other routines to work with. + */ + snprintf(path, size, "%s%s" "%c" ".pki" "%c" "nssdb", + nss_method, homedir, sep, sep); + return path + strlen(nss_method); +} + +static char * +dbphrase(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + (void)arg; + if (retry) + return NULL; + assert(PK11_IsInternal(slot)); + return strdup(SECURE_USERDB_DEFAULT_KEY); +} + +int +__pmInitCertificates(void) +{ + char nssdb[MAXPATHLEN]; + PK11SlotInfo *slot; + SECStatus secsts; + static int initialized; + + /* Only attempt this once. */ + if (initialized) + return 0; + initialized = 1; + + PK11_SetPasswordFunc(dbphrase); + + /* + * Check for client certificate databases. We enforce use + * of the per-user shared NSS database at $HOME/.pki/nssdb + * For simplicity, we create this directory if we need to. + * If we cannot, we silently bail out so that users who're + * not using secure connections (initially everyone) don't + * have to diagnose / put up with spurious errors. + */ + if (__pmMakePath(dbpath(nssdb, sizeof(nssdb), "sql:"), 0700) < 0) + return 0; + secsts = NSS_InitReadWrite(nssdb); + + if (secsts != SECSuccess) { + /* fallback, older versions of NSS do not support sql: */ + dbpath(nssdb, sizeof(nssdb), ""); + secsts = NSS_InitReadWrite(nssdb); + } + + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + if ((slot = PK11_GetInternalKeySlot()) != NULL) { + if (PK11_NeedUserInit(slot)) + PK11_InitPin(slot, NULL, SECURE_USERDB_DEFAULT_KEY); + else if (PK11_NeedLogin(slot)) + PK11_Authenticate(slot, PR_FALSE, NULL); + PK11_FreeSlot(slot); + } + + /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */ + do { + const PRUint16 *cipher; + for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher) + SSL_CipherPolicySet(*cipher, SSL_ALLOWED); + } while (0); + SSL_ClearSessionCache(); + + return 0; +} + +int +__pmShutdownCertificates(void) +{ + if (NSS_Shutdown() != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + return 0; +} + +static void +saveUserCertificate(CERTCertificate *cert) +{ + SECStatus secsts; + PK11SlotInfo *slot = PK11_GetInternalKeySlot(); + CERTCertTrust *trust = NULL; + + secsts = PK11_ImportCert(slot, cert, CK_INVALID_HANDLE, + SECURE_SERVER_CERTIFICATE, PR_FALSE); + if (secsts != SECSuccess) + goto done; + + secsts = SECFailure; + trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust)); + if (!trust) + goto done; + + secsts = CERT_DecodeTrustString(trust, "P,P,P"); + if (secsts != SECSuccess) + goto done; + + secsts = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust); + +done: + if (slot) + PK11_FreeSlot(slot); + if (trust) + PORT_Free(trust); + + /* + * Issue a warning only, but continue, if we fail to save certificate + * (this is not a fatal condition on setting up the secure socket). + */ + if (secsts != SECSuccess) { + char errmsg[PM_MAXERRMSGLEN]; + pmprintf("WARNING: Failed to save certificate locally: %s\n", + pmErrStr_r(__pmSecureSocketsError(PR_GetError()), + errmsg, sizeof(errmsg))); + pmflush(); + } +} + +static int +rejectUserCertificate(const char *message) +{ + pmprintf("%s? (no)\n", message); + pmflush(); + return 0; +} + +#ifdef HAVE_SYS_TERMIOS_H +static int +queryCertificateOK(const char *message) +{ + int c, fd, sts = 0, count = 0; + + fd = fileno(stdin); + /* if we cannot interact, simply assume the answer to be "no". */ + if (!isatty(fd)) + return rejectUserCertificate(message); + + do { + struct termios saved, raw; + + pmprintf("%s (y/n)? ", message); + pmflush(); + + /* save terminal state and temporarily enter raw terminal mode */ + if (tcgetattr(fd, &saved) < 0) + return 0; + cfmakeraw(&raw); + if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) + return 0; + + c = getchar(); + if (c == 'y' || c == 'Y') + sts = 1; /* yes */ + else if (c == 'n' || c == 'N') + sts = 0; /* no */ + else + sts = -1; /* dunno, try again (3x) */ + tcsetattr(fd, TCSAFLUSH, &saved); + pmprintf("\n"); + } while (sts == -1 && ++count < 3); + pmflush(); + + return sts; +} +#else +static int +queryCertificateOK(const char *message) +{ + /* no way implemented to interact to query the user, so decline */ + return rejectUserCertificate(message); +} +#endif + +static void +reportFingerprint(SECItem *item) +{ + unsigned char fingerprint[SHA1_LENGTH] = { 0 }; + SECItem fitem; + char *fstring; + + PK11_HashBuf(SEC_OID_SHA1, fingerprint, item->data, item->len); + fitem.data = fingerprint; + fitem.len = SHA1_LENGTH; + fstring = CERT_Hexify(&fitem, 1); + pmprintf("SHA1 fingerprint is %s\n", fstring); + PORT_Free(fstring); +} + +static SECStatus +queryCertificateAuthority(PRFileDesc *sslsocket) +{ + int sts; + int secsts = SECFailure; + char *result; + CERTCertificate *servercert; + + result = SSL_RevealURL(sslsocket); + pmprintf("WARNING: " + "issuer of certificate received from host %s is not trusted.\n", + result); + PORT_Free(result); + + servercert = SSL_PeerCertificate(sslsocket); + if (servercert) { + reportFingerprint(&servercert->derCert); + sts = queryCertificateOK("Do you want to accept and save this certificate locally anyway"); + if (sts == 1) { + saveUserCertificate(servercert); + secsts = SECSuccess; + } + CERT_DestroyCertificate(servercert); + } else { + pmflush(); + } + return secsts; +} + +static SECStatus +queryCertificateDomain(PRFileDesc *sslsocket) +{ + int sts; + char *result; + SECItem secitem = { 0 }; + SECStatus secstatus = SECFailure; + PRArenaPool *arena = NULL; + CERTCertificate *servercert = NULL; + + /* + * Propagate a warning through to the client. Show the expected + * host, then list the DNS names from the server certificate. + */ + result = SSL_RevealURL(sslsocket); + pmprintf("WARNING: " +"The domain name %s does not match the DNS name(s) on the server certificate:\n", + result); + PORT_Free(result); + + servercert = SSL_PeerCertificate(sslsocket); + secstatus = CERT_FindCertExtension(servercert, + SEC_OID_X509_SUBJECT_ALT_NAME, &secitem); + if (secstatus != SECSuccess || !secitem.data) { + pmprintf("Unable to find alt name extension on the server certificate\n"); + } else if ((arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE)) == NULL) { + pmprintf("Out of memory while generating name list\n"); + SECITEM_FreeItem(&secitem, PR_FALSE); + } else { + CERTGeneralName *namelist, *n; + + namelist = n = CERT_DecodeAltNameExtension(arena, &secitem); + SECITEM_FreeItem(&secitem, PR_FALSE); + if (!namelist) { + pmprintf("Unable to decode alt name extension on server certificate\n"); + } else { + do { + if (n->type == certDNSName) + pmprintf(" %.*s\n", (int)n->name.other.len, n->name.other.data); + n = CERT_GetNextGeneralName(n); + } while (n != namelist); + } + } + if (arena) + PORT_FreeArena(arena, PR_FALSE); + if (servercert) + CERT_DestroyCertificate(servercert); + + sts = queryCertificateOK("Do you want to accept this certificate anyway"); + return (sts == 1) ? SECSuccess : SECFailure; +} + +static SECStatus +badCertificate(void *arg, PRFileDesc *sslsocket) +{ + (void)arg; + switch (PR_GetError()) { + case SSL_ERROR_BAD_CERT_DOMAIN: + return queryCertificateDomain(sslsocket); + case SEC_ERROR_UNKNOWN_ISSUER: + return queryCertificateAuthority(sslsocket); + default: + break; + } + return SECFailure; +} + +static int +__pmAuthLogCB(void *context, int priority, const char *message) +{ + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthLogCB enter ctx=%p pri=%d\n", __FILE__, context, priority); + + if (!message) + return SASL_BADPARAM; + switch (priority) { + case SASL_LOG_NONE: + return SASL_OK; + case SASL_LOG_ERR: + priority = LOG_ERR; + break; + case SASL_LOG_FAIL: + priority = LOG_ALERT; + break; + case SASL_LOG_WARN: + priority = LOG_WARNING; + break; + case SASL_LOG_NOTE: + priority = LOG_NOTICE; + break; + case SASL_LOG_DEBUG: + case SASL_LOG_TRACE: + case SASL_LOG_PASS: + if (pmDebug & DBG_TRACE_AUTH) + priority = LOG_DEBUG; + else + return SASL_OK; + break; + default: + priority = LOG_INFO; + break; + } + __pmNotifyErr(priority, "%s", message); + return SASL_OK; +} + +#if !defined(IS_MINGW) +static void echoOff(int fd) +{ + if (isatty(fd)) { + struct termios tio; + tcgetattr(fd, &tio); + tio.c_lflag &= ~ECHO; + tcsetattr(fd, TCSAFLUSH, &tio); + } +} + +static void echoOn(int fd) +{ + if (isatty(fd)) { + struct termios tio; + tcgetattr(fd, &tio); + tio.c_lflag |= ECHO; + tcsetattr(fd, TCSAFLUSH, &tio); + } +} +#define fgetsQuietly(buf,length,input) fgets(buf,length,input) +#define consoleName "/dev/tty" +#else +#define echoOn(fd) do { } while (0) +#define echoOff(fd) do { } while (0) +#define consoleName "CON:" +static char *fgetsQuietly(char *buf, int length, FILE *input) +{ + int c; + char *end = buf; + + if (!isatty(fileno(input))) + return fgets(buf, length, input); + do { + c = getch(); + if (c == '\b') { + if (end > buf) + end--; + } + else if (--length > 0) + *end++ = c; + if (!c || c == '\n' || c == '\r') + break; + } while (1); + + return buf; +} +#endif + +static char *fgetsPrompt(FILE *in, FILE *out, const char *prompt, int secret) +{ + size_t length; + int infd = fileno(in); + int isTTY = isatty(infd); + char *value, phrase[256]; + + if (isTTY) { + fprintf(out, "%s", prompt); + fflush(out); + if (secret) + echoOff(infd); + } + + memset(phrase, 0, sizeof(phrase)); + value = fgetsQuietly(phrase, sizeof(phrase)-1, in); + if (!value) + return strdup(""); + length = strlen(value) - 1; + while (length && (value[length] == '\n' || value[length] == '\r')) + value[length] = '\0'; + + if (isTTY && secret) { + fprintf(out, "\n"); + echoOn(infd); + } + + return strdup(value); +} + +/* + * SASL is calling us looking for the value for a specific attribute; + * we must respond as best we can: + * - if user specified it on the command line, its in the given hash + * - if we can interact, we can ask the user for it (taking care for + * sensitive info like passwords, not to echo back to the user) + * Also take care to handle non-console interactive modes, like the + * pmchart case. Further, we should consider a mode where we extract + * these values from a per-user config file too (ala. libvirt). + * + * Return value is a dynamically allocated string, caller must free. + */ + +static char * +__pmGetAttrConsole(const char *prompt, int secret) +{ + FILE *input, *output; + char *value, *console; + + /* + * Interactive mode: open terminal and discuss with user + * For graphical tools, we do not want to ever be here. + * For testing, we want to just error out of here ASAP. + */ + console = getenv("PCP_CONSOLE"); + if (console) { + if (strcmp(console, "none") == 0) + return NULL; + } else { + console = consoleName; + } + + input = fopen(console, "r"); + if (input == NULL) { + __pmNotifyErr(LOG_ERR, "opening input terminal for read\n"); + return NULL; + } + output = fopen(console, "w"); + if (output == NULL) { + __pmNotifyErr(LOG_ERR, "opening output terminal for write\n"); + fclose(input); + return NULL; + } + + value = fgetsPrompt(input, output, prompt, secret); + + fclose(input); + fclose(output); + + return value; +} + +static char * +__pmGetAttrValue(__pmAttrKey key, __pmHashCtl *attrs, const char *prompt) +{ + __pmHashNode *node; + char *value; + + if ((node = __pmHashSearch(key, attrs)) != NULL) + return (char *)node->data; + value = __pmGetAttrConsole(prompt, key == PCP_ATTR_PASSWORD); + if (value) /* must track all our own memory use in SASL */ + __pmHashAdd(key, value, attrs); + return value; +} + + +static int +__pmAuthRealmCB(void *context, int id, const char **realms, const char **result) +{ + __pmHashCtl *attrs = (__pmHashCtl *)context; + char *value = NULL; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthRealmCB enter ctx=%p id=%#x\n", __FILE__, context, id); + + if (id != SASL_CB_GETREALM) + return SASL_FAIL; + + value = __pmGetAttrValue(PCP_ATTR_REALM, attrs, "Realm: "); + *result = (const char *)value; + + if (pmDebug & DBG_TRACE_AUTH) { + fprintf(stderr, "%s:__pmAuthRealmCB ctx=%p, id=%#x, realms=(", __FILE__, context, id); + if (realms) { + if (*realms) + fprintf(stderr, "%s", *realms); + for (value = (char *) *(realms + 1); value && *value; value++) + fprintf(stderr, " %s", value); + } + fprintf(stderr, ") -> rslt=%s\n", *result ? *result : "(none)"); + } + return SASL_OK; +} + +static int +__pmAuthSimpleCB(void *context, int id, const char **result, unsigned *len) +{ + __pmHashCtl *attrs = (__pmHashCtl *)context; + char *value = NULL; + int sts; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthSimpleCB enter ctx=%p id=%#x\n", __FILE__, context, id); + + if (!result) + return SASL_BADPARAM; + + sts = SASL_OK; + switch (id) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + value = __pmGetAttrValue(PCP_ATTR_USERNAME, attrs, "Username: "); + break; + case SASL_CB_LANGUAGE: + break; + default: + sts = SASL_BADPARAM; + break; + } + + if (len) + *len = value ? strlen(value) : 0; + *result = value; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthSimpleCB ctx=%p id=%#x -> sts=%d rslt=%p len=%d\n", + __FILE__, context, id, sts, *result, len ? *len : -1); + return sts; +} + +static int +__pmAuthSecretCB(sasl_conn_t *saslconn, void *context, int id, sasl_secret_t **secret) +{ + __pmHashCtl *attrs = (__pmHashCtl *)context; + size_t length = 0; + char *password; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthSecretCB enter ctx=%p id=%#x\n", __FILE__, context, id); + + if (saslconn == NULL || secret == NULL || id != SASL_CB_PASS) + return SASL_BADPARAM; + + password = __pmGetAttrValue(PCP_ATTR_PASSWORD, attrs, "Password: "); + length = password ? strlen(password) : 0; + + *secret = (sasl_secret_t *) calloc(1, sizeof(sasl_secret_t) + length + 1); + if (!*secret) { + free(password); + return SASL_NOMEM; + } + + if (password) { + (*secret)->len = length; + strcpy((char *)(*secret)->data, password); + } + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthSecretCB ctx=%p id=%#x -> data=%s len=%u\n", + __FILE__, context, id, password, (unsigned)length); + free(password); + + return SASL_OK; +} + +static int +__pmAuthPromptCB(void *context, int id, const char *challenge, const char *prompt, + const char *defaultresult, const char **result, unsigned *length) +{ + char *value, message[512]; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthPromptCB enter ctx=%p id=%#x\n", __FILE__, context, id); + + if (id != SASL_CB_ECHOPROMPT && id != SASL_CB_NOECHOPROMPT) + return SASL_BADPARAM; + if (!prompt || !result || !length) + return SASL_BADPARAM; + if (defaultresult == NULL) + defaultresult = ""; + + if (!challenge) + snprintf(message, sizeof(message), "%s [%s]: ", prompt, defaultresult); + else + snprintf(message, sizeof(message), "%s [challenge: %s] [%s]: ", + prompt, challenge, defaultresult); + message[sizeof(message)-1] = '\0'; + + if (id == SASL_CB_ECHOPROMPT) { + value = __pmGetAttrConsole(message, 0); + if (value && value[0] != '\0') { + *result = value; + } else { + free(value); + *result = defaultresult; + } + } else { + if (fgets(message, sizeof(message), stdin) == NULL || message[0]) + *result = strdup(message); + else + *result = defaultresult; + } + if (!*result) + return SASL_NOMEM; + + *length = (unsigned) strlen(*result); + return SASL_OK; +} + +static int +__pmSecureClientInit(int flags) +{ + int sts; + + /* Ensure correct security lib initialisation order */ + __pmInitSecureSockets(); + + /* + * If secure sockets functionality available, iterate over the set of + * known locations for certificate databases and attempt to initialise + * one of them for our use. + */ + sts = 0; + if ((flags & PDU_FLAG_NO_NSS_INIT) == 0) { + sts = __pmInitCertificates(); + if (sts < 0) + __pmNotifyErr(LOG_WARNING, "__pmConnectPMCD: " + "certificate database exists, but failed initialization"); + } + return sts; +} + +static int +__pmSecureClientIPCFlags(int fd, int flags, const char *hostname, __pmHashCtl *attrs) +{ + __pmSecureSocket socket; + sasl_callback_t *cb; + SECStatus secsts; + int sts; + + if (__pmDataIPC(fd, &socket) < 0) + return -EOPNOTSUPP; + + if ((flags & PDU_FLAG_SECURE) != 0) { + sts = __pmSecureClientInit(flags); + if (sts < 0) + return sts; + sts = __pmSetupSecureSocket(fd, &socket); + if (sts < 0) + return __pmSecureSocketsError(PR_GetError()); + if ((socket.sslFd = SSL_ImportFD(NULL, socket.nsprFd)) == NULL) { + __pmNotifyErr(LOG_ERR, "SecureClientIPCFlags: importing socket into SSL"); + return PM_ERR_IPC; + } + socket.nsprFd = socket.sslFd; + + secsts = SSL_OptionSet(socket.sslFd, SSL_SECURITY, PR_TRUE); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + secsts = SSL_OptionSet(socket.sslFd, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + secsts = SSL_SetURL(socket.sslFd, hostname); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + secsts = SSL_BadCertHook(socket.sslFd, + (SSLBadCertHandler)badCertificate, NULL); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + } + + if ((flags & PDU_FLAG_COMPRESS) != 0) { + /* + * The current implementation of compression requires an SSL/TLS + * connection. + */ + if (socket.sslFd == NULL) + return -EOPNOTSUPP; + secsts = SSL_OptionSet(socket.sslFd, SSL_ENABLE_DEFLATE, PR_TRUE); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + } + + if ((flags & PDU_FLAG_AUTH) != 0) { + __pmInitAuthClients(); + socket.saslCB = calloc(LIMIT_CLIENT_CALLBACKS, sizeof(sasl_callback_t)); + if ((cb = socket.saslCB) == NULL) + return -ENOMEM; + cb->id = SASL_CB_USER; + cb->proc = (sasl_callback_func)&__pmAuthSimpleCB; + cb->context = (void *)attrs; + cb++; + cb->id = SASL_CB_AUTHNAME; + cb->proc = (sasl_callback_func)&__pmAuthSimpleCB; + cb->context = (void *)attrs; + cb++; + cb->id = SASL_CB_LANGUAGE; + cb->proc = (sasl_callback_func)&__pmAuthSimpleCB; + cb++; + cb->id = SASL_CB_GETREALM; + cb->proc = (sasl_callback_func)&__pmAuthRealmCB; + cb->context = (void *)attrs; + cb++; + cb->id = SASL_CB_PASS; + cb->proc = (sasl_callback_func)&__pmAuthSecretCB; + cb->context = (void *)attrs; + cb++; + cb->id = SASL_CB_ECHOPROMPT; + cb->proc = (sasl_callback_func)&__pmAuthPromptCB; + cb++; + cb->id = SASL_CB_NOECHOPROMPT; + cb->proc = (sasl_callback_func)&__pmAuthPromptCB; + cb++; + cb->id = SASL_CB_LIST_END; + cb++; + assert(cb - socket.saslCB <= LIMIT_CLIENT_CALLBACKS); + + sts = sasl_client_new(SECURE_SERVER_SASL_SERVICE, + hostname, + NULL, NULL, /*iplocal,ipremote*/ + socket.saslCB, + 0, &socket.saslConn); + if (sts != SASL_OK && sts != SASL_CONTINUE) + return __pmSecureSocketsError(sts); + } + + /* save changes back into the IPC table (updates client sslFd) */ + return __pmSetDataIPC(fd, (void *)&socket); +} + +static int +__pmSecureClientNegotiation(int fd, int *strength) +{ + PRIntervalTime timer; + PRFileDesc *sslsocket; + SECStatus secsts; + int enabled, keysize; + int msec; + + sslsocket = (PRFileDesc *)__pmGetSecureSocket(fd); + if (!sslsocket) + return -EINVAL; + + secsts = SSL_ResetHandshake(sslsocket, PR_FALSE /*client*/); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + msec = __pmConvertTimeout(TIMEOUT_DEFAULT); + timer = PR_MillisecondsToInterval(msec); + secsts = SSL_ForceHandshakeWithTimeout(sslsocket, timer); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + secsts = SSL_SecurityStatus(sslsocket, &enabled, NULL, &keysize, NULL, NULL, NULL); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + *strength = (enabled > 0) ? keysize : DEFAULT_SECURITY_STRENGTH; + return 0; +} + +static void +__pmInitAuthPaths(void) +{ + char *path; + + if ((path = getenv("PCP_SASL2_PLUGIN_PATH")) != NULL) + sasl_set_path(SASL_PATH_TYPE_PLUGIN, path); + if ((path = getenv("PCP_SASL2_CONFIG_PATH")) != NULL) + sasl_set_path(SASL_PATH_TYPE_CONFIG, path); +} + +static sasl_callback_t common_callbacks[] = { \ + { .id = SASL_CB_LOG, .proc = (sasl_callback_func)&__pmAuthLogCB }, + { .id = SASL_CB_LIST_END }}; + +int +__pmInitAuthClients(void) +{ + __pmInitAuthPaths(); + if (sasl_client_init(common_callbacks) != SASL_OK) + return -EINVAL; + return 0; +} + +int +__pmInitAuthServer(void) +{ + __pmInitAuthPaths(); + if (sasl_server_init(common_callbacks, pmProgname) != SASL_OK) { + __pmNotifyErr(LOG_ERR, "Failed to start authenticating server"); + return -EINVAL; + } + return 0; +} + +static int +__pmAuthClientSetProperties(sasl_conn_t *saslconn, int ssf) +{ + int sts; + sasl_security_properties_t props; + + /* set external security strength factor */ + if ((sts = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf)) != SASL_OK) + return __pmSecureSocketsError(sts); + + /* set general security properties */ + memset(&props, 0, sizeof(props)); + props.maxbufsize = LIMIT_AUTH_PDU; + props.max_ssf = UINT_MAX; + if ((sts = sasl_setprop(saslconn, SASL_SEC_PROPS, &props)) != SASL_OK) + return __pmSecureSocketsError(sts); + + return 0; +} + +static int +__pmAuthClientNegotiation(int fd, int ssf, const char *hostname, __pmHashCtl *attrs) +{ + int sts, zero, saslsts = SASL_FAIL; + int pinned, length, method_length; + char *payload, buffer[LIMIT_AUTH_PDU]; + const char *method = NULL; + sasl_conn_t *saslconn; + __pmHashNode *node; + __pmPDU *pb; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthClientNegotiation(fd=%d, ssf=%d, host=%s)\n", + __FILE__, fd, ssf, hostname); + + if ((saslconn = (sasl_conn_t *)__pmGetUserAuthData(fd)) == NULL) + return -EINVAL; + + /* setup all the security properties for this connection */ + if ((sts = __pmAuthClientSetProperties(saslconn, ssf)) < 0) + return sts; + + /* lookup users preferred connection method, if specified */ + if ((node = __pmHashSearch(PCP_ATTR_METHOD, attrs)) != NULL) + method = (const char *)node->data; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthClientNegotiation requesting \"%s\" method\n", + __FILE__, method ? method : "default"); + + /* get security mechanism list */ + sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb); + if (sts == PDU_AUTH) { + sts = __pmDecodeAuth(pb, &zero, &payload, &length); + if (sts >= 0) { + strncpy(buffer, payload, length); + buffer[length] = '\0'; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthClientNegotiation got methods: " + "\"%s\" (%d)\n", __FILE__, buffer, length); + /* + * buffer now contains the list of server mechanisms - + * override using users preference (if any) and proceed. + */ + if (method) { + strncpy(buffer, method, sizeof(buffer)); + buffer[sizeof(buffer) - 1] = '\0'; + length = strlen(buffer); + } + + payload = NULL; + saslsts = sasl_client_start(saslconn, buffer, NULL, + (const char **)&payload, + (unsigned int *)&length, &method); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + sts = __pmSecureSocketsError(saslsts); + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_client_start failed: %d (%s)\n", + saslsts, pmErrStr(sts)); + } + } + } else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + } else if (sts != PM_ERR_TIMEOUT) { + sts = PM_ERR_IPC; + } + + if (pinned) + __pmUnpinPDUBuf(pb); + if (sts < 0) + return sts; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_client_start chose \"%s\" method, saslsts=%s\n", + method, saslsts == SASL_CONTINUE ? "continue" : "ok"); + + /* tell server we've made a decision and are ready to move on */ + strncpy(buffer, method, sizeof(buffer)); + buffer[sizeof(buffer) - 1] = '\0'; + method_length = strlen(buffer); + if (payload) { + if (LIMIT_AUTH_PDU - method_length - 1 < length) + return -E2BIG; + memcpy(buffer + method_length + 1, payload, length); + length += method_length + 1; + } else { + length = method_length + 1; + } + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_client_start sending (%d bytes) \"%s\"\n", + length, buffer); + + if ((sts = __pmSendAuth(fd, FROM_ANON, 0, buffer, length)) < 0) + return sts; + + while (saslsts == SASL_CONTINUE) { + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmAuthClientNegotiation awaiting server reply\n", __FILE__); + + sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb); + if (sts == PDU_AUTH) { + sts = __pmDecodeAuth(pb, &zero, &payload, &length); + if (sts >= 0) { + saslsts = sasl_client_step(saslconn, payload, length, NULL, + (const char **)&buffer, + (unsigned int *)&length); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + sts = __pmSecureSocketsError(saslsts); + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_client_step failed: %d (%s)\n", + saslsts, pmErrStr(sts)); + break; + } + if (pmDebug & DBG_TRACE_AUTH) { + fprintf(stderr, "%s:__pmAuthClientNegotiation" + " step recv (%d bytes)", __FILE__, length); + } + } + } else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + } else if (sts != PM_ERR_TIMEOUT) { + sts = PM_ERR_IPC; + } + + if (pinned) + __pmUnpinPDUBuf(pb); + if (sts >= 0) + sts = __pmSendAuth(fd, FROM_ANON, 0, length ? buffer : "", length); + if (sts < 0) + break; + } + + if (pmDebug & DBG_TRACE_AUTH) { + if (sts < 0) + fprintf(stderr, "%s:__pmAuthClientNegotiation loop failed\n", __FILE__); + else { + saslsts = sasl_getprop(saslconn, SASL_USERNAME, (const void **)&payload); + fprintf(stderr, "%s:__pmAuthClientNegotiation success, username=%s\n", + __FILE__, saslsts != SASL_OK ? "?" : payload); + } + } + + return sts; +} + +int +__pmSecureClientHandshake(int fd, int flags, const char *hostname, __pmHashCtl *attrs) +{ + int sts, ssf = DEFAULT_SECURITY_STRENGTH; + + /* + * If the server uses the secure-ack protocol, then expect an error + * pdu here containing the server's secure status. If the status is zero, + * then all is ok, otherwise, return the status to the caller. + */ + if (flags & PDU_FLAG_SECURE_ACK) { + __pmPDU *rpdu; + int pinpdu; + int serverSts; + pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &rpdu); + if (sts != PDU_ERROR) { + if (pinpdu) + __pmUnpinPDUBuf(&rpdu); + return -PM_ERR_IPC; + } + sts = __pmDecodeError (rpdu, &serverSts); + if (pinpdu) + __pmUnpinPDUBuf(&rpdu); + if (sts < 0) + return sts; + if (serverSts < 0) + return serverSts; + } + + if (flags & PDU_FLAG_CREDS_REQD) { + if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL) + return 0; + flags |= PDU_FLAG_AUTH; /* force the use of SASL authentication */ + } + if ((sts = __pmSecureClientIPCFlags(fd, flags, hostname, attrs)) < 0) + return sts; + if (((flags & PDU_FLAG_SECURE) != 0) && + ((sts = __pmSecureClientNegotiation(fd, &ssf)) < 0)) + return sts; + if (((flags & PDU_FLAG_AUTH) != 0) && + ((sts = __pmAuthClientNegotiation(fd, ssf, hostname, attrs)) < 0)) + return sts; + return 0; +} + +void * +__pmGetSecureSocket(int fd) +{ + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) < 0) + return NULL; + return (void *)socket.sslFd; +} + +void * +__pmGetUserAuthData(int fd) +{ + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) < 0) + return NULL; + return (void *)socket.saslConn; +} + +static void +sendSecureAck(int fd, int flags, int sts) { + /* + * At this point we've attempted some required initialization for secure + * sockets. If the client wants a secure-ack then send an error pdu + * containing our status. The client will then know whether or not to + * proceed with the secure handshake. + */ + if (flags & PDU_FLAG_SECURE_ACK) + __pmSendError (fd, FROM_ANON, sts); +} + +int +__pmSecureServerIPCFlags(int fd, int flags) +{ + __pmSecureSocket socket; + SECStatus secsts; + int saslsts; + int sts; + + if (__pmDataIPC(fd, &socket) < 0) + return -EOPNOTSUPP; + + if ((flags & PDU_FLAG_SECURE) != 0) { + sts = __pmSecureServerInit(); + if (sts < 0) { + sendSecureAck(fd, flags, sts); + return sts; + } + sts = __pmSetupSecureSocket(fd, &socket); + if (sts < 0) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + if ((socket.sslFd = SSL_ImportFD(NULL, socket.nsprFd)) == NULL) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + socket.nsprFd = socket.sslFd; + + secsts = SSL_OptionSet(socket.sslFd, SSL_NO_LOCKS, PR_TRUE); + if (secsts != SECSuccess) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + secsts = SSL_OptionSet(socket.sslFd, SSL_SECURITY, PR_TRUE); + if (secsts != SECSuccess) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + secsts = SSL_OptionSet(socket.sslFd, SSL_HANDSHAKE_AS_SERVER, PR_TRUE); + if (secsts != SECSuccess) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + secsts = SSL_OptionSet(socket.sslFd, SSL_REQUEST_CERTIFICATE, PR_FALSE); + if (secsts != SECSuccess) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + secsts = SSL_OptionSet(socket.sslFd, SSL_REQUIRE_CERTIFICATE, PR_FALSE); + if (secsts != SECSuccess) { + sts = __pmSecureSocketsError(PR_GetError()); + sendSecureAck(fd, flags, sts); + return sts; + } + sendSecureAck(fd, flags, sts); + } + + if ((flags & PDU_FLAG_COMPRESS) != 0) { + /* + * The current implementation of compression requires an SSL/TLS + * connection. + */ + if (socket.sslFd == NULL) + return -EOPNOTSUPP; + secsts = SSL_OptionSet(socket.sslFd, SSL_ENABLE_DEFLATE, PR_TRUE); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + } + + if ((flags & PDU_FLAG_AUTH) != 0) { + sts = __pmInitAuthServer(); + if (sts < 0) + return sts; + saslsts = sasl_server_new(SECURE_SERVER_SASL_SERVICE, + NULL, NULL, /*localdomain,userdomain*/ + NULL, NULL, NULL, /*iplocal,ipremote,callbacks*/ + 0, &socket.saslConn); + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "%s:__pmSecureServerIPCFlags SASL server: %d\n", __FILE__, saslsts); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) + return __pmSecureSocketsError(saslsts); + } + + /* save changes back into the IPC table */ + return __pmSetDataIPC(fd, (void *)&socket); +} + +static int +sockOptValue(const void *option_value, __pmSockLen option_len) +{ + switch(option_len) { + case sizeof(int): + return *(int *)option_value; + default: + __pmNotifyErr(LOG_ERR, "sockOptValue: invalid option length: %d\n", option_len); + break; + } + return 0; +} + +int +__pmSetSockOpt(int fd, int level, int option_name, const void *option_value, + __pmSockLen option_len) +{ + /* Map the request to the NSPR equivalent, if possible. */ + PRSocketOptionData option_data; + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { + switch(level) { + case SOL_SOCKET: + switch(option_name) { + /* + * These options are not related. They are just both options for which + * NSPR has no direct mapping. + */ +#ifdef IS_MINGW + case SO_EXCLUSIVEADDRUSE: /* Only exists on MINGW */ +#endif + { + /* + * There is no direct mapping of this option in NSPR. + * The best we can do is to use the native handle and + * call setsockopt on that handle. + */ + fd = PR_FileDesc2NativeHandle(socket.nsprFd); + return setsockopt(fd, level, option_name, option_value, option_len); + } + case SO_KEEPALIVE: + option_data.option = PR_SockOpt_Keepalive; + option_data.value.keep_alive = sockOptValue(option_value, option_len); + break; + case SO_LINGER: { + struct linger *linger = (struct linger *)option_value; + option_data.option = PR_SockOpt_Linger; + option_data.value.linger.polarity = linger->l_onoff; + option_data.value.linger.linger = linger->l_linger; + break; + } + case SO_REUSEADDR: + option_data.option = PR_SockOpt_Reuseaddr; + option_data.value.reuse_addr = sockOptValue(option_value, option_len); + break; + default: + __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for SOL_SOCKET: %d\n", + __FILE__, option_name); + return -1; + } + break; + case IPPROTO_TCP: + if (option_name == TCP_NODELAY) { + option_data.option = PR_SockOpt_NoDelay; + option_data.value.no_delay = sockOptValue(option_value, option_len); + break; + } + __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for IPPROTO_TCP: %d\n", + __FILE__, option_name); + return -1; + case IPPROTO_IPV6: + if (option_name == IPV6_V6ONLY) { + /* + * There is no direct mapping of this option in NSPR. + * The best we can do is to use the native handle and + * call setsockopt on that handle. + */ + fd = PR_FileDesc2NativeHandle(socket.nsprFd); + return setsockopt(fd, level, option_name, option_value, option_len); + } + __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for IPPROTO_IPV6: %d\n", + __FILE__, option_name); + return -1; + default: + __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented level: %d\n", __FILE__, level); + return -1; + } + + return (PR_SetSocketOption(socket.nsprFd, &option_data) + == PR_SUCCESS) ? 0 : -1; + } + + /* We have a native socket. */ + return setsockopt(fd, level, option_name, option_value, option_len); +} + +int +__pmGetSockOpt(int fd, int level, int option_name, void *option_value, + __pmSockLen *option_len) +{ + __pmSecureSocket socket; + + /* Map the request to the NSPR equivalent, if possible. */ + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { + switch (level) { + case SOL_SOCKET: + switch(option_name) { + +#if defined(HAVE_STRUCT_UCRED) + case SO_PEERCRED: +#endif + case SO_ERROR: { + /* + * There is no direct mapping of this option in NSPR. + * Best we can do is call getsockopt on the native fd. + */ + fd = PR_FileDesc2NativeHandle(socket.nsprFd); + return getsockopt(fd, level, option_name, option_value, option_len); + } + default: + __pmNotifyErr(LOG_ERR, + "%s:__pmGetSockOpt: unimplemented option_name for SOL_SOCKET: %d\n", + __FILE__, option_name); + return -1; + } + break; + + default: + __pmNotifyErr(LOG_ERR, "%s:__pmGetSockOpt: unimplemented level: %d\n", __FILE__, level); + break; + } + return -1; + } + + /* We have a native socket. */ + return getsockopt(fd, level, option_name, option_value, option_len); +} + +ssize_t +__pmWrite(int fd, const void *buffer, size_t length) +{ + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { + ssize_t size = PR_Write(socket.nsprFd, buffer, length); + if (size < 0) + __pmSecureSocketsError(PR_GetError()); + return size; + } + return write(fd, buffer, length); +} + +ssize_t +__pmRead(int fd, void *buffer, size_t length) +{ + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { + ssize_t size = PR_Read(socket.nsprFd, buffer, length); + if (size < 0) + __pmSecureSocketsError(PR_GetError()); + return size; + } + return read(fd, buffer, length); +} + +ssize_t +__pmSend(int fd, const void *buffer, size_t length, int flags) +{ + __pmSecureSocket socket; + + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { + ssize_t size = PR_Write(socket.nsprFd, buffer, length); + if (size < 0) + __pmSecureSocketsError(PR_GetError()); + return size; + } + return send(fd, buffer, length, flags); +} + +ssize_t +__pmRecv(int fd, void *buffer, size_t length, int flags) +{ + __pmSecureSocket socket; + ssize_t size; + + if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "%s:__pmRecv[secure](", __FILE__); + } +#endif + size = PR_Read(socket.nsprFd, buffer, length); + if (size < 0) + __pmSecureSocketsError(PR_GetError()); + } + else { +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "%s:__pmRecv(", __FILE__); + } +#endif + size = recv(fd, buffer, length, flags); + } +#ifdef PCP_DEBUG + if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) { + fprintf(stderr, "%d, ..., %d, " PRINTF_P_PFX "%x) -> %d\n", + fd, (int)length, flags, (int)size); + } +#endif + return size; +} + +/* + * In certain situations, we need to allow access to previously-read + * data on a socket. This is because, for example, the SSL protocol + * buffering may have already consumed data that we are now expecting + * (in this case, its buffered internally and a socket read will give + * up that data). + * + * PR_Poll does not seem to play well here and so we need to use the + * native select-based mechanism to block and/or query the state of + * pending data. + */ +int +__pmSocketReady(int fd, struct timeval *timeout) +{ + __pmSecureSocket socket; + __pmFdSet onefd; + + if (__pmDataIPC(fd, &socket) == 0 && socket.sslFd) + if (SSL_DataPending(socket.sslFd)) + return 1; /* proceed without blocking */ + + FD_ZERO(&onefd); + FD_SET(fd, &onefd); + return select(fd+1, &onefd, NULL, NULL, timeout); +} diff --git a/src/libpcp/src/secureserver.c b/src/libpcp/src/secureserver.c new file mode 100644 index 0000000..a8c0629 --- /dev/null +++ b/src/libpcp/src/secureserver.c @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * + * Server side security features - via Network Security Services (NSS) and + * the Simple Authentication and Security Layer (SASL). + * + * 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. + */ + +#include "pmapi.h" +#include "impl.h" +#define SOCKET_INTERNAL +#include "internal.h" +#include <keyhi.h> +#include <secder.h> +#include <pk11pub.h> +#include <sys/stat.h> + +#define MAX_NSSDB_PASSWORD_LENGTH 256 + +static struct { + /* NSS certificate management */ + CERTCertificate *certificate; + SECKEYPrivateKey *private_key; + const char *password_file; + SSLKEAType certificate_KEA; + char database_path[MAXPATHLEN]; + + /* status flags (bitfields) */ + unsigned int initialized : 1; + unsigned int init_failed : 1; + unsigned int certificate_verified : 1; /* NSS */ + unsigned int ssl_session_cache_setup : 1; /* NSS */ +} secure_server; + +int +__pmSecureServerSetFeature(__pmServerFeature wanted) +{ + (void)wanted; + return 0; /* nothing dynamically enabled at this stage */ +} + +int +__pmSecureServerClearFeature(__pmServerFeature clear) +{ + (void)clear; + return 0; /* nothing dynamically disabled at this stage */ +} + +int +__pmSecureServerHasFeature(__pmServerFeature query) +{ + int sts = 0; + + switch (query) { + case PM_SERVER_FEATURE_SECURE: + return ! secure_server.init_failed; + case PM_SERVER_FEATURE_COMPRESS: + case PM_SERVER_FEATURE_AUTH: + sts = 1; + break; + default: + break; + } + return sts; +} + +static int +secure_file_contents(const char *filename, char **passwd, size_t *length) +{ + struct stat stat; + size_t size = *length; + char *pass = NULL; + FILE *file = NULL; + int sts; + + if ((file = fopen(filename, "r")) == NULL) + goto fail; + if (fstat(fileno(file), &stat) < 0) + goto fail; + if (stat.st_size > size) { + setoserror(E2BIG); + goto fail; + } + if ((pass = (char *)PORT_Alloc(stat.st_size)) == NULL) { + setoserror(ENOMEM); + goto fail; + } + sts = fread(pass, 1, stat.st_size, file); + if (sts < 1) { + setoserror(EINVAL); + goto fail; + } + while (sts > 0 && (pass[sts-1] == '\r' || pass[sts-1] == '\n')) + pass[--sts] = '\0'; + *passwd = pass; + *length = sts; + fclose(file); + return 0; + +fail: + sts = -oserror(); + if (file) + fclose(file); + if (pass) + PORT_Free(pass); + return sts; +} + +static char * +certificate_database_password(PK11SlotInfo *info, PRBool retry, void *arg) +{ + size_t length = MAX_NSSDB_PASSWORD_LENGTH; + char *password = NULL; + char passfile[MAXPATHLEN]; + int sts; + + (void)arg; + (void)info; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + passfile[0] = '\0'; + if (secure_server.password_file) + strncpy(passfile, secure_server.password_file, MAXPATHLEN-1); + passfile[MAXPATHLEN-1] = '\0'; + PM_UNLOCK(__pmLock_libpcp); + + if (passfile[0] == '\0') { + __pmNotifyErr(LOG_ERR, "Password sought but no password file given"); + return NULL; + } + if (retry) { + __pmNotifyErr(LOG_ERR, "Retry attempted during password extraction"); + return NULL; /* no soup^Wretries for you */ + } + + sts = secure_file_contents(passfile, &password, &length); + if (sts < 0) { + __pmNotifyErr(LOG_ERR, "Cannot read password file \"%s\": %s", + passfile, pmErrStr(sts)); + return NULL; + } + return password; +} + +static int +__pmCertificateTimestamp(SECItem *vtime, char *buffer, size_t size) +{ + PRExplodedTime exploded; + SECStatus secsts; + int64 itime; + + switch (vtime->type) { + case siUTCTime: + secsts = DER_UTCTimeToTime(&itime, vtime); + break; + case siGeneralizedTime: + secsts = DER_GeneralizedTimeToTime(&itime, vtime); + break; + default: + return -EINVAL; + } + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + /* Convert to local time */ + PR_ExplodeTime(itime, PR_GMTParameters, &exploded); + if (!PR_FormatTime(buffer, size, "%a %b %d %H:%M:%S %Y", &exploded)) + return __pmSecureSocketsError(PR_GetError()); + return 0; +} + +static void +__pmDumpCertificate(FILE *fp, const char *nickname, CERTCertificate *cert) +{ + CERTValidity *valid = &cert->validity; + char tbuf[256]; + + fprintf(fp, "Certificate: %s", nickname); + if (__pmCertificateTimestamp(&valid->notBefore, tbuf, sizeof(tbuf)) == 0) + fprintf(fp, " Not Valid Before: %s UTC", tbuf); + if (__pmCertificateTimestamp(&valid->notAfter, tbuf, sizeof(tbuf)) == 0) + fprintf(fp, " Not Valid After: %s UTC", tbuf); +} + +static int +__pmValidCertificate(CERTCertDBHandle *db, CERTCertificate *cert, PRTime stamp) +{ + SECCertificateUsage usage = certificateUsageSSLServer; + SECStatus secsts = CERT_VerifyCertificate(db, cert, PR_TRUE, usage, + stamp, NULL, NULL, &usage); + return (secsts == SECSuccess); +} + +static char * +serverdb(char *path, size_t size, char *db_method) +{ + int sep = __pmPathSeparator(); + char *nss_method = getenv("PCP_SECURE_DB_METHOD"); + + if (nss_method == NULL) + nss_method = db_method; + + /* + * Fill in a buffer with the server NSS database specification. + * Return a pointer to the filesystem path component - without + * the <method>:-prefix - for other routines to work with. + */ + snprintf(path, size, "%s" "%c" "etc" "%c" "pki" "%c" "nssdb", + nss_method, sep, sep, sep); + return path + strlen(nss_method); +} + +int +__pmSecureServerSetup(const char *db, const char *passwd) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + /* Configure optional (cmdline) password file in case DB locked */ + secure_server.password_file = passwd; + + /* + * Configure location of the NSS database with a sane default. + * For servers, we default to the shared (sql) system-wide database. + * If command line db specified, pass it directly through - allowing + * any old database format, at the users discretion. + */ + if (db) { + /* shortened-buffer-size (-2) guarantees null-termination */ + strncpy(secure_server.database_path, db, MAXPATHLEN-2); + } + + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +int +__pmSecureServerInit(void) +{ + const char *nickname = SECURE_SERVER_CERTIFICATE; + SECStatus secsts; + int pathSpecified; + int sts = 0; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + /* Only attempt this once. */ + if (secure_server.initialized) + goto done; + secure_server.initialized = 1; + + if (PR_Initialized() != PR_TRUE) + PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + + /* Configure optional (cmdline) password file in case DB locked */ + PK11_SetPasswordFunc(certificate_database_password); + + /* + * Configure location of the NSS database with a sane default. + * For servers, we default to the shared (sql) system-wide database. + * If command line db specified, pass it directly through - allowing + * any old database format, at the users discretion. + */ + if (!secure_server.database_path[0]) { + const char *path; + pathSpecified = 0; + path = serverdb(secure_server.database_path, MAXPATHLEN, "sql:"); + + /* this is the default case on some platforms, so no log spam */ + if (access(path, R_OK|X_OK) < 0) { + if (pmDebug & DBG_TRACE_CONTEXT) + __pmNotifyErr(LOG_INFO, + "Cannot access system security database: %s", + secure_server.database_path); + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } + } + else + pathSpecified = 1; + + secsts = NSS_Init(secure_server.database_path); + if (secsts != SECSuccess && !pathSpecified) { + /* fallback, older versions of NSS do not support sql: */ + serverdb(secure_server.database_path, MAXPATHLEN, ""); + secsts = NSS_Init(secure_server.database_path); + } + + if (secsts != SECSuccess) { + __pmNotifyErr(LOG_ERR, "Cannot setup certificate DB (%s): %s", + secure_server.database_path, + pmErrStr(__pmSecureSocketsError(PR_GetError()))); + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } + + /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */ + do { + const PRUint16 *cipher; + for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher) + SSL_CipherPolicySet(*cipher, SSL_ALLOWED); + } while (0); + + /* Configure SSL session cache for multi-process server, using defaults */ + secsts = SSL_ConfigMPServerSIDCache(1, 0, 0, NULL); + if (secsts != SECSuccess) { + __pmNotifyErr(LOG_ERR, "Unable to configure SSL session ID cache: %s", + pmErrStr(__pmSecureSocketsError(PR_GetError()))); + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } else { + secure_server.ssl_session_cache_setup = 1; + } + + /* + * Iterate over any/all PCP Collector nickname certificates, + * seeking one valid certificate. No-such-nickname is not an + * error (not configured by admin at all) but anything else is. + */ + CERTCertList *certlist; + CERTCertDBHandle *nssdb = CERT_GetDefaultCertDB(); + CERTCertificate *dbcert = PK11_FindCertFromNickname(nickname, NULL); + + if (dbcert) { + PRTime now = PR_Now(); + SECItem *name = &dbcert->derSubject; + CERTCertListNode *node; + + certlist = CERT_CreateSubjectCertList(NULL, nssdb, name, now, PR_FALSE); + if (certlist) { + for (node = CERT_LIST_HEAD(certlist); + !CERT_LIST_END(node, certlist); + node = CERT_LIST_NEXT (node)) { + if (pmDebug & DBG_TRACE_CONTEXT) + __pmDumpCertificate(stderr, nickname, node->cert); + if (!__pmValidCertificate(nssdb, node->cert, now)) + continue; + secure_server.certificate_verified = 1; + break; + } + CERT_DestroyCertList(certlist); + } + + if (secure_server.certificate_verified) { + secure_server.certificate_KEA = NSS_FindCertKEAType(dbcert); + secure_server.private_key = PK11_FindKeyByAnyCert(dbcert, NULL); + if (!secure_server.private_key) { + __pmNotifyErr(LOG_ERR, "Unable to extract %s private key", + nickname); + CERT_DestroyCertificate(dbcert); + secure_server.certificate_verified = 0; + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } + } else { + __pmNotifyErr(LOG_ERR, "Unable to find a valid %s", nickname); + CERT_DestroyCertificate(dbcert); + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } + } + + if (! secure_server.certificate_verified) { + if (pmDebug & DBG_TRACE_CONTEXT) { + __pmNotifyErr(LOG_INFO, "No valid %s in security database: %s", + nickname, secure_server.database_path); + } + sts = -EOPNOTSUPP; /* not fatal - just no secure connections */ + secure_server.init_failed = 1; + goto done; + } + + secure_server.certificate = dbcert; + secure_server.init_failed = 0; + sts = 0; + +done: + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +void +__pmSecureServerShutdown(void) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (secure_server.certificate) { + CERT_DestroyCertificate(secure_server.certificate); + secure_server.certificate = NULL; + } + if (secure_server.private_key) { + SECKEY_DestroyPrivateKey(secure_server.private_key); + secure_server.private_key = NULL; + } + if (secure_server.ssl_session_cache_setup) { + SSL_ShutdownServerSessionIDCache(); + secure_server.ssl_session_cache_setup = 0; + } + if (secure_server.initialized) { + NSS_Shutdown(); + secure_server.initialized = 0; + } + PM_UNLOCK(__pmLock_libpcp); +} + +static int +__pmSecureServerNegotiation(int fd, int *strength) +{ + PRIntervalTime timer; + PRFileDesc *sslsocket; + SECStatus secsts; + int enabled, keysize; + int msec; + + sslsocket = (PRFileDesc *)__pmGetSecureSocket(fd); + if (!sslsocket) + return PM_ERR_IPC; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + secsts = SSL_ConfigSecureServer(sslsocket, + secure_server.certificate, + secure_server.private_key, + secure_server.certificate_KEA); + PM_UNLOCK(__pmLock_libpcp); + + if (secsts != SECSuccess) { + __pmNotifyErr(LOG_ERR, "Unable to configure secure server: %s", + pmErrStr(__pmSecureSocketsError(PR_GetError()))); + return PM_ERR_IPC; + } + + secsts = SSL_ResetHandshake(sslsocket, PR_TRUE /*server*/); + if (secsts != SECSuccess) { + __pmNotifyErr(LOG_ERR, "Unable to reset secure handshake: %s", + pmErrStr(__pmSecureSocketsError(PR_GetError()))); + return PM_ERR_IPC; + } + + /* Server initiates handshake now to get early visibility of errors */ + msec = __pmConvertTimeout(TIMEOUT_DEFAULT); + timer = PR_MillisecondsToInterval(msec); + secsts = SSL_ForceHandshakeWithTimeout(sslsocket, timer); + if (secsts != SECSuccess) { + __pmNotifyErr(LOG_ERR, "Unable to force secure handshake: %s", + pmErrStr(__pmSecureSocketsError(PR_GetError()))); + return PM_ERR_IPC; + } + + secsts = SSL_SecurityStatus(sslsocket, &enabled, NULL, &keysize, NULL, NULL, NULL); + if (secsts != SECSuccess) + return __pmSecureSocketsError(PR_GetError()); + + *strength = (enabled > 0) ? keysize : DEFAULT_SECURITY_STRENGTH; + return 0; +} + +static int +__pmSetUserGroupAttributes(const char *username, __pmHashCtl *attrs) +{ + char name[32]; + char *namep; + uid_t uid; + gid_t gid; + + if (__pmGetUserIdentity(username, &uid, &gid, PM_RECOV_ERR) == 0) { + snprintf(name, sizeof(name), "%u", uid); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_USERID, namep, attrs); + else + return -ENOMEM; + + snprintf(name, sizeof(name), "%u", gid); + name[sizeof(name)-1] = '\0'; + if ((namep = strdup(name)) != NULL) + __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs); + else + return -ENOMEM; + return 0; + } + __pmNotifyErr(LOG_ERR, "Authenticated user %s not found\n", username); + return -ESRCH; +} + +static int +__pmAuthServerSetAttributes(sasl_conn_t *conn, __pmHashCtl *attrs) +{ + const void *property = NULL; + char *username; + int sts; + + sts = sasl_getprop(conn, SASL_USERNAME, &property); + username = (char *)property; + if (sts == SASL_OK && username) { + __pmNotifyErr(LOG_INFO, + "Successful authentication for user \"%s\"\n", + username); + if ((username = strdup(username)) == NULL) { + __pmNoMem("__pmAuthServerSetAttributes", + strlen(username), PM_RECOV_ERR); + return -ENOMEM; + } + } else { + __pmNotifyErr(LOG_ERR, + "Authentication complete, but no username\n"); + return -ESRCH; + } + + if ((sts = __pmHashAdd(PCP_ATTR_USERNAME, username, attrs)) < 0) + return sts; + return __pmSetUserGroupAttributes(username, attrs); +} + +static int +__pmAuthServerSetProperties(sasl_conn_t *conn, int ssf) +{ + int saslsts; + sasl_security_properties_t props; + + /* set external security strength factor */ + saslsts = sasl_setprop(conn, SASL_SSF_EXTERNAL, &ssf); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + __pmNotifyErr(LOG_ERR, "SASL setting external SSF to %d: %s", + ssf, sasl_errstring(saslsts, NULL, NULL)); + return __pmSecureSocketsError(saslsts); + } + + /* set general security properties */ + memset(&props, 0, sizeof(props)); + props.maxbufsize = LIMIT_AUTH_PDU; + props.max_ssf = UINT_MAX; + saslsts = sasl_setprop(conn, SASL_SEC_PROPS, &props); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + __pmNotifyErr(LOG_ERR, "SASL setting security properties: %s", + sasl_errstring(saslsts, NULL, NULL)); + return __pmSecureSocketsError(saslsts); + } + + return 0; +} + +static int +__pmAuthServerNegotiation(int fd, int ssf, __pmHashCtl *attrs) +{ + int sts, saslsts; + int pinned, length, count; + char *payload, *offset; + sasl_conn_t *sasl_conn; + __pmPDU *pb; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation(fd=%d, ssf=%d)\n", + fd, ssf); + + if ((sasl_conn = (sasl_conn_t *)__pmGetUserAuthData(fd)) == NULL) + return -EINVAL; + + /* setup all the security properties for this connection */ + if ((sts = __pmAuthServerSetProperties(sasl_conn, ssf)) < 0) + return sts; + + saslsts = sasl_listmech(sasl_conn, + NULL, NULL, " ", NULL, + (const char **)&payload, + (unsigned int *)&length, + &count); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + __pmNotifyErr(LOG_ERR, "Generating client mechanism list: %s", + sasl_errstring(saslsts, NULL, NULL)); + return __pmSecureSocketsError(saslsts); + } + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation - sending mechanism list " + "(%d items, %d bytes): \"%s\"\n", count, length, payload); + + if ((sts = __pmSendAuth(fd, FROM_ANON, 0, payload, length)) < 0) + return sts; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation - wait for mechanism\n"); + + sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb); + if (sts == PDU_AUTH) { + sts = __pmDecodeAuth(pb, &count, &payload, &length); + if (sts >= 0) { + for (count = 0; count < length; count++) { + if (payload[count] == '\0') + break; + } + if (count < length) { /* found an initial response */ + length = length - count - 1; + offset = payload + count + 1; + } else { + length = 0; + offset = NULL; + } + + saslsts = sasl_server_start(sasl_conn, payload, + offset, length, + (const char **)&payload, + (unsigned int *)&length); + if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) { + sts = __pmSecureSocketsError(saslsts); + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_server_start failed: %d (%s)\n", + saslsts, pmErrStr(sts)); + } else { + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "sasl_server_start success: sts=%s\n", + saslsts == SASL_CONTINUE ? "continue" : "ok"); + } + } + } else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + } else if (sts != PM_ERR_TIMEOUT) { + sts = PM_ERR_IPC; + } + + if (pinned) + __pmUnpinPDUBuf(pb); + if (sts < 0) + return sts; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation method negotiated\n"); + + while (saslsts == SASL_CONTINUE) { + if (!payload) { + __pmNotifyErr(LOG_ERR, "No SASL data to send"); + sts = -EINVAL; + break; + } + if ((sts = __pmSendAuth(fd, FROM_ANON, 0, payload, length)) < 0) + break; + + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation awaiting response\n"); + + sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb); + if (sts == PDU_AUTH) { + sts = __pmDecodeAuth(pb, &count, &payload, &length); + if (sts >= 0) { + sts = saslsts = sasl_server_step(sasl_conn, payload, length, + (const char **)&payload, + (unsigned int *)&length); + if (sts != SASL_OK && sts != SASL_CONTINUE) { + sts = __pmSecureSocketsError(sts); + break; + } + if (pmDebug & DBG_TRACE_AUTH) { + fprintf(stderr, "__pmAuthServerNegotiation" + " step recv (%d bytes)\n", length); + } + } + } else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + } else if (sts != PM_ERR_TIMEOUT) { + sts = PM_ERR_IPC; + } + + if (pinned) + __pmUnpinPDUBuf(pb); + if (sts < 0) + break; + } + + if (sts < 0) { + if (pmDebug & DBG_TRACE_AUTH) + fprintf(stderr, "__pmAuthServerNegotiation loop failed: %d\n", sts); + return sts; + } + + return __pmAuthServerSetAttributes(sasl_conn, attrs); +} + +int +__pmSecureServerHandshake(int fd, int flags, __pmHashCtl *attrs) +{ + int sts, ssf = DEFAULT_SECURITY_STRENGTH; + + /* protect from unsupported requests from future/oddball clients */ + if ((flags & ~(PDU_FLAG_SECURE | PDU_FLAG_SECURE_ACK | PDU_FLAG_COMPRESS + | PDU_FLAG_AUTH | PDU_FLAG_CREDS_REQD)) != 0) + return PM_ERR_IPC; + + if (flags & PDU_FLAG_CREDS_REQD) { + if (__pmHashSearch(PCP_ATTR_USERID, attrs) != NULL) + return 0; /* unix domain socket */ + else + flags |= PDU_FLAG_AUTH; /* force authentication */ + } + + if ((sts = __pmSecureServerIPCFlags(fd, flags)) < 0) + return sts; + if (((flags & PDU_FLAG_SECURE) != 0) && + ((sts = __pmSecureServerNegotiation(fd, &ssf)) < 0)) + return sts; + if (((flags & PDU_FLAG_AUTH) != 0) && + ((sts = __pmAuthServerNegotiation(fd, ssf, attrs)) < 0)) + return sts; + return 0; +} diff --git a/src/libpcp/src/sortinst.c b/src/libpcp/src/sortinst.c new file mode 100644 index 0000000..3d557f4 --- /dev/null +++ b/src/libpcp/src/sortinst.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include <stdlib.h> +#include "pmapi.h" + +static int +comp(const void *a, const void *b) +{ + pmValue *ap = (pmValue *)a; + pmValue *bp = (pmValue *)b; + + return ap->inst - bp->inst; +} + +void +pmSortInstances(pmResult *rp) +{ + int i; + + for (i = 0; i < rp->numpmid; i++) { + if (rp->vset[i]->numval > 1) { + qsort(rp->vset[i]->vlist, rp->vset[i]->numval, sizeof(pmValue), comp); + } + } +} diff --git a/src/libpcp/src/spec.c b/src/libpcp/src/spec.c new file mode 100644 index 0000000..8a7f299 --- /dev/null +++ b/src/libpcp/src/spec.c @@ -0,0 +1,1077 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2007 Aconex. All Rights Reserved. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* + * Parse uniform metric and host specification syntax + */ + +#include <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "internal.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +static void * +parseAlloc(const char *func, size_t need) +{ + void *tmp; + + if ((tmp = malloc(need)) == NULL) + __pmNoMem(func, need, PM_FATAL_ERR); + return tmp; +} + +static void +parseError(const char *func, const char *spec, const char *point, char *msg, char **rslt) +{ + int need; + const char *p; + char *q; + + if (rslt == NULL) + return; + + need = 2 * (int)strlen(spec) + 1 + 6 + (int)strlen(msg) + 2; + *rslt = q = (char *)parseAlloc(func, need); + for (p = spec; *p != '\0'; p++) + *q++ = *p; + *q++ = '\n'; + for (p = spec; p != point; p++) + *q++ = isgraph((int)*p) ? ' ' : *p; + sprintf(q, "^ -- %s\n", msg); /* safe */ +} + +static void * +metricAlloc(size_t need) +{ + return parseAlloc("pmParseMetricSpec", need); +} + +static void +metricError(const char *spec, const char *point, char *msg, char **rslt) +{ + parseError("pmParseMetricSpec", spec, point, msg, rslt); +} + +int /* 0 -> ok, PM_ERR_GENERIC -> error */ +pmParseMetricSpec( + const char *spec, /* parse this string */ + int isarch, /* default source: 0 -> host, 1 -> archive */ + char *source, /* name of default host or archive */ + pmMetricSpec **rslt, /* result allocated and returned here */ + char **errmsg) /* error message */ +{ + pmMetricSpec *msp = NULL; + const char *scan; + const char *mark; + const char *h_start = NULL; /* host name */ + const char *h_end = NULL; + const char *a_start = NULL; /* archive name */ + const char *a_end = NULL; + const char *m_start = NULL; /* metric name */ + const char *m_end = NULL; + const char *i_start = NULL; /* instance names */ + const char *i_end = NULL; + char *i_str = NULL; /* temporary instance names */ + char *i_scan; + int ninst; /* number of instance names */ + char *push; + const char *pull; + int length; + int i; + int inquote = 0; /* true if within quotes */ + + scan = spec; + while (isspace((int)*scan)) + scan++; + + /* + * Options here are ... + * [host:]metric[[instance list]] + * special case for PM_CONTEXT_LOCAL [@:]metric[[instance list]] + * [archive/]metric[[instance list]] + * + * Find end of metric name first ([ or end of string) then scan + * backwards for first ':' or '/' + */ + mark = index(scan, (int)'['); + if (mark == NULL) mark = &scan[strlen(scan)-1]; + while (mark >= scan) { + if (*mark == ':') { + h_start = scan; + h_end = mark-1; + while (h_end >= scan && isspace((int)*h_end)) h_end--; + if (h_end < h_start) { + metricError(spec, h_start, "host name expected", errmsg); + return PM_ERR_GENERIC; + } + h_end++; + scan = mark+1; + break; + } + else if (*mark == '/') { + a_start = scan; + a_end = mark-1; + while (a_end >= scan && isspace((int)*a_end)) a_end--; + if (a_end < a_start) { + metricError(spec, a_start, "archive name expected", errmsg); + return PM_ERR_GENERIC; + } + a_end++; + scan = mark+1; + break; + } + mark--; + } + + while (isspace((int)*scan)) + scan++; + mark = scan; + + /* delimit metric name */ + m_start = scan; + while (! isspace((int)*scan) && *scan != '\0' && *scan != '[') { + if (*scan == ']' || *scan == ',') { + metricError(spec, scan, "unexpected character in metric name", errmsg); + return PM_ERR_GENERIC; + } + if (*scan == '\\' && *(scan+1) != '\0') + scan++; + scan++; + } + m_end = scan; + if (m_start == m_end) { + metricError(spec, m_start, "performance metric name expected", errmsg); + return PM_ERR_GENERIC; + } + + while (isspace((int)*scan)) + scan++; + + /* delimit instance names */ + if (*scan == '[') { + scan++; + while (isspace((int)*scan)) + scan++; + i_start = scan; + for ( ; ; ) { + if (*scan == '\0') { + if (inquote) + metricError(spec, scan, "closing \" and ] expected", errmsg); + else + metricError(spec, scan, "closing ] expected", errmsg); + return PM_ERR_GENERIC; + } + if (*scan == '\\' && *(scan+1) != '\0') + scan++; + else if (*scan == '"') + inquote = 1 - inquote; + else if (!inquote && *scan == ']') + break; + scan++; + } + i_end = scan; + scan++; + } + + /* check for rubbish at end of string */ + while (isspace((int)*scan)) + scan++; + if (*scan != '\0') { + metricError(spec, scan, "unexpected extra characters", errmsg); + return PM_ERR_GENERIC; + } + + /* count instance names and make temporary copy */ + ninst = 0; + if (i_start != NULL) { + i_str = (char *) metricAlloc(i_end - i_start + 1); + + /* count and copy instance names */ + scan = i_start; + i_scan = i_str; + while (scan < i_end) { + + /* copy single instance name */ + ninst++; + if (*scan == '"') { + scan++; + for (;;) { + if (scan >= i_end) { + metricError(spec, scan, "closing \" expected (pmParseMetricSpec botch?)", errmsg); + if (i_str) + free(i_str); + return PM_ERR_GENERIC; + } + if (*scan == '\\') + scan++; + else if (*scan == '"') + break; + *i_scan++ = *scan++; + } + scan++; + } + else { + while (! isspace((int)*scan) && *scan != ',' && scan < i_end) { + if (*scan == '\\') + scan++; + *i_scan++ = *scan++; + } + } + *i_scan++ = '\0'; + + /* skip delimiters */ + while ((isspace((int)*scan) || *scan == ',') && scan < i_end) + scan++; + } + i_start = i_str; + i_end = i_scan; + } + + /* single memory allocation for result structure */ + length = (int)(sizeof(pmMetricSpec) + + ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0) + + ((h_start) ? h_end - h_start + 1 : 0) + + ((a_start) ? a_end - a_start + 1 : 0) + + ((m_start) ? m_end - m_start + 1 : 0) + + ((i_start) ? i_end - i_start + 1 : 0)); + msp = (pmMetricSpec *)metricAlloc(length); + + /* strings follow pmMetricSpec proper */ + push = ((char *) msp) + + sizeof(pmMetricSpec) + + ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0); + + /* copy metric name */ + msp->metric = push; + pull = m_start; + while (pull < m_end) { + if (*pull == '\\' && (pull+1) < m_end) + pull++; + *push++ = *pull++; + } + *push++ = '\0'; + + /* copy host name */ + if (h_start != NULL) { + if (h_end - h_start == 1 && *h_start == '@') { + /* PM_CONTEXT_LOCAL special case */ + msp->isarch = 2; + } + else { + /* PM_CONTEXT_HOST */ + msp->isarch = 0; + } + msp->source = push; + pull = h_start; + while (pull < h_end) { + if (*pull == '\\' && (pull+1) < h_end) + pull++; + *push++ = *pull++; + } + *push++ = '\0'; + } + + /* copy archive name */ + else if (a_start != NULL) { + msp->isarch = 1; + msp->source = push; + pull = a_start; + while (pull < a_end) { + if (*pull == '\\' && (pull+1) < a_end) + pull++; + *push++ = *pull++; + } + *push++ = '\0'; + } + + /* take default host or archive */ + else { + msp->isarch = isarch; + msp->source = source; + } + + /* instance names */ + msp->ninst = ninst; + pull = i_start; + for (i = 0; i < ninst; i++) { + msp->inst[i] = push; + do + *push++ = *pull; + while (*pull++ != '\0'); + } + + if (i_str) + free(i_str); + *rslt = msp; + return 0; +} + +void +pmFreeMetricSpec(pmMetricSpec *spec) +{ + free(spec); +} + + +static void +hostError(const char *spec, const char *point, char *msg, char **rslt) +{ + parseError("pmParseHostSpec", spec, point, msg, rslt); +} + +static char * +hostStrndup(const char *name, int namelen) +{ + char *s = malloc(namelen + 1); + strncpy(s, name, namelen); + s[namelen] = '\0'; + return s; +} + +static pmHostSpec * +hostAdd(pmHostSpec *specp, int *count, const char *name, int namelen) +{ + int n = *count; + char *host; + + host = hostStrndup(name, namelen); + if (!host || (specp = realloc(specp, sizeof(pmHostSpec) * (n+1))) == NULL) { + if (host != NULL) + free(host); + *count = 0; + return NULL; + } + specp[n].name = host; + specp[n].ports = NULL; + specp[n].nports = 0; + + *count = n + 1; + return specp; +} + +int +__pmAddHostPorts(pmHostSpec *specp, int *ports, int nports) +{ + int *portlist; + + if ((portlist = malloc(sizeof(int) * (specp->nports + nports))) == NULL) + return -ENOMEM; + if (specp->nports > 0) { + memcpy(portlist, specp->ports, sizeof(int) * specp->nports); + free(specp->ports); + } + memcpy(&portlist[specp->nports], ports, sizeof(int) * nports); + specp->ports = portlist; + specp->nports = specp->nports + nports; + return 0; +} + +void +__pmDropHostPort(pmHostSpec *specp) +{ + specp->nports--; + memmove(&specp->ports[0], &specp->ports[1], specp->nports*sizeof(int)); +} + +/* + * Parse a host specification, with optional ports and proxy host(s). + * Examples: + * pcp -h app1.aconex.com:44321,4321@firewall.aconex.com:44322 + * pcp -h app1.aconex.com:44321@firewall.aconex.com:44322 + * pcp -h app1.aconex.com:44321@firewall.aconex.com + * pcp -h app1.aconex.com@firewall.aconex.com + * pcp -h app1.aconex.com:44321 + * pcp -h 192.168.122.1:44321 + * pcp -h [fe80::5eff:35ff:fe07:55ca]:44321,4321@[fe80::5eff:35ff:fe07:55cc]:44322 + * pcp -h [fe80::5eff:35ff:fe07:55ca]:44321 + * + * Basic algorithm: + * look for first colon, @ or null; preceding text is hostname + * if colon, look for comma, @ or null, preceding text is port + * while comma, look for comma, @ or null, preceding text is next port + * if @, start following host specification at the following character, + * by returning to the start and repeating the above for the next chunk. + * Note: + * IPv6 addresses contain colons and, so, must be separated from the + * rest of the spec somehow. A common notation among ipv6-enabled + * applications is to enclose the address within brackets, as in + * [fe80::5eff:35ff:fe07:55ca]:44321. We keep it simple, however, + * and allow any host spec to be enclosed in brackets. + * Note: + * Currently only two hosts are useful, but ability to handle more than + * one optional proxy host is there (i.e. proxy ->proxy ->... ->pmcd), + * in case someone implements the pmproxy->pmproxy protocol extension. + */ +static int /* 0 -> ok, PM_ERR_GENERIC -> error message is set */ +parseHostSpec( + const char *spec, + char **position, /* parse this string, return end char */ + pmHostSpec **rslt, /* result allocated and returned here */ + int *count, + char **errmsg) /* error message */ +{ + pmHostSpec *hsp = NULL; + const char *s, *start, *next; + int nhosts = 0, sts = 0; + + for (s = start = *position; s != NULL; s++) { + /* Allow the host spec to be enclosed in brackets. */ + if (s == start && *s == '[') { + for (s++; *s != ']' && *s != '\0'; s++) + ; + if (*s != ']') { + hostError(spec, s, "missing closing ']' for host spec", errmsg); + sts = PM_ERR_GENERIC; + goto fail; + } + next = s + 1; /* past the trailing ']' */ + if (*next != ':' && *next != '@' && *next != '\0' && *next != '/' && *next != '?') { + hostError(spec, next, "extra characters after host spec", errmsg); + sts = PM_ERR_GENERIC; + goto fail; + } + start++; /* past the initial '[' */ + } + else + next = s; + if (*next == ':' || *next == '@' || *next == '\0' || *next == '/' || *next == '?') { + if (s == *position) + break; + else if (s == start) + continue; + hsp = hostAdd(hsp, &nhosts, start, s - start); + if (hsp == NULL) { + sts = -ENOMEM; + goto fail; + } + s = next; + if (*s == ':') { + for (++s, start = s; s != NULL; s++) { + if (*s == ',' || *s == '@' || *s == '\0' || *s == '/' || *s == '?') { + if (s - start < 1) { + hostError(spec, s, "missing port", errmsg); + sts = PM_ERR_GENERIC; + goto fail; + } + int port = atoi(start); + sts = __pmAddHostPorts(&hsp[nhosts-1], &port, 1); + if (sts < 0) + goto fail; + start = s + 1; + if (*s == '@' || *s == '\0' || *s == '/' || *s == '?') + break; + continue; + } + if (isdigit((int)*s)) + continue; + hostError(spec, s, "non-numeric port", errmsg); + sts = PM_ERR_GENERIC; + goto fail; + } + } + if (*s == '@') { + start = s+1; + continue; + } + break; + } + } + *position = (char *)s; + *count = nhosts; + *rslt = hsp; + return 0; + +fail: + __pmFreeHostSpec(hsp, nhosts); + *rslt = NULL; + *count = 0; + return sts; +} + +/* + * Parse a socket path. + * Accept anything up to, but not including the first ':', or the end of the spec. + * We use ':' as the delimeter even though a '?' could be the start of the attibutes because + * '?' is a valid character in a socket path. It is also consistent with things like $PATH. + */ +static int /* 0 -> ok, PM_ERR_GENERIC -> error message is set */ +parseSocketPath( + const char *spec, + char **position, /* parse this string, return end char */ + pmHostSpec **rslt) /* result allocated and returned here */ +{ + pmHostSpec *hsp = NULL; + const char *s, *start, *path; + char absolute_path[MAXPATHLEN]; + size_t len; + int nhosts = 0; + + /* Scan to the end of the string or to the first ':'. */ + for (s = start = *position; s != NULL; s++) { + if (*s == '\0') + break; + if (*s == ':') { + ++s; + break; + } + } + + /* If the path is empty, then provide the default. */ + if (s == start) { + path = __pmPMCDLocalSocketDefault(); + len = strlen(path); + } + else { + path = start; + len = s - start; + } + + /* + * Make sure that the path is absolute. parseProtocolSpec() removes the + * (optional) "//" from "local://some/path". + */ + if (*path != __pmPathSeparator()) { + len = snprintf (absolute_path, sizeof(absolute_path), "%c%s", + __pmPathSeparator(), path); + path = absolute_path; + } + + /* Add the path as the only member of the host list. */ + hsp = hostAdd(hsp, &nhosts, path, len); + if (hsp == NULL) { + __pmFreeHostSpec(hsp, nhosts); + *rslt = NULL; + return -ENOMEM; + } + + *position = (char *)s; + *rslt = hsp; + return 0; +} + +int +__pmParseHostSpec( + const char *spec, /* parse this string */ + pmHostSpec **rslt, /* result allocated and returned here */ + int *count, /* number of host specs returned here */ + char **errmsg) /* error message */ +{ + char *s = (char *)spec; + int sts; + + if ((sts = parseHostSpec(spec, &s, rslt, count, errmsg)) < 0) + return sts; + + if (*s == '\0') + return 0; + + hostError(spec, s, "unexpected terminal character", errmsg); + __pmFreeHostSpec(*rslt, *count); + *rslt = NULL; + *count = 0; + return PM_ERR_GENERIC; +} + +static int +unparseHostSpec(pmHostSpec *hostp, int count, char *string, size_t size, int prefix) +{ + int off = 0, len = size; /* offset in string and space remaining */ + int i, j, sts; + + for (i = 0; i < count; i++) { + if (i > 0) { + if ((sts = snprintf(string + off, len, "@")) >= size) { + off = -E2BIG; + goto done; + } + len -= sts; off += sts; + } + + if (prefix && hostp[i].nports == PM_HOST_SPEC_NPORTS_LOCAL) { + if ((sts = snprintf(string + off, len, "local:/%s", hostp[i].name + 1)) >= size) { + off = -E2BIG; + goto done; + } + } + else if (prefix && hostp[i].nports == PM_HOST_SPEC_NPORTS_UNIX) { + if ((sts = snprintf(string + off, len, "unix:/%s", hostp[i].name + 1)) >= size) { + off = -E2BIG; + goto done; + } + } + else { + if ((sts = snprintf(string + off, len, "%s", hostp[i].name)) >= size) { + off = -E2BIG; + goto done; + } + } + len -= sts; off += sts; + + for (j = 0; j < hostp[i].nports; j++) { + if ((sts = snprintf(string + off, len, + "%c%u", (j == 0) ? ':' : ',', + hostp[i].ports[j])) >= size) { + off = -E2BIG; + goto done; + } + len -= sts; off += sts; + } + } + +done: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "__pmUnparseHostSpec([name=%s ports=%p nport=%d], count=%d, ...) -> ", hostp->name, hostp->ports, hostp->nports, count); + if (off < 0) { + char errmsg[PM_MAXERRMSGLEN]; + pmErrStr_r(off, errmsg, sizeof(errmsg)); + fprintf(stderr, "%s\n", errmsg); + } + else + fprintf(stderr, "%d \"%s\"\n", off, string); + } +#endif + return off; +} + +int +__pmUnparseHostSpec(pmHostSpec *hostp, int count, char *string, size_t size) +{ + return unparseHostSpec(hostp, count, string, size, 1); +} + +void +__pmFreeHostSpec(pmHostSpec *specp, int count) +{ + int i; + + for (i = 0; i < count; i++) { + free(specp[i].name); + specp[i].name = NULL; + if (specp[i].nports > 0) + free(specp[i].ports); + specp[i].ports = NULL; + specp[i].nports = 0; + } + if (specp && count) + free(specp); +} + +static __pmHashWalkState +attrHashNodeDel(const __pmHashNode *tp, void *cp) +{ + (void)cp; + if (tp->data) + free(tp->data); + return PM_HASH_WALK_DELETE_NEXT; +} + +void +__pmFreeAttrsSpec(__pmHashCtl *attrs) +{ + __pmHashWalkCB(attrHashNodeDel, NULL, attrs); +} + +void +__pmFreeHostAttrsSpec(pmHostSpec *hosts, int count, __pmHashCtl *attrs) +{ + __pmFreeHostSpec(hosts, count); + __pmFreeAttrsSpec(attrs); +} + +#define PCP_PROTOCOL_NAME "pcp" +#define PCP_PROTOCOL_PREFIX PCP_PROTOCOL_NAME ":" +#define PCP_PROTOCOL_SIZE (sizeof(PCP_PROTOCOL_NAME)-1) +#define PCP_PROTOCOL_PREFIXSZ (sizeof(PCP_PROTOCOL_PREFIX)-1) +#define PCPS_PROTOCOL_NAME "pcps" +#define PCPS_PROTOCOL_PREFIX PCPS_PROTOCOL_NAME ":" +#define PCPS_PROTOCOL_SIZE (sizeof(PCPS_PROTOCOL_NAME)-1) +#define PCPS_PROTOCOL_PREFIXSZ (sizeof(PCPS_PROTOCOL_PREFIX)-1) +#define LOCAL_PROTOCOL_NAME "local" +#define LOCAL_PROTOCOL_PREFIX LOCAL_PROTOCOL_NAME ":" +#define LOCAL_PROTOCOL_SIZE (sizeof(LOCAL_PROTOCOL_NAME)-1) +#define LOCAL_PROTOCOL_PREFIXSZ (sizeof(LOCAL_PROTOCOL_PREFIX)-1) +#define UNIX_PROTOCOL_NAME "unix" +#define UNIX_PROTOCOL_PREFIX UNIX_PROTOCOL_NAME ":" +#define UNIX_PROTOCOL_SIZE (sizeof(UNIX_PROTOCOL_NAME)-1) +#define UNIX_PROTOCOL_PREFIXSZ (sizeof(UNIX_PROTOCOL_PREFIX)-1) + +static int +parseProtocolSpec( + const char *spec, /* the original, complete string to parse */ + char **position, + int *attribute, + char **value, + char **errmsg) +{ + char *protocol = NULL; + char *s = *position; + + /* optionally extract protocol specifier */ + if (strncmp(s, PCP_PROTOCOL_PREFIX, PCP_PROTOCOL_PREFIXSZ) == 0) { + protocol = PCP_PROTOCOL_NAME; + s += PCP_PROTOCOL_PREFIXSZ; + *attribute = PCP_ATTR_PROTOCOL; + } else if (strncmp(s, PCPS_PROTOCOL_PREFIX, PCPS_PROTOCOL_PREFIXSZ) == 0) { + protocol = PCPS_PROTOCOL_NAME; + s += PCPS_PROTOCOL_PREFIXSZ; + *attribute = PCP_ATTR_PROTOCOL; + } else if (strncmp(s, LOCAL_PROTOCOL_PREFIX, LOCAL_PROTOCOL_PREFIXSZ) == 0) { + protocol = LOCAL_PROTOCOL_NAME; + s += LOCAL_PROTOCOL_PREFIXSZ; + *attribute = PCP_ATTR_LOCAL; + } else if (strncmp(s, UNIX_PROTOCOL_PREFIX, UNIX_PROTOCOL_PREFIXSZ) == 0) { + protocol = UNIX_PROTOCOL_NAME; + s += UNIX_PROTOCOL_PREFIXSZ; + *attribute = PCP_ATTR_UNIXSOCK; + } + + /* optionally skip over slash-delimiters */ + if (protocol) { + while (*s == '/') + s++; + if ((*value = strdup(protocol)) == NULL) + return -ENOMEM; + } else { + *value = NULL; + *attribute = PCP_ATTR_NONE; + } + + *position = s; + return 0; +} + +__pmAttrKey +__pmLookupAttrKey(const char *attribute, size_t size) +{ + if (size == sizeof("compress") && + strncmp(attribute, "compress", size) == 0) + return PCP_ATTR_COMPRESS; + if ((size == sizeof("userauth") && + strncmp(attribute, "userauth", size) == 0) || + (size == sizeof("authorise") && + (strncmp(attribute, "authorise", size) == 0 || + strncmp(attribute, "authorize", size) == 0))) + return PCP_ATTR_USERAUTH; + if ((size == sizeof("user") && + strncmp(attribute, "user", size) == 0) || + (size == sizeof("username") && + strncmp(attribute, "username", size) == 0)) + return PCP_ATTR_USERNAME; + if (size == sizeof("realm") && + strncmp(attribute, "realm", size) == 0) + return PCP_ATTR_REALM; + if ((size == sizeof("authmeth") && + strncmp(attribute, "authmeth", size) == 0) || + (size == sizeof("method") && + strncmp(attribute, "method", size) == 0)) + return PCP_ATTR_METHOD; + if ((size == sizeof("pass") && + strncmp(attribute, "pass", size) == 0) || + (size == sizeof("password") && + strncmp(attribute, "password", size) == 0)) + return PCP_ATTR_PASSWORD; + if ((size == sizeof("unix") && + strncmp(attribute, "unix", size) == 0) || + (size == sizeof("unixsock") && + strncmp(attribute, "unixsock", size) == 0)) + return PCP_ATTR_UNIXSOCK; + if ((size == sizeof("local") && + strncmp(attribute, "local", size) == 0)) + return PCP_ATTR_LOCAL; + if ((size == sizeof("uid") && + strncmp(attribute, "uid", size) == 0) || + (size == sizeof("userid") && + strncmp(attribute, "userid", size) == 0)) + return PCP_ATTR_USERID; + if ((size == sizeof("gid") && + strncmp(attribute, "gid", size) == 0) || + (size == sizeof("groupid") && + strncmp(attribute, "groupid", size) == 0)) + return PCP_ATTR_GROUPID; + if ((size == sizeof("pid") && + strncmp(attribute, "pid", size) == 0) || + (size == sizeof("processid") && + strncmp(attribute, "processid", size) == 0)) + return PCP_ATTR_PROCESSID; + if (size == sizeof("secure") && + strncmp(attribute, "secure", size) == 0) + return PCP_ATTR_SECURE; + return PCP_ATTR_NONE; +} + +/* + * Parse the attributes component of a PCP connection string. + * Optionally, an initial attribute:value pair can be passed + * in as well to add to the parsed set. + */ +static int +parseAttributeSpec( + const char *spec, /* the original, complete string to parse */ + char **position, /* parse from here onward and update at end */ + int attribute, + char *value, + __pmHashCtl *attributes, + char **errmsg) +{ + char *s, *start, *v = NULL; + char buffer[32]; /* must be large enough to hold largest attr name */ + int buflen, attr, len, sts; + + if (attribute != PCP_ATTR_NONE) + if ((sts = __pmHashAdd(attribute, (void *)value, attributes)) < 0) + return sts; + + for (s = start = *position; s != NULL; s++) { + /* parse: foo=bar&moo&goo=blah ... go! */ + if (*s == '\0' || *s == '/' || *s == '&') { + if ((*s == '\0' || *s == '/') && s == start) + break; + len = v ? (v - start - 1) : (s - start); + buflen = (len < sizeof(buffer)-1) ? len : sizeof(buffer)-1; + strncpy(buffer, start, buflen); + buffer[buflen] = '\0'; + attr = __pmLookupAttrKey(buffer, buflen+1); + if (attr != PCP_ATTR_NONE) { + char *val = NULL; + + if (v && (val = strndup(v, s - v)) == NULL) { + sts = -ENOMEM; + goto fail; + } + if ((sts = __pmHashAdd(attr, (void *)val, attributes)) < 0) { + free(val); + goto fail; + } + } + v = NULL; + if (*s == '\0' || *s == '/') + break; + start = s + 1; /* start of attribute name */ + continue; + } + if (*s == '=') { + v = s + 1; /* start of attribute value */ + } + } + + *position = s; + return 0; + +fail: + if (attribute != PCP_ATTR_NONE) /* avoid double free in caller */ + __pmHashDel(attribute, (void *)value, attributes); + __pmFreeAttrsSpec(attributes); + return sts; +} + +/* + * Finally, bring it all together to handle parsing full connection URLs: + * + * pcp://oss.sgi.com:45892?user=otto&pass=blotto&compress=true + * pcps://oss.sgi.com@proxy.org:45893?user=jimbo&pass=jones&compress=true + * local://path/to/socket:?user=jimbo&pass=jones + * unix://path/to/socket + */ +int +__pmParseHostAttrsSpec( + const char *spec, /* the original, complete string to parse */ + pmHostSpec **host, /* hosts result allocated and returned here */ + int *count, + __pmHashCtl *attributes, + char **errmsg) /* error message */ +{ + char *value = NULL, *s = (char *)spec; + int sts, attr; + + *count = 0; /* ensure this initialised for fail: code */ + + /* parse optional protocol section */ + if ((sts = parseProtocolSpec(spec, &s, &attr, &value, errmsg)) < 0) + return sts; + + if (attr == PCP_ATTR_LOCAL || attr == PCP_ATTR_UNIXSOCK) { + /* We are looking for a socket path. */ + if ((sts = parseSocketPath(spec, &s, host)) < 0) + goto fail; + *count = 1; + host[0]->nports = (attr == PCP_ATTR_LOCAL) ? + PM_HOST_SPEC_NPORTS_LOCAL : PM_HOST_SPEC_NPORTS_UNIX; + } + else { + /* We are looking for a host spec. */ + if ((sts = parseHostSpec(spec, &s, host, count, errmsg)) < 0) + goto fail; + } + + /* skip over an attributes delimiter */ + if (*s == '?') { + s++; /* optionally skip over the question mark */ + } else if (*s != '\0' && *s != '/') { + hostError(spec, s, "unexpected terminal character", errmsg); + sts = PM_ERR_GENERIC; + goto fail; + } + + /* parse optional attributes section */ + if ((sts = parseAttributeSpec(spec, &s, attr, value, attributes, errmsg)) < 0) + goto fail; + + return 0; + +fail: + if (value) + free(value); + if (*count) + __pmFreeHostSpec(*host, *count); + *count = 0; + *host = NULL; + return sts; +} + +static int +unparseAttribute(__pmHashNode *node, char *string, size_t size) +{ + return __pmAttrStr_r(node->key, node->data, string, size); +} + +int +__pmAttrKeyStr_r(__pmAttrKey key, char *string, size_t size) +{ + switch (key) { + case PCP_ATTR_PROTOCOL: + return snprintf(string, size, "protocol"); + case PCP_ATTR_COMPRESS: + return snprintf(string, size, "compress"); + case PCP_ATTR_USERAUTH: + return snprintf(string, size, "userauth"); + case PCP_ATTR_USERNAME: + return snprintf(string, size, "username"); + case PCP_ATTR_AUTHNAME: + return snprintf(string, size, "authname"); + case PCP_ATTR_PASSWORD: + return snprintf(string, size, "password"); + case PCP_ATTR_METHOD: + return snprintf(string, size, "method"); + case PCP_ATTR_REALM: + return snprintf(string, size, "realm"); + case PCP_ATTR_SECURE: + return snprintf(string, size, "secure"); + case PCP_ATTR_UNIXSOCK: + return snprintf(string, size, "unixsock"); + case PCP_ATTR_LOCAL: + return snprintf(string, size, "local"); + case PCP_ATTR_USERID: + return snprintf(string, size, "userid"); + case PCP_ATTR_GROUPID: + return snprintf(string, size, "groupid"); + case PCP_ATTR_PROCESSID: + return snprintf(string, size, "processid"); + case PCP_ATTR_NONE: + default: + break; + } + return 0; +} + +int +__pmAttrStr_r(__pmAttrKey key, const char *data, char *string, size_t size) +{ + char name[16]; /* must be sufficient to hold any key name (above) */ + int sts; + + if ((sts = __pmAttrKeyStr_r(key, name, sizeof(name))) <= 0) + return sts; + + switch (key) { + case PCP_ATTR_PROTOCOL: + case PCP_ATTR_USERNAME: + case PCP_ATTR_PASSWORD: + case PCP_ATTR_METHOD: + case PCP_ATTR_REALM: + case PCP_ATTR_SECURE: + case PCP_ATTR_USERID: + case PCP_ATTR_GROUPID: + case PCP_ATTR_PROCESSID: + return snprintf(string, size, "%s=%s", name, data ? data : ""); + + case PCP_ATTR_UNIXSOCK: + case PCP_ATTR_LOCAL: + case PCP_ATTR_COMPRESS: + case PCP_ATTR_USERAUTH: + return snprintf(string, size, "%s", name); + + case PCP_ATTR_NONE: + default: + break; + } + return 0; +} + +int +__pmUnparseHostAttrsSpec( + pmHostSpec *hosts, + int count, + __pmHashCtl *attrs, + char *string, + size_t size) +{ + __pmHashNode *node; + int off = 0, len = size; /* offset in string and space remaining */ + int sts, first; + + if ((node = __pmHashSearch(PCP_ATTR_PROTOCOL, attrs)) != NULL) { + if ((sts = snprintf(string, len, "%s://", (char *)node->data)) >= len) + return -E2BIG; + len -= sts; off += sts; + } + else if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL) { + if ((sts = snprintf(string, len, "unix:/")) >= len) + return -E2BIG; + len -= sts; off += sts; + } + else if (__pmHashSearch(PCP_ATTR_LOCAL, attrs) != NULL) { + if ((sts = snprintf(string, len, "local:/")) >= len) + return -E2BIG; + len -= sts; off += sts; + } + + if ((sts = unparseHostSpec(hosts, count, string + off, len, 0)) >= len) + return sts; + len -= sts; off += sts; + + first = 1; + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { + if (node->key == PCP_ATTR_PROTOCOL || + node->key == PCP_ATTR_UNIXSOCK || node->key == PCP_ATTR_LOCAL) + continue; + if ((sts = snprintf(string + off, len, "%c", first ? '?' : '&')) >= len) + return -E2BIG; + len -= sts; off += sts; + first = 0; + + if ((sts = unparseAttribute(node, string + off, len)) >= len) + return -E2BIG; + len -= sts; off += sts; + } + + return off; +} diff --git a/src/libpcp/src/store.c b/src/libpcp/src/store.c new file mode 100644 index 0000000..2d16ef3 --- /dev/null +++ b/src/libpcp/src/store.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "internal.h" + +int +pmStore(const pmResult *result) +{ + int n; + int sts; + __pmContext *ctxp; + __pmDSO *dp; + + if (result->numpmid < 1) + return PM_ERR_TOOSMALL; + + for (n = 0; n < result->numpmid; n++) { + if (result->vset[n]->numval < 1) { + return PM_ERR_VALUE; + } + } + + if ((sts = pmWhichContext()) >= 0) { + int ctx = sts; + + ctxp = __pmHandleToPtr(sts); + if (ctxp == NULL) + return PM_ERR_NOCONTEXT; + if (ctxp->c_type == PM_CONTEXT_HOST) { + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + sts = __pmSendResult(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), result); + if (sts < 0) + sts = __pmMapErrno(sts); + else { + __pmPDU *pb; + int pinpdu; + + pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, + ctxp->c_pmcd->pc_tout_sec, &pb); + if (sts == PDU_ERROR) + __pmDecodeError(pb, &sts); + else if (sts != PM_ERR_TIMEOUT) + sts = PM_ERR_IPC; + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + PM_UNLOCK(__pmLock_libpcp); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + /* + * have to do them one at a time in case different DSOs + * involved ... need to copy each result->vset[n] + */ + pmResult tmp; + pmValueSet tmpvset; + + if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) { + /* Local context requires single-threaded applications */ + sts = PM_ERR_THREAD; + } else { + sts = 0; + for (n = 0; sts == 0 && n < result->numpmid; n++) { + if ((dp = __pmLookupDSO(((__pmID_int *)&result->vset[n]->pmid)->domain)) == NULL) + sts = PM_ERR_NOAGENT; + else { + tmp.numpmid = 1; + tmp.vset[0] = &tmpvset; + tmpvset.numval = 1; + tmpvset.pmid = result->vset[n]->pmid; + tmpvset.valfmt = result->vset[n]->valfmt; + tmpvset.vlist[0] = result->vset[n]->vlist[0]; + if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + dp->dispatch.version.four.ext->e_context = ctx; + sts = dp->dispatch.version.any.store(&tmp, dp->dispatch.version.any.ext); + } + } + } + } + else { + /* assume PM_CONTEXT_ARCHIVE -- this is an error */ + sts = PM_ERR_NOTHOST; + } + PM_UNLOCK(ctxp->c_lock); + } + + return sts; +} diff --git a/src/libpcp/src/stuffvalue.c b/src/libpcp/src/stuffvalue.c new file mode 100644 index 0000000..23e9cc1 --- /dev/null +++ b/src/libpcp/src/stuffvalue.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" + +int +__pmStuffValue(const pmAtomValue *avp, pmValue *vp, int type) +{ + void *src; + size_t need, body; + + switch (type) { + case PM_TYPE_32: + case PM_TYPE_U32: + vp->value.lval = avp->ul; + return PM_VAL_INSITU; + + case PM_TYPE_FLOAT: + body = sizeof(float); + src = (void *)&avp->f; + break; + + case PM_TYPE_64: + case PM_TYPE_U64: + case PM_TYPE_DOUBLE: + body = sizeof(__int64_t); + src = (void *)&avp->ull; + break; + + case PM_TYPE_AGGREGATE: + /* + * vbp field of pmAtomValue points to a dynamically allocated + * pmValueBlock ... the vlen and vtype fields MUST have been + * already set up. + * A new pmValueBlock header will be allocated below, so adjust + * the length here (PM_VAL_HDR_SIZE will be added back later). + */ + body = avp->vbp->vlen - PM_VAL_HDR_SIZE; + src = avp->vbp->vbuf; + break; + + case PM_TYPE_STRING: + body = strlen(avp->cp) + 1; + src = (void *)avp->cp; + break; + + case PM_TYPE_AGGREGATE_STATIC: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + /* + * vbp field of pmAtomValue points to a statically allocated + * pmValueBlock ... the vlen and vtype fields MUST have been + * already set up and are not modified here + * + * DO NOT make a copy of the value in this case + */ + vp->value.pval = avp->vbp; + return PM_VAL_SPTR; + + default: + return PM_ERR_TYPE; + } + need = body + PM_VAL_HDR_SIZE; + vp->value.pval = (pmValueBlock *)malloc( + (need < sizeof(pmValueBlock)) ? sizeof(pmValueBlock) : need); + if (vp->value.pval == NULL) + return -oserror(); + vp->value.pval->vlen = (int)need; + vp->value.pval->vtype = type; + memcpy((void *)vp->value.pval->vbuf, (void *)src, body); + return PM_VAL_DPTR; +} diff --git a/src/libpcp/src/tv.c b/src/libpcp/src/tv.c new file mode 100644 index 0000000..cabdf75 --- /dev/null +++ b/src/libpcp/src/tv.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include <sys/time.h> + +/* + * real additive time, *ap plus *bp + */ +double +__pmtimevalAdd(const struct timeval *ap, const struct timeval *bp) +{ + return (double)(ap->tv_sec + bp->tv_sec) + (double)(ap->tv_usec + bp->tv_usec)/1000000.0; +} + +/* + * real time difference, *ap minus *bp + */ +double +__pmtimevalSub(const struct timeval *ap, const struct timeval *bp) +{ + return (double)(ap->tv_sec - bp->tv_sec) + (double)(ap->tv_usec - bp->tv_usec)/1000000.0; +} + +/* + * convert a timeval to a double (units = seconds) + */ +double +__pmtimevalToReal(const struct timeval *val) +{ + double dbl = (double)(val->tv_sec); + dbl += (double)val->tv_usec / 1000000.0; + return dbl; +} + +/* + * convert double to a timeval + */ +void +__pmtimevalFromReal(double dbl, struct timeval *val) +{ + val->tv_sec = (time_t)dbl; + val->tv_usec = (long)(((dbl - (double)val->tv_sec) * 1000000.0)); +} + +/* + * Sleep for a specified amount of time + */ +void +__pmtimevalSleep(struct timeval interval) +{ + struct timespec delay; + struct timespec left; + int sts; + + delay.tv_sec = interval.tv_sec; + delay.tv_nsec = interval.tv_usec * 1000; + + for (;;) { /* loop to catch early wakeup by nanosleep */ + sts = nanosleep(&delay, &left); + if (sts == 0 || (sts < 0 && oserror() != EINTR)) + break; + delay = left; + } +} + +/* subtract timevals */ +static struct timeval +tsub(struct timeval t1, struct timeval t2) +{ + t1.tv_usec -= t2.tv_usec; + if (t1.tv_usec < 0) { + t1.tv_usec += 1000000; + t1.tv_sec--; + } + t1.tv_sec -= t2.tv_sec; + return t1; +} + +/* convert timeval to timespec */ +static struct timespec * +tospec(struct timeval tv, struct timespec *ts) +{ + ts->tv_nsec = tv.tv_usec * 1000; + ts->tv_sec = tv.tv_sec; + return ts; +} + +#if !defined(IS_MINGW) +void +__pmtimevalNow(struct timeval *tv) +{ + gettimeofday(tv, NULL); +} +#endif + +/* sleep until given timeval */ +void +__pmtimevalPause(struct timeval sched) +{ + int sts; + struct timeval curr; /* current time */ + struct timespec delay; /* interval to sleep */ + struct timespec left; /* remaining sleep time */ + + __pmtimevalNow(&curr); + tospec(tsub(sched, curr), &delay); + for (;;) { /* loop to catch early wakeup by nanosleep */ + sts = nanosleep(&delay, &left); + if (sts == 0 || (sts < 0 && oserror() != EINTR)) + break; + delay = left; + } +} diff --git a/src/libpcp/src/tz.c b/src/libpcp/src/tz.c new file mode 100644 index 0000000..1157ad6 --- /dev/null +++ b/src/libpcp/src/tz.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 1995-2003,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * These routines manipulate the environment and call lots of routines + * like localtime(), asctime(), gmtime(), getenv(), putenv() ... all of + * which are not thread-safe. + * + * We use the big lock to prevent concurrent execution. + * + * Need to call PM_INIT_LOCKS() in all the exposed routines because we + * may be called before a context has been created, and missed the + * lock initialization in pmNewContext(). + */ + +#include "pmapi.h" +#include "impl.h" + +static char *envtz; /* buffer in env */ +static int envtzlen; + +static char *savetz; /* real $TZ from env */ +static char **savetzp; + +static int nzone; /* table of zones */ +static int curzone = -1; +static char **zone; + +#if !defined(HAVE_UNDERBAR_ENVIRON) +#define _environ environ +#endif + +extern char **_environ; + +static void +_pushTZ(void) +{ + char **p; + + savetzp = NULL; + for (p = _environ; *p != NULL; p++) { + if (strncmp(*p, "TZ=", 3) == 0) { + savetz = *p; + *p = envtz; + savetzp = p; + break; + } + } + if (*p == NULL) + putenv(envtz); + tzset(); +} + +static void +_popTZ(void) +{ + if (savetzp != NULL) + *savetzp = savetz; + else + putenv("TZ="); + tzset(); +} + +/* + * Construct TZ=... subject to the constraint that the length of the + * timezone part is not more than PM_TZ_MAXLEN bytes + * Assumes TZ= is in the start of tzbuffer and this is not touched. + * And finally set TZ in the environment. + */ +static void +__pmSquashTZ(char *tzbuffer) +{ + time_t now = time(NULL); + struct tm *t; + char *tzn; +#ifndef IS_MINGW + time_t offset; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + tzset(); + t = localtime(&now); + +#ifdef HAVE_ALTZONE + offset = (t->tm_isdst > 0) ? altzone : timezone; +#elif defined HAVE_STRFTIME_z + { + char tzoffset[6]; /* +1200\0 */ + + strftime (tzoffset, sizeof (tzoffset), "%z", t); + offset = -strtol (tzoffset, NULL, 10); + offset = ((offset/100) * 3600) + ((offset%100) * 60); + } +#else + { + struct tm *gmt = gmtime(&now); + offset = (gmt->tm_hour - t->tm_hour) * 3600 + + (gmt->tm_min - t->tm_min) * 60; + } +#endif + + tzn = tzname[(t->tm_isdst > 0)]; + + if (offset != 0) { + int hours = offset / 3600; + int mins = abs ((offset % 3600) / 60); + int len = (int) strlen(tzn); + + if (mins == 0) { + /* -3 for +HH in worst case */ + if (len > PM_TZ_MAXLEN-3) len = PM_TZ_MAXLEN-3; + snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%*.*s%+d", len, len, tzn, hours); + } + else { + /* -6 for +HH:MM in worst case */ + if (len > PM_TZ_MAXLEN-6) len = PM_TZ_MAXLEN-6; + snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%*.*s%+d:%02d", len, len, tzn, hours, mins); + } + } + else { + strncpy(tzbuffer+3, tzn, PM_TZ_MAXLEN); + tzbuffer[PM_TZ_MAXLEN+4-1] = '\0'; + } + putenv(tzbuffer); + + PM_UNLOCK(__pmLock_libpcp); + return; + +#else /* IS_MINGW */ + /* + * Use the native Win32 API to extract the timezone. This is + * a Windows timezone, we want the POSIX style but there's no + * API, really. What we've found works, is the same approach + * the MSYS dll takes - we set TZ their way (below) and then + * use tzset, then extract. Note that the %Z and %z strftime + * parameters do not contain abbreviated names/offsets (they + * both contain Windows timezone, and both are the same with + * no TZ). Note also that putting the Windows name into the + * environment as TZ does not do anything good (see the tzset + * MSDN docs). + */ +#define is_upper(c) ((unsigned)(c) - 'A' <= 26) + + TIME_ZONE_INFORMATION tz; + static const char wildabbr[] = "GMT"; + char tzbuf[256], tzoff[64]; + char *cp, *dst, *off; + wchar_t *src; + div_t d; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + GetTimeZoneInformation(&tz); + dst = cp = tzbuf; + off = tzoff; + for (src = tz.StandardName; *src; src++) + if (is_upper(*src)) *dst++ = *src; + if (cp == dst) { + /* In Asian Windows, tz.StandardName may not contain + the timezone name. */ + strcpy(cp, wildabbr); + cp += strlen(wildabbr); + } + else + cp = dst; + d = div(tz.Bias+tz.StandardBias, 60); + sprintf(cp, "%d", d.quot); + sprintf(off, "%d", d.quot); + if (d.rem) { + sprintf(cp=strchr(cp, 0), ":%d", abs(d.rem)); + sprintf(off=strchr(off, 0), ":%d", abs(d.rem)); + } + if (tz.StandardDate.wMonth) { + cp = strchr(cp, 0); + dst = cp; + for (src = tz.DaylightName; *src; src++) + if (is_upper(*src)) *dst++ = *src; + if (cp == dst) { + /* In Asian Windows, tz.StandardName may not contain + the daylight name. */ + strcpy(tzbuf, wildabbr); + cp += strlen(wildabbr); + } + else + cp = dst; + d = div(tz.Bias+tz.DaylightBias, 60); + sprintf(cp, "%d", d.quot); + if (d.rem) + sprintf(cp=strchr(cp, 0), ":%d", abs(d.rem)); + cp = strchr(cp, 0); + sprintf(cp=strchr(cp, 0), ",M%d.%d.%d/%d", + tz.DaylightDate.wMonth, + tz.DaylightDate.wDay, + tz.DaylightDate.wDayOfWeek, + tz.DaylightDate.wHour); + if (tz.DaylightDate.wMinute || tz.DaylightDate.wSecond) + sprintf(cp=strchr(cp, 0), ":%d", tz.DaylightDate.wMinute); + if (tz.DaylightDate.wSecond) + sprintf(cp=strchr(cp, 0), ":%d", tz.DaylightDate.wSecond); + cp = strchr(cp, 0); + sprintf(cp=strchr(cp, 0), ",M%d.%d.%d/%d", + tz.StandardDate.wMonth, + tz.StandardDate.wDay, + tz.StandardDate.wDayOfWeek, + tz.StandardDate.wHour); + if (tz.StandardDate.wMinute || tz.StandardDate.wSecond) + sprintf(cp=strchr(cp, 0), ":%d", tz.StandardDate.wMinute); + if (tz.StandardDate.wSecond) + sprintf(cp=strchr(cp, 0), ":%d", tz.StandardDate.wSecond); + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_TIMECONTROL) + fprintf(stderr, "Win32 TZ=%s\n", tzbuf); +#endif + + snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%s", tzbuf); + putenv(tzbuffer); + + tzset(); + t = localtime(&now); + tzn = tzname[(t->tm_isdst > 0)]; + + snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%s%s", tzn, tzoff); + putenv(tzbuffer); + + PM_UNLOCK(__pmLock_libpcp); + return; +#endif +} + +/* + * __pmTimezone: work out local timezone + */ +char * +__pmTimezone(void) +{ + static char *tzbuffer = NULL; + char *tz; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + tz = getenv("TZ"); + + if (tzbuffer == NULL) { + /* + * size is PM_TZ_MAXLEN + length of "TZ=" + null byte terminator + */ + tzbuffer = (char *)malloc(PM_TZ_MAXLEN+4); + if (tzbuffer == NULL) { + /* not much we can do here ... */ + PM_UNLOCK(__pmLock_libpcp); + return NULL; + } + strcpy(tzbuffer, "TZ="); + } + + if (tz == NULL || tz[0] == ':') { + /* NO TZ in the environment - invent one. If TZ starts with a colon, + * it's an Olson-style TZ and it does not supported on all IRIXes, so + * squash it into a simple one (pv#788431). */ + __pmSquashTZ(tzbuffer); + tz = &tzbuffer[3]; + } else if (strlen(tz) > PM_TZ_MAXLEN) { + /* TZ is too long to fit into the internal PCP timezone structs + * let's try to sqash it a bit */ + char *tb; + + if ((tb = strdup(tz)) == NULL) { + /* sorry state of affairs, go squash w/out copying buffer */ + __pmSquashTZ(tzbuffer); + tz = &tzbuffer[3]; + } + else { + char *ptz = tz; + char *zeros; + char *end = tb; + + while ((zeros = strstr(ptz, ":00")) != NULL) { + strncpy(end, ptz, zeros-ptz); + end += zeros-ptz; + *end = '\0'; + ptz = zeros+3; + } + + if (strlen(tb) > PM_TZ_MAXLEN) { + /* Still too long - let's pretend it's Olson */ + __pmSquashTZ(tzbuffer); + tz = &tzbuffer[3]; + } else { + strcpy(tzbuffer+3, tb); + putenv(tzbuffer); + tz = tzbuffer+3; + } + + free(tb); + } + } + + PM_UNLOCK(__pmLock_libpcp); + return tz; +} + +/* + * buffer should be at least PM_TZ_MAXLEN bytes long + */ +char * +__pmTimezone_r(char *buf, int buflen) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + strcpy(buf, __pmTimezone()); + PM_UNLOCK(__pmLock_libpcp); + return buf; +} + +int +pmUseZone(const int tz_handle) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + if (tz_handle < 0 || tz_handle >= nzone) { + PM_UNLOCK(__pmLock_libpcp); + return -1; + } + + curzone = tz_handle; + strcpy(&envtz[3], zone[curzone]); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmUseZone(%d) tz=%s\n", curzone, zone[curzone]); +#endif + + PM_UNLOCK(__pmLock_libpcp); + return 0; +} + +int +pmNewZone(const char *tz) +{ + int len; + int hack = 0; + int sts; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + len = (int)strlen(tz); + if (len == 3) { + /* + * things like TZ=GMT may be broken in libc, particularly + * in _ltzset() of time_comm.c, where changes to TZ are + * sometimes not properly reflected. + * TZ=GMT+0 avoids the problem. + */ + len += 2; + hack = 1; + } + + if (len+4 > envtzlen) { + /* expand buffer for env */ + if (envtz != NULL) + free(envtz); + envtzlen = len+4; + envtz = (char *)malloc(envtzlen); + strcpy(envtz, "TZ="); + } + strcpy(&envtz[3], tz); + if (hack) + /* see above */ + strcpy(&envtz[6], "+0"); + + curzone = nzone++; + zone = (char **)realloc(zone, nzone * sizeof(char *)); + if (zone == NULL) { + __pmNoMem("pmNewZone", nzone * sizeof(char *), PM_FATAL_ERR); + } + zone[curzone] = strdup(&envtz[3]); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmNewZone(%s) -> %d\n", zone[curzone], curzone); +#endif + sts = curzone; + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +int +pmNewContextZone(void) +{ + __pmContext *ctxp; + int sts; + + if ((ctxp = __pmHandleToPtr(pmWhichContext())) == NULL) + return PM_ERR_NOCONTEXT; + + if (ctxp->c_type == PM_CONTEXT_ARCHIVE) { + sts = pmNewZone(ctxp->c_archctl->ac_log->l_label.ill_tz); + PM_UNLOCK(ctxp->c_lock); + } + else if (ctxp->c_type == PM_CONTEXT_LOCAL) { + char tzbuf[PM_TZ_MAXLEN]; + /* from env, not PMCD */ + PM_UNLOCK(ctxp->c_lock); + __pmTimezone_r(tzbuf, sizeof(tzbuf)); + sts = pmNewZone(tzbuf); + } + else { + /* assume PM_CONTEXT_HOST */ + char *name = "pmcd.timezone"; + pmID pmid; + pmResult *rp; + + PM_UNLOCK(ctxp->c_lock); + if ((sts = pmLookupName(1, &name, &pmid)) < 0) + return sts; + if ((sts = pmFetch(1, &pmid, &rp)) >= 0) { + if (rp->vset[0]->numval == 1 && + (rp->vset[0]->valfmt == PM_VAL_DPTR || rp->vset[0]->valfmt == PM_VAL_SPTR)) + sts = pmNewZone((char *)rp->vset[0]->vlist[0].value.pval->vbuf); + else + sts = PM_ERR_VALUE; + pmFreeResult(rp); + } + } + + return sts; +} + +char * +pmCtime(const time_t *clock, char *buf) +{ +#if !defined(IS_SOLARIS) && !defined(IS_MINGW) + struct tm tbuf; +#endif + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + if (curzone >= 0) { + _pushTZ(); +#if defined(IS_SOLARIS) || defined(IS_MINGW) + strcpy(buf, asctime(localtime(clock))); +#else + asctime_r(localtime_r(clock, &tbuf), buf); +#endif + _popTZ(); + } + else { +#if defined(IS_SOLARIS) || defined(IS_MINGW) + strcpy(buf, asctime(localtime(clock))); +#else + asctime_r(localtime_r(clock, &tbuf), buf); +#endif + } + + PM_UNLOCK(__pmLock_libpcp); + return buf; +} + +struct tm * +pmLocaltime(const time_t *clock, struct tm *result) +{ +#if defined(IS_SOLARIS) || defined(IS_MINGW) + struct tm *tmp; +#endif + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + if (curzone >= 0) { + _pushTZ(); +#if defined(IS_SOLARIS) || defined(IS_MINGW) + tmp = localtime(clock); + memcpy(result, tmp, sizeof(*result)); +#else + localtime_r(clock, result); +#endif + _popTZ(); + } + else { +#if defined(IS_SOLARIS) || defined(IS_MINGW) + tmp = localtime(clock); + memcpy(result, tmp, sizeof(*result)); +#else + localtime_r(clock, result); +#endif + } + + PM_UNLOCK(__pmLock_libpcp); + return result; +} + +time_t +__pmMktime(struct tm *timeptr) +{ + time_t ans; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + + if (curzone >= 0) { + _pushTZ(); + ans = mktime(timeptr); + _popTZ(); + } + else + ans = mktime(timeptr); + + PM_UNLOCK(__pmLock_libpcp); + return ans; +} + +int +pmWhichZone(char **tz) +{ + int sts; + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (curzone >= 0) + *tz = zone[curzone]; + sts = curzone; + PM_UNLOCK(__pmLock_libpcp); + return sts; +} diff --git a/src/libpcp/src/units.c b/src/libpcp/src/units.c new file mode 100644 index 0000000..8827868 --- /dev/null +++ b/src/libpcp/src/units.c @@ -0,0 +1,1107 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include <inttypes.h> + +#if defined(HAVE_MATH_H) +#include <math.h> +#endif + +#if !defined(ABS) +#define ABS(a) ((a) < 0 ? -(a) : (a)) +#endif + +#if defined(HAVE_CONST_LONGLONG) +#define SIGN_64_MASK 0x8000000000000000LL +#else +#define SIGN_64_MASK 0x8000000000000000 +#endif + +/* + * pmAtomValue -> string, max length is 80 bytes + * + * To avoid alignment problems, avp _must_ be aligned appropriately + * for a pmAtomValue pointer by the caller. + */ +char * +pmAtomStr_r(const pmAtomValue *avp, int type, char *buf, int buflen) +{ + int i; + int vlen; + char strbuf[40]; + + switch (type) { + case PM_TYPE_32: + snprintf(buf, buflen, "%d", avp->l); + break; + case PM_TYPE_U32: + snprintf(buf, buflen, "%u", avp->ul); + break; + case PM_TYPE_64: + snprintf(buf, buflen, "%"PRIi64, avp->ll); + break; + case PM_TYPE_U64: + snprintf(buf, buflen, "%"PRIu64, avp->ull); + break; + case PM_TYPE_FLOAT: + snprintf(buf, buflen, "%e", (double)avp->f); + break; + case PM_TYPE_DOUBLE: + snprintf(buf, buflen, "%e", avp->d); + break; + case PM_TYPE_STRING: + if (avp->cp == NULL) + snprintf(buf, buflen, "<null>"); + else { + i = (int)strlen(avp->cp); + if (i < 38) + snprintf(buf, buflen, "\"%s\"", avp->cp); + else + snprintf(buf, buflen, "\"%34.34s...\"", avp->cp); + } + break; + case PM_TYPE_AGGREGATE: + case PM_TYPE_AGGREGATE_STATIC: + if (avp->vbp == NULL) { + snprintf(buf, buflen, "<null>"); + break; + } + vlen = avp->vbp->vlen - PM_VAL_HDR_SIZE; + if (vlen == 0) + snprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen); + else { + char *cp; + char *bp; + snprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen); + cp = (char *)avp->vbp->vbuf; + for (i = 0; i < vlen && i < 12; i++) { + bp = &buf[strlen(buf)]; + if ((i % 4) == 0) + snprintf(bp, sizeof(buf) - (bp-buf), " %02x", *cp & 0xff); + else + snprintf(bp, sizeof(buf) - (bp-buf), "%02x", *cp & 0xff); + cp++; + } + if (vlen > 12) { + bp = &buf[strlen(buf)]; + snprintf(bp, sizeof(buf) - (bp-buf), " ..."); + } + } + break; + case PM_TYPE_EVENT: { + /* have to assume alignment is OK in this case */ + pmEventArray *eap = (pmEventArray *)avp->vbp; + if (eap->ea_nrecords == 1) + snprintf(buf, buflen, "[1 event record]"); + else + snprintf(buf, buflen, "[%d event records]", eap->ea_nrecords); + break; + } + case PM_TYPE_HIGHRES_EVENT: { + /* have to assume alignment is OK in this case */ + pmHighResEventArray *hreap = (pmHighResEventArray *)avp->vbp; + if (hreap->ea_nrecords == 1) + snprintf(buf, buflen, "[1 event record]"); + else + snprintf(buf, buflen, "[%d event records]", hreap->ea_nrecords); + break; + } + default: + snprintf(buf, buflen, "Error: unexpected type: %s", pmTypeStr_r(type, strbuf, sizeof(strbuf))); + } + return buf; +} + +/* + * To avoid alignment problems, avp _must_ be aligned appropriately + * for a pmAtomValue pointer by the caller. + */ +const char * +pmAtomStr(const pmAtomValue *avp, int type) +{ + static char abuf[80]; + pmAtomStr_r(avp, type, abuf, sizeof(abuf)); + return abuf; +} + +/* + * must be in agreement with ordinal values for PM_TYPE_* #defines + */ +static const char *typename[] = { + "32", "U32", "64", "U64", "FLOAT", "DOUBLE", "STRING", "AGGREGATE", "AGGREGATE_STATIC", "EVENT", "HIGHRES_EVENT" +}; + +/* PM_TYPE_* -> string, max length is 20 bytes */ +char * +pmTypeStr_r(int type, char *buf, int buflen) +{ + if (type >= 0 && type < sizeof(typename)/sizeof(typename[0])) + snprintf(buf, buflen, "%s", typename[type]); + else if (type == PM_TYPE_NOSUPPORT) + snprintf(buf, buflen, "%s", "Not Supported"); + else if (type == PM_TYPE_UNKNOWN) + snprintf(buf, buflen, "%s", "Unknown"); + else + snprintf(buf, buflen, "Illegal type=%d", type); + + return buf; +} + +const char * +pmTypeStr(int type) +{ + static char tbuf[20]; + pmTypeStr_r(type, tbuf, sizeof(tbuf)); + return tbuf; +} + +/* scale+units -> string, max length is 60 bytes */ +char * +pmUnitsStr_r(const pmUnits *pu, char *buf, int buflen) +{ + char *spacestr = NULL; + char *timestr = NULL; + char *countstr = NULL; + char *p; + char sbuf[20]; + char tbuf[20]; + char cbuf[20]; + + buf[0] = '\0'; + + if (pu->dimSpace) { + switch (pu->scaleSpace) { + case PM_SPACE_BYTE: + spacestr = "byte"; + break; + case PM_SPACE_KBYTE: + spacestr = "Kbyte"; + break; + case PM_SPACE_MBYTE: + spacestr = "Mbyte"; + break; + case PM_SPACE_GBYTE: + spacestr = "Gbyte"; + break; + case PM_SPACE_TBYTE: + spacestr = "Tbyte"; + break; + case PM_SPACE_PBYTE: + spacestr = "Pbyte"; + break; + case PM_SPACE_EBYTE: + spacestr = "Ebyte"; + break; + default: + snprintf(sbuf, sizeof(sbuf), "space-%d", pu->scaleSpace); + spacestr = sbuf; + break; + } + } + if (pu->dimTime) { + switch (pu->scaleTime) { + case PM_TIME_NSEC: + timestr = "nanosec"; + break; + case PM_TIME_USEC: + timestr = "microsec"; + break; + case PM_TIME_MSEC: + timestr = "millisec"; + break; + case PM_TIME_SEC: + timestr = "sec"; + break; + case PM_TIME_MIN: + timestr = "min"; + break; + case PM_TIME_HOUR: + timestr = "hour"; + break; + default: + snprintf(tbuf, sizeof(tbuf), "time-%d", pu->scaleTime); + timestr = tbuf; + break; + } + } + if (pu->dimCount) { + switch (pu->scaleCount) { + case 0: + countstr = "count"; + break; + case 1: + snprintf(cbuf, sizeof(cbuf), "count x 10"); + countstr = cbuf; + break; + default: + snprintf(cbuf, sizeof(cbuf), "count x 10^%d", pu->scaleCount); + countstr = cbuf; + break; + } + } + + p = buf; + + if (pu->dimSpace > 0) { + if (pu->dimSpace == 1) + snprintf(p, buflen, "%s", spacestr); + else + snprintf(p, buflen, "%s^%d", spacestr, pu->dimSpace); + while (*p) p++; + *p++ = ' '; + } + if (pu->dimTime > 0) { + if (pu->dimTime == 1) + snprintf(p, buflen - (p - buf), "%s", timestr); + else + snprintf(p, buflen - (p - buf), "%s^%d", timestr, pu->dimTime); + while (*p) p++; + *p++ = ' '; + } + if (pu->dimCount > 0) { + if (pu->dimCount == 1) + snprintf(p, buflen - (p - buf), "%s", countstr); + else + snprintf(p, buflen - (p - buf), "%s^%d", countstr, pu->dimCount); + while (*p) p++; + *p++ = ' '; + } + if (pu->dimSpace < 0 || pu->dimTime < 0 || pu->dimCount < 0) { + *p++ = '/'; + *p++ = ' '; + if (pu->dimSpace < 0) { + if (pu->dimSpace == -1) + snprintf(p, buflen - (p - buf), "%s", spacestr); + else + snprintf(p, buflen - (p - buf), "%s^%d", spacestr, -pu->dimSpace); + while (*p) p++; + *p++ = ' '; + } + if (pu->dimTime < 0) { + if (pu->dimTime == -1) + snprintf(p, buflen - (p - buf), "%s", timestr); + else + snprintf(p, buflen - (p - buf), "%s^%d", timestr, -pu->dimTime); + while (*p) p++; + *p++ = ' '; + } + if (pu->dimCount < 0) { + if (pu->dimCount == -1) + snprintf(p, buflen - (p - buf), "%s", countstr); + else + snprintf(p, buflen - (p - buf), "%s^%d", countstr, -pu->dimCount); + while (*p) p++; + *p++ = ' '; + } + } + + if (buf[0] == '\0') { + /* + * dimension is all 0, but scale maybe specified ... small + * anomaly here as we would expect dimCount to be 1 not + * 0 for these cases, but support maintained for historical + * behaviour + */ + if (pu->scaleCount == 1) + snprintf(buf, buflen, "x 10"); + else if (pu->scaleCount != 0) + snprintf(buf, buflen, "x 10^%d", pu->scaleCount); + } + else { + p--; + *p = '\0'; + } + + return buf; +} + +const char * +pmUnitsStr(const pmUnits *pu) +{ + static char ubuf[60]; + pmUnitsStr_r(pu, ubuf, sizeof(ubuf)); + return ubuf; +} + +/* Scale conversion, based on value format, value type and scale */ +int +pmConvScale(int type, const pmAtomValue *ival, const pmUnits *iunit, + pmAtomValue *oval, const pmUnits *ounit) +{ + int sts; + int k; + __int64_t div, mult; + __int64_t d, m; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + fprintf(stderr, "pmConvScale: %s", pmAtomStr_r(ival, type, strbuf, sizeof(strbuf))); + fprintf(stderr, " [%s]", pmUnitsStr_r(iunit, strbuf, sizeof(strbuf))); + } +#endif + + if (iunit->dimSpace != ounit->dimSpace || + iunit->dimTime != ounit->dimTime || + iunit->dimCount != ounit->dimCount) { + sts = PM_ERR_CONV; + goto bad; + } + + div = mult = 1; + + if (iunit->dimSpace) { + d = 1; + m = 1; + switch (iunit->scaleSpace) { + case PM_SPACE_BYTE: + d = 1024 * 1024; + break; + case PM_SPACE_KBYTE: + d = 1024; + break; + case PM_SPACE_MBYTE: + /* the canonical unit */ + break; + case PM_SPACE_GBYTE: + m = 1024; + break; + case PM_SPACE_TBYTE: + m = 1024 * 1024; + break; + case PM_SPACE_PBYTE: + m = 1024 * 1024 * 1024; + break; + case PM_SPACE_EBYTE: + m = (__int64_t)1024 * 1024 * 1024 * 1024; + break; + default: + sts = PM_ERR_UNIT; + goto bad; + } + switch (ounit->scaleSpace) { + case PM_SPACE_BYTE: + m *= 1024 * 1024; + break; + case PM_SPACE_KBYTE: + m *= 1024; + break; + case PM_SPACE_MBYTE: + /* the canonical unit */ + break; + case PM_SPACE_GBYTE: + d *= 1024; + break; + case PM_SPACE_TBYTE: + d *= 1024 * 1024; + break; + case PM_SPACE_PBYTE: + d *= 1024 * 1024 * 1024; + break; + case PM_SPACE_EBYTE: + d *= (__int64_t)1024 * 1024 * 1024 * 1024; + break; + default: + sts = PM_ERR_UNIT; + goto bad; + } + if (iunit->dimSpace > 0) { + for (k = 0; k < iunit->dimSpace; k++) { + div *= d; + mult *= m; + } + } + else { + for (k = iunit->dimSpace; k < 0; k++) { + mult *= d; + div *= m; + } + } + } + + if (iunit->dimTime) { + d = 1; + m = 1; + switch (iunit->scaleTime) { + case PM_TIME_NSEC: + d = 1000000000; + break; + case PM_TIME_USEC: + d = 1000000; + break; + case PM_TIME_MSEC: + d = 1000; + break; + case PM_TIME_SEC: + /* the canonical unit */ + break; + case PM_TIME_MIN: + m = 60; + break; + case PM_TIME_HOUR: + m = 3600; + break; + default: + sts = PM_ERR_UNIT; + goto bad; + } + switch (ounit->scaleTime) { + case PM_TIME_NSEC: + m *= 1000000000; + break; + case PM_TIME_USEC: + m *= 1000000; + break; + case PM_TIME_MSEC: + m *= 1000; + break; + case PM_TIME_SEC: + /* the canonical unit */ + break; + case PM_TIME_MIN: + d *= 60; + break; + case PM_TIME_HOUR: + d *= 3600; + break; + default: + sts = PM_ERR_UNIT; + goto bad; + } + if (iunit->dimTime > 0) { + for (k = 0; k < iunit->dimTime; k++) { + div *= d; + mult *= m; + } + } + else { + for (k = iunit->dimTime; k < 0; k++) { + mult *= d; + div *= m; + } + } + } + + if (iunit->dimCount || + (iunit->dimSpace == 0 && iunit->dimTime == 0)) { + d = 1; + m = 1; + if (iunit->scaleCount < 0) { + for (k = iunit->scaleCount; k < 0; k++) + d *= 10; + } + else if (iunit->scaleCount > 0) { + for (k = 0; k < iunit->scaleCount; k++) + m *= 10; + } + if (ounit->scaleCount < 0) { + for (k = ounit->scaleCount; k < 0; k++) + m *= 10; + } + else if (ounit->scaleCount > 0) { + for (k = 0; k < ounit->scaleCount; k++) + d *= 10; + } + if (iunit->dimCount > 0) { + for (k = 0; k < iunit->dimCount; k++) { + div *= d; + mult *= m; + } + } + else if (iunit->dimCount < 0) { + for (k = iunit->dimCount; k < 0; k++) { + mult *= d; + div *= m; + } + } + else { + mult = m; + div = d; + } + } + + if (mult % div == 0) { + mult /= div; + div = 1; + } + + switch (type) { + case PM_TYPE_32: + oval->l = (__int32_t)((ival->l * mult + div/2) / div); + break; + case PM_TYPE_U32: + oval->ul = (__uint32_t)((ival->ul * mult + div/2) / div); + break; + case PM_TYPE_64: + oval->ll = (ival->ll * mult + div/2) / div; + break; + case PM_TYPE_U64: + oval->ull = (ival->ull * mult + div/2) / div; + break; + case PM_TYPE_FLOAT: + oval->f = ival->f * ((float)mult / (float)div); + break; + case PM_TYPE_DOUBLE: + oval->d = ival->d * ((double)mult / (double)div); + break; + default: + sts = PM_ERR_CONV; + goto bad; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + fprintf(stderr, " -> %s", pmAtomStr_r(oval, type, strbuf, sizeof(strbuf))); + fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf))); + } +#endif + return 0; + +bad: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[60]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf))); + } +#endif + return sts; +} + +/* Value extract from pmValue and type conversion */ +int +pmExtractValue(int valfmt, const pmValue *ival, int itype, + pmAtomValue *oval, int otype) +{ + void *avp; + pmAtomValue av; + int sts = 0; + int len; + const char *vp; + char buf[80]; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + fprintf(stderr, "pmExtractValue: "); + vp = "???"; + } +#endif + + oval->ll = 0; + if (valfmt == PM_VAL_INSITU) { + av.l = ival->value.lval; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf)); + } +#endif + switch (itype) { + + case PM_TYPE_32: + case PM_TYPE_UNKNOWN: + switch (otype) { + case PM_TYPE_32: + oval->l = av.l; + break; + case PM_TYPE_U32: + if (av.l < 0) + sts = PM_ERR_SIGN; + else + oval->ul = (__uint32_t)av.l; + break; + case PM_TYPE_64: + oval->ll = (__int64_t)av.l; + break; + case PM_TYPE_U64: + if (av.l < 0) + sts = PM_ERR_SIGN; + else + oval->ull = (__uint64_t)av.l; + break; + case PM_TYPE_FLOAT: + oval->f = (float)av.l; + break; + case PM_TYPE_DOUBLE: + oval->d = (double)av.l; + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_U32: + switch (otype) { + case PM_TYPE_32: + if (av.ul > 0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)av.ul; + break; + case PM_TYPE_U32: + oval->ul = (__uint32_t)av.ul; + break; + case PM_TYPE_64: + oval->ll = (__int64_t)av.ul; + break; + case PM_TYPE_U64: + oval->ull = (__uint64_t)av.ul; + break; + case PM_TYPE_FLOAT: + oval->f = (float)av.ul; + break; + case PM_TYPE_DOUBLE: + oval->d = (double)av.ul; + break; + default: + sts = PM_ERR_CONV; + } + break; + + /* + * Notes on conversion to FLOAT ... because of the limited + * precision of the mantissa, more than one integer value + * maps to the same floating point value ... hence the + * >= (float)max-int-value style of tests + */ + case PM_TYPE_FLOAT: /* old style insitu encoding */ + switch (otype) { + case PM_TYPE_32: + if ((float)ABS(av.f) >= (float)0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)av.f; + break; + case PM_TYPE_U32: + if (av.f >= (float)((unsigned)0xffffffff)) + sts = PM_ERR_TRUNC; + else if (av.f < 0) + sts = PM_ERR_SIGN; + else + oval->ul = (__uint32_t)av.f; + break; + case PM_TYPE_64: +#if defined(HAVE_CONST_LONGLONG) + if (av.f >= (float)0x7fffffffffffffffLL) + sts = PM_ERR_TRUNC; +#else + if (av.f >= (float)0x7fffffffffffffff) + sts = PM_ERR_TRUNC; +#endif + else + oval->ll = (__int64_t)av.f; + break; + case PM_TYPE_U64: +#if defined(HAVE_CONST_LONGLONG) + if (av.f >= (float)((__uint64_t)0xffffffffffffffffLL)) + sts = PM_ERR_TRUNC; +#else + if (av.f >= (float)((__uint64_t)0xffffffffffffffff)) + sts = PM_ERR_TRUNC; +#endif + else if (av.f < 0) + sts = PM_ERR_SIGN; + else + oval->ull = (__uint64_t)av.f; + break; + case PM_TYPE_FLOAT: + oval->f = av.f; + break; + case PM_TYPE_DOUBLE: + oval->d = (double)av.f; + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_64: + case PM_TYPE_U64: + case PM_TYPE_DOUBLE: + case PM_TYPE_STRING: + case PM_TYPE_AGGREGATE: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + default: + sts = PM_ERR_CONV; + } + } + else if (valfmt == PM_VAL_DPTR || valfmt == PM_VAL_SPTR) { + __int64_t src; + __uint64_t usrc; + double dsrc; + float fsrc; + switch (itype) { + + case PM_TYPE_64: + if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__int64_t) || + (ival->value.pval->vtype != PM_TYPE_64 && + ival->value.pval->vtype != 0)) { + sts = PM_ERR_CONV; + break; + } + avp = (void *)&ival->value.pval->vbuf; + memcpy((void *)&av.ll, avp, sizeof(av.ll)); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf)); + } +#endif + src = av.ll; + switch (otype) { + case PM_TYPE_32: + if (src > 0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)src; + break; + case PM_TYPE_U32: + if (src > (unsigned)0xffffffff) + sts = PM_ERR_TRUNC; + else if (src < 0) + sts = PM_ERR_SIGN; + else + oval->ul = (__uint32_t)src; + break; + case PM_TYPE_64: + oval->ll = src; + break; + case PM_TYPE_U64: + if (src < 0) + sts = PM_ERR_SIGN; + else + oval->ull = (__uint64_t)src; + break; + case PM_TYPE_FLOAT: + oval->f = (float)src; + break; + case PM_TYPE_DOUBLE: + oval->d = (double)src; + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_U64: + if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__uint64_t) || + (ival->value.pval->vtype != PM_TYPE_U64 && + ival->value.pval->vtype != 0)) { + sts = PM_ERR_CONV; + break; + } + avp = (void *)&ival->value.pval->vbuf; + memcpy((void *)&av.ull, avp, sizeof(av.ull)); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf)); + } +#endif + usrc = av.ull; + switch (otype) { + case PM_TYPE_32: + if (usrc > 0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)usrc; + break; + case PM_TYPE_U32: + if (usrc > (unsigned)0xffffffff) + sts = PM_ERR_TRUNC; + else + oval->ul = (__uint32_t)usrc; + break; + case PM_TYPE_64: +#if defined(HAVE_CONST_LONGLONG) + if (usrc > (__int64_t)0x7fffffffffffffffLL) + sts = PM_ERR_TRUNC; +#else + if (usrc > (__int64_t)0x7fffffffffffffff) + sts = PM_ERR_TRUNC; +#endif + else + oval->ll = (__int64_t)usrc; + break; + case PM_TYPE_U64: + oval->ull = usrc; + break; + case PM_TYPE_FLOAT: +#if !defined(HAVE_CAST_U64_DOUBLE) + if (SIGN_64_MASK & usrc) + oval->f = (float)(__int64_t)(usrc & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK; + else + oval->f = (float)(__int64_t)usrc; +#else + oval->f = (float)usrc; +#endif + break; + case PM_TYPE_DOUBLE: +#if !defined(HAVE_CAST_U64_DOUBLE) + if (SIGN_64_MASK & usrc) + oval->d = (double)(__int64_t)(usrc & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK; + else + oval->d = (double)(__int64_t)usrc; +#else + oval->d = (double)usrc; +#endif + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_DOUBLE: + if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(double) || + (ival->value.pval->vtype != PM_TYPE_DOUBLE && + ival->value.pval->vtype != 0)) { + sts = PM_ERR_CONV; + break; + } + avp = (void *)&ival->value.pval->vbuf; + memcpy((void *)&av.d, avp, sizeof(av.d)); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf)); + } +#endif + dsrc = av.d; + switch (otype) { + case PM_TYPE_32: + if (ABS(dsrc) >= (double)0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)dsrc; + break; + case PM_TYPE_U32: + if (dsrc >= (double)((unsigned)0xffffffff)) + sts = PM_ERR_TRUNC; + else if (dsrc < 0) + sts = PM_ERR_SIGN; + else + oval->ul = (__uint32_t)dsrc; + break; + case PM_TYPE_64: +#if defined(HAVE_CONST_LONGLONG) + if (dsrc >= (double)0x7fffffffffffffffLL) + sts = PM_ERR_TRUNC; +#else + if (dsrc >= (double)0x7fffffffffffffff) + sts = PM_ERR_TRUNC; +#endif + else + oval->ll = (__int64_t)dsrc; + break; + case PM_TYPE_U64: +#if defined(HAVE_CONST_LONGLONG) + if (dsrc >= (double)((__uint64_t)0xffffffffffffffffLL)) + sts = PM_ERR_TRUNC; +#else + if (dsrc >= (double)((__uint64_t)0xffffffffffffffff)) + sts = PM_ERR_TRUNC; +#endif + else if (dsrc < 0) + sts = PM_ERR_SIGN; + else + oval->ull = (__uint64_t)dsrc; + break; + case PM_TYPE_FLOAT: + oval->f = (float)dsrc; + break; + case PM_TYPE_DOUBLE: + oval->d = dsrc; + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_FLOAT: /* new style pmValueBlock encoding */ + if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(float) || + (ival->value.pval->vtype != PM_TYPE_FLOAT && + ival->value.pval->vtype != 0)) { + sts = PM_ERR_CONV; + break; + } + avp = (void *)&ival->value.pval->vbuf; + memcpy((void *)&av.f, avp, sizeof(av.f)); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf)); + } +#endif + fsrc = av.f; + switch (otype) { + case PM_TYPE_32: + if ((float)ABS(fsrc) >= (float)0x7fffffff) + sts = PM_ERR_TRUNC; + else + oval->l = (__int32_t)fsrc; + break; + case PM_TYPE_U32: + if (fsrc >= (float)((unsigned)0xffffffff)) + sts = PM_ERR_TRUNC; + else if (fsrc < 0) + sts = PM_ERR_SIGN; + else + oval->ul = (__uint32_t)fsrc; + break; + case PM_TYPE_64: +#if defined(HAVE_CONST_LONGLONG) + if (fsrc >= (float)0x7fffffffffffffffLL) + sts = PM_ERR_TRUNC; +#else + if (fsrc >= (float)0x7fffffffffffffff) + sts = PM_ERR_TRUNC; +#endif + else + oval->ll = (__int64_t)fsrc; + break; + case PM_TYPE_U64: +#if defined(HAVE_CONST_LONGLONG) + if (fsrc >= (float)((__uint64_t)0xffffffffffffffffLL)) + sts = PM_ERR_TRUNC; +#else + if (fsrc >= (float)((__uint64_t)0xffffffffffffffff)) + sts = PM_ERR_TRUNC; +#endif + else if (fsrc < 0) + sts = PM_ERR_SIGN; + else + oval->ull = (__uint64_t)fsrc; + break; + case PM_TYPE_FLOAT: + oval->f = fsrc; + break; + case PM_TYPE_DOUBLE: + oval->d = (float)fsrc; + break; + default: + sts = PM_ERR_CONV; + } + break; + + case PM_TYPE_STRING: + if (ival->value.pval->vtype != PM_TYPE_STRING && + ival->value.pval->vtype != 0) { + sts = PM_ERR_CONV; + break; + } + len = ival->value.pval->vlen - PM_VAL_HDR_SIZE; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + if (ival->value.pval->vbuf[0] == '\0') + vp = "<null>"; + else { + int i; + i = (int)strlen(ival->value.pval->vbuf); + if (i < 38) + snprintf(buf, sizeof(buf), "\"%s\"", ival->value.pval->vbuf); + else + snprintf(buf, sizeof(buf), "\"%34.34s...\"", ival->value.pval->vbuf); + vp = buf; + } + } +#endif + if (otype != PM_TYPE_STRING) { + sts = PM_ERR_CONV; + break; + } + if ((oval->cp = (char *)malloc(len + 1)) == NULL) { + __pmNoMem("pmExtractValue.string", len + 1, PM_FATAL_ERR); + } + memcpy(oval->cp, ival->value.pval->vbuf, len); + oval->cp[len] = '\0'; + break; + + case PM_TYPE_AGGREGATE: + if (ival->value.pval->vtype != PM_TYPE_AGGREGATE && + ival->value.pval->vtype != 0) { + sts = PM_ERR_CONV; + break; + } + len = ival->value.pval->vlen; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + int vlen; + int i; + vlen = ival->value.pval->vlen - PM_VAL_HDR_SIZE; + if (vlen == 0) + snprintf(buf, sizeof(buf), "[len=%d]", vlen); + else { + char *cp; + char *bp; + snprintf(buf, sizeof(buf), "[len=%d]", vlen); + cp = (char *)ival->value.pval->vbuf; + for (i = 0; i < vlen && i < 12; i++) { + bp = &buf[strlen(buf)]; + if ((i % 4) == 0) + snprintf(bp, sizeof(buf) - (bp-buf), " %02x", *cp & 0xff); + else + snprintf(bp, sizeof(buf) - (bp-buf), "%02x", *cp & 0xff); + cp++; + } + if (vlen > 12) { + bp = &buf[strlen(buf)]; + snprintf(bp, sizeof(buf) - (bp-buf), " ..."); + } + } + vp = buf; + } +#endif + if (otype != PM_TYPE_AGGREGATE) { + sts = PM_ERR_CONV; + break; + } + if ((oval->vbp = (pmValueBlock *)malloc(len)) == NULL) { + __pmNoMem("pmExtractValue.aggr", len, PM_FATAL_ERR); + } + memcpy(oval->vbp, ival->value.pval, len); + break; + + case PM_TYPE_32: + case PM_TYPE_U32: + case PM_TYPE_EVENT: + case PM_TYPE_HIGHRES_EVENT: + default: + sts = PM_ERR_CONV; + } + } + else + sts = PM_ERR_CONV; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_VALUE) { + char strbuf[80]; + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, " %s", vp); + fprintf(stderr, " [%s]", pmTypeStr_r(itype, strbuf, sizeof(strbuf))); + if (sts == 0) + fprintf(stderr, " -> %s", pmAtomStr_r(oval, otype, strbuf, sizeof(strbuf))); + else + fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg))); + fprintf(stderr, " [%s]\n", pmTypeStr_r(otype, strbuf, sizeof(strbuf))); + } +#endif + + return sts; +} diff --git a/src/libpcp/src/util.c b/src/libpcp/src/util.c new file mode 100644 index 0000000..f0e212a --- /dev/null +++ b/src/libpcp/src/util.c @@ -0,0 +1,2351 @@ +/* + * General Utility Routines + * + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 2009 Aconex. All Rights Reserved. + * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * Thread-safe notes + * + * pmState - no side-effects, don't bother locking + * + * pmProgname - most likely set in main(), not worth protecting here + * and impossible to capture all the read uses in other places + * + * base (in __pmProcessDataSize) - no real side-effects, don't bother + * locking + */ + +#include <stdarg.h> +#include <sys/stat.h> +#include <inttypes.h> +#include <limits.h> +#include <ctype.h> + +#include "pmapi.h" +#include "impl.h" +#include "pmdbg.h" +#include "internal.h" + +#if defined(HAVE_SYS_TIMES_H) +#include <sys/times.h> +#endif +#if defined(HAVE_SYS_MMAN_H) +#include <sys/mman.h> +#endif +#if defined(HAVE_IEEEFP_H) +#include <ieeefp.h> +#endif +#if defined(HAVE_MATH_H) +#include <math.h> +#endif +#if defined(IS_DARWIN) +#include <sys/sysctl.h> +#include <mach/clock.h> +#include <mach/mach.h> +#endif + +static FILE **filelog; +static int nfilelog; +static int dosyslog; +static int pmState = PM_STATE_APPL; +static int done_exit; + +INTERN char *pmProgname = "pcp"; /* the real McCoy */ + +static int vpmprintf(const char *, va_list); + +/* + * if onoff == 1, logging is to syslog and stderr, else logging is + * just to stderr (this is the default) + */ +void +__pmSyslog(int onoff) +{ + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + dosyslog = onoff; + if (dosyslog) + openlog("pcp", LOG_PID, LOG_DAEMON); + else + closelog(); + PM_UNLOCK(__pmLock_libpcp); +} + +/* + * This is a wrapper around syslog(3C) that writes similar messages to stderr, + * but if __pmSyslog(1) is called, the messages will really go to syslog + */ +void +__pmNotifyErr(int priority, const char *message, ...) +{ + va_list arg; + char *p; + char *level; + time_t now; + + va_start(arg, message); + + time(&now); + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (dosyslog) { + char syslogmsg[2048]; + + vsnprintf(syslogmsg, sizeof(syslogmsg), message, arg); + va_end(arg); + va_start(arg, message); + syslog(priority, "%s", syslogmsg); + } + PM_UNLOCK(__pmLock_libpcp); + + /* + * do the stderr equivalent + */ + + switch (priority) { + case LOG_EMERG : + level = "Emergency"; + break; + case LOG_ALERT : + level = "Alert"; + break; + case LOG_CRIT : + level = "Critical"; + break; + case LOG_ERR : + level = "Error"; + break; + case LOG_WARNING : + level = "Warning"; + break; + case LOG_NOTICE : + level = "Notice"; + break; + case LOG_INFO : + level = "Info"; + break; + case LOG_DEBUG : + level = "Debug"; + break; + default: + level = "???"; + break; + } + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + pmprintf("[%.19s] %s(%" FMT_PID ") %s: ", ctime(&now), pmProgname, getpid(), level); + PM_UNLOCK(__pmLock_libpcp); + vpmprintf(message, arg); + va_end(arg); + /* trailing \n if needed */ + for (p = (char *)message; *p; p++) + ; + if (p == message || p[-1] != '\n') + pmprintf("\n"); + pmflush(); +} + +static void +logheader(const char *progname, FILE *log, const char *act) +{ + time_t now; + char host[MAXHOSTNAMELEN]; + + setlinebuf(log); /* line buffering for log files */ + gethostname(host, MAXHOSTNAMELEN); + host[MAXHOSTNAMELEN-1] = '\0'; + time(&now); + PM_LOCK(__pmLock_libpcp); + fprintf(log, "Log for %s on %s %s %s\n", progname, host, act, ctime(&now)); + PM_UNLOCK(__pmLock_libpcp); +} + +static void +logfooter(FILE *log, const char *act) +{ + time_t now; + + time(&now); + PM_LOCK(__pmLock_libpcp); + fprintf(log, "\nLog %s %s", act, ctime(&now)); + PM_UNLOCK(__pmLock_libpcp); +} + +static void +logonexit(void) +{ + int i; + + PM_LOCK(__pmLock_libpcp); + if (++done_exit != 1) { + PM_UNLOCK(__pmLock_libpcp); + return; + } + + for (i = 0; i < nfilelog; i++) + logfooter(filelog[i], "finished"); + + PM_UNLOCK(__pmLock_libpcp); +} + +/* common code shared by __pmRotateLog and __pmOpenLog */ +static FILE * +logreopen(const char *progname, const char *logname, FILE *oldstream, + int *status) +{ + int oldfd; + int dupoldfd; + FILE *dupoldstream = oldstream; + + /* + * Do our own version of freopen() because the standard one closes the + * original stream BEFORE trying to open the new one. Once it's gone, + * there's no way to get the closed stream back if the open fails. + */ + + fflush(oldstream); + *status = 0; /* set to one if all this works ... */ + oldfd = fileno(oldstream); + if ((dupoldfd = dup(oldfd)) >= 0) { + /* + * try to remove the file first ... don't bother if this fails, + * but if it succeeds, we at least get a chance to define the + * owner and mode, rather than inheriting this from an existing + * writeable file ... really only a problem when called as with + * uid == 0, e.g. from pmcd(1). + */ + unlink(logname); + + oldstream = freopen(logname, "w", oldstream); + if (oldstream == NULL) { + int save_error = oserror(); /* need for error message */ + + close(oldfd); + if (dup(dupoldfd) != oldfd) { + /* fd juggling failed! */ + oldstream = NULL; + } + else { + /* oldfd now re-instated as at entry */ + oldstream = fdopen(oldfd, "w"); + } + if (oldstream == NULL) { + /* serious trouble ... choose least obnoxious alternative */ + if (dupoldstream == stderr) + oldstream = fdopen(fileno(stdout), "w"); + else + oldstream = fdopen(fileno(stderr), "w"); + } + if (oldstream != NULL) { + /* + * oldstream was NULL, but recovered so now fixup + * input oldstream ... this is potentially dangerous, + * but we're relying on + * (a) fflush(oldstream) on entry flushes buffers + * (b) fdopen() leaves new oldstream initialized + * (c) caller knows nothing about "new" oldstream + * and is never going to fclose() it, so only + * fclose() will come at exit() and should be + * benign (except possibly for a free() of an + * already free()'d buffer) + */ + *dupoldstream = *oldstream; /* struct copy */ + /* put oldstream back for return value */ + oldstream = dupoldstream; + } + pmprintf("%s: cannot open log \"%s\" for writing : %s\n", + progname, logname, strerror(save_error)); + pmflush(); + } + else { + /* yippee */ + *status = 1; + } + close(dupoldfd); + } + else { + pmprintf("%s: cannot redirect log output to \"%s\": %s\n", + progname, logname, strerror(errno)); + pmflush(); + } + return oldstream; +} + +FILE * +__pmOpenLog(const char *progname, const char *logname, FILE *oldstream, + int *status) +{ + oldstream = logreopen(progname, logname, oldstream, status); + PM_INIT_LOCKS(); + logheader(progname, oldstream, "started"); + + PM_LOCK(__pmLock_libpcp); + nfilelog++; + if (nfilelog == 1) + atexit(logonexit); + + filelog = (FILE **)realloc(filelog, nfilelog * sizeof(FILE *)); + if (filelog == NULL) { + __pmNoMem("__pmOpenLog", nfilelog * sizeof(FILE *), PM_FATAL_ERR); + } + filelog[nfilelog-1] = oldstream; + + PM_UNLOCK(__pmLock_libpcp); + return oldstream; +} + +FILE * +__pmRotateLog(const char *progname, const char *logname, FILE *oldstream, + int *status) +{ + int i; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + for (i = 0; i < nfilelog; i++) { + if (oldstream == filelog[i]) { + logfooter(oldstream, "rotated"); /* old */ + oldstream = logreopen(progname, logname, oldstream, status); + logheader(progname, oldstream, "rotated"); /* new */ + filelog[i] = oldstream; + break; + } + } + PM_UNLOCK(__pmLock_libpcp); + return oldstream; +} + +/* pmID -> string, max length is 20 bytes */ +char * +pmIDStr_r(pmID pmid, char *buf, int buflen) +{ + __pmID_int* p = (__pmID_int*)&pmid; + if (pmid == PM_ID_NULL) + snprintf(buf, buflen, "%s", "PM_ID_NULL"); + else if (p->domain == DYNAMIC_PMID && p->item == 0) + /* + * this PMID represents the base of a dynamic subtree in the PMNS + * ... identified by setting the domain field to the reserved + * value DYNAMIC_PMID and storing the real domain of the PMDA + * that can enumerate the subtree in the cluster field, while + * the item field is not used + */ + snprintf(buf, buflen, "%d.*.*", p->cluster); + else + snprintf(buf, buflen, "%d.%d.%d", p->domain, p->cluster, p->item); + return buf; +} + +const char * +pmIDStr(pmID pmid) +{ + static char idbuf[20]; + pmIDStr_r(pmid, idbuf, sizeof(idbuf)); + return idbuf; +} + +/* pmInDom -> string, max length is 20 bytes */ +char * +pmInDomStr_r(pmInDom indom, char *buf, int buflen) +{ + __pmInDom_int* p = (__pmInDom_int*)&indom; + if (indom == PM_INDOM_NULL) + snprintf(buf, buflen, "%s", "PM_INDOM_NULL"); + else + snprintf(buf, buflen, "%d.%d", p->domain, p->serial); + return buf; +} + +const char * +pmInDomStr(pmInDom indom) +{ + static char indombuf[20]; + pmInDomStr_r(indom, indombuf, sizeof(indombuf)); + return indombuf; +} + +/* double -> string, max length is 8 bytes */ +char * +pmNumberStr_r(double value, char *buf, int buflen) +{ + if (value >= 0.0) { + if (value >= 999995000000000.0) + snprintf(buf, buflen, " inf? "); + else if (value >= 999995000000.0) + snprintf(buf, buflen, "%6.2fT", value / 1000000000000.0); + else if (value >= 999995000.0) + snprintf(buf, buflen, "%6.2fG", value / 1000000000.0); + else if (value >= 999995.0) + snprintf(buf, buflen, "%6.2fM", value / 1000000.0); + else if (value >= 999.995) + snprintf(buf, buflen, "%6.2fK", value / 1000.0); + else if (value >= 0.005) + snprintf(buf, buflen, "%6.2f ", value); + else + snprintf(buf, buflen, "%6.2f ", 0.0); + } + else { + if (value <= -99995000000000.0) + snprintf(buf, buflen, "-inf? "); + else if (value <= -99995000000.0) + snprintf(buf, buflen, "%6.2fT", value / 1000000000000.0); + else if (value <= -99995000.0) + snprintf(buf, buflen, "%6.2fG", value / 1000000000.0); + else if (value <= -99995.0) + snprintf(buf, buflen, "%6.2fM", value / 1000000.0); + else if (value <= -99.995) + snprintf(buf, buflen, "%6.2fK", value / 1000.0); + else if (value <= -0.005) + snprintf(buf, buflen, "%6.2f ", value); + else + snprintf(buf, buflen, "%6.2f ", 0.0); + } + return buf; +} + +const char * +pmNumberStr(double value) +{ + static char nbuf[8]; + pmNumberStr_r(value, nbuf, sizeof(nbuf)); + return nbuf; +} + +/* flags -> string, max length is 64 bytes */ +char * +pmEventFlagsStr_r(int flags, char *buf, int buflen) +{ + /* + * buffer needs to be long enough to hold each flag name + * (excluding missed) plus the separation commas, so + * point,start,end,id,parent (even though it is unlikely that + * both start and end would be set for the one event record) + */ + int started = 0; + + if (flags & PM_EVENT_FLAG_MISSED) + return strcpy(buf, "missed"); + + buf[0] = '\0'; + if (flags & PM_EVENT_FLAG_POINT) { + if (started++) strcat(buf, ","); + strcat(buf, "point"); + } + if (flags & PM_EVENT_FLAG_START) { + if (started++) strcat(buf, ","); + strcat(buf, "start"); + } + if (flags & PM_EVENT_FLAG_END) { + if (started++) strcat(buf, ","); + strcat(buf, "end"); + } + if (flags & PM_EVENT_FLAG_ID) { + if (started++) strcat(buf, ","); + strcat(buf, "id"); + } + if (flags & PM_EVENT_FLAG_PARENT) { + if (started++) strcat(buf, ","); + strcat(buf, "parent"); + } + return buf; +} + +const char * +pmEventFlagsStr(int flags) +{ + static char ebuf[64]; + pmEventFlagsStr_r(flags, ebuf, sizeof(ebuf)); + return ebuf; +} + +/* + * Several PMAPI interfaces allocate a list of strings into a buffer + * pointed to by (char **) which can be safely freed simply by + * freeing the pointer to the buffer. + * + * Here we provide some functions for manipulating these lists. + */ + +/* Add the given item to the list, which may be empty. */ +int +__pmStringListAdd(char *item, int numElements, char ***list) +{ + size_t ptrSize; + size_t dataSize; + size_t newSize; + char *initialString; + char *finalString; + char **newList; + int i; + + /* Compute the sizes of the pointers and data for the current list. */ + if (*list != NULL) { + ptrSize = numElements * sizeof(**list); + initialString = **list; + finalString = (*list)[numElements - 1]; + dataSize = (finalString + strlen(finalString) + 1) - initialString; + } + else { + ptrSize = 0; + dataSize = 0; + } + + /* + * Now allocate a new buffer for the expanded list. + * We need room for a new pointer and for the new item. + */ + newSize = ptrSize + sizeof(**list) + dataSize + strlen(item) + 1; + newList = realloc(*list, newSize); + if (newList == NULL) { + __pmNoMem("__pmStringListAdd", newSize, PM_FATAL_ERR); + } + + /* + * Shift the existing data to make room for the new pointer and + * recompute each existing pointer. + */ + finalString = (char *)(newList + numElements + 1); + if (dataSize != 0) { + initialString = (char *)(newList + numElements); + memmove(finalString, initialString, dataSize); + for (i = 0; i < numElements; ++i) { + newList[i] = finalString; + finalString += strlen(finalString) + 1; + } + } + + /* Now add the new item. */ + newList[numElements] = finalString; + strcpy(finalString, item); + + *list = newList; + return numElements + 1; +} + +/* Search for the given string in the given string list. */ +char * +__pmStringListFind(const char *item, int numElements, char **list) +{ + int e; + + if (list == NULL) + return NULL; /* no list to search */ + + for (e = 0; e < numElements; ++e) { + if (strcmp(item, list[e]) == 0) + return list[e]; + } + + /* Not found. */ + return NULL; +} + +/* + * Save/restore global debugging flag, without locking. + * Needed since tracing PDUs really messes __pmDump*() routines + * up when pmNameInDom is called internally. + */ +static int +save_debug(void) +{ + int saved = pmDebug; + pmDebug = 0; + return saved; +} + +static void +restore_debug(int saved) +{ + pmDebug = saved; +} + +static void +dump_valueset(FILE *f, pmValueSet *vsp) +{ + pmDesc desc; + char errmsg[PM_MAXERRMSGLEN]; + char strbuf[20]; + char *pmid, *p; + int have_desc = 1; + int n, j; + + pmid = pmIDStr_r(vsp->pmid, strbuf, sizeof(strbuf)); + if ((n = pmNameID(vsp->pmid, &p)) < 0) + fprintf(f," %s (%s):", pmid, "<noname>"); + else { + fprintf(f," %s (%s):", pmid, p); + free(p); + } + if (vsp->numval == 0) { + fprintf(f, " No values returned!\n"); + return; + } + if (vsp->numval < 0) { + fprintf(f, " %s\n", pmErrStr_r(vsp->numval, errmsg, sizeof(errmsg))); + return; + } + if (__pmGetInternalState() == PM_STATE_PMCS || + pmLookupDesc(vsp->pmid, &desc) < 0) { + /* don't know, so punt on the most common cases */ + desc.indom = PM_INDOM_NULL; + have_desc = 0; + } + + fprintf(f, " numval: %d", vsp->numval); + fprintf(f, " valfmt: %d vlist[]:\n", vsp->valfmt); + for (j = 0; j < vsp->numval; j++) { + pmValue *vp = &vsp->vlist[j]; + if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) { + fprintf(f," inst [%d", vp->inst); + if (have_desc && + pmNameInDom(desc.indom, vp->inst, &p) >= 0) { + fprintf(f, " or \"%s\"]", p); + free(p); + } + else { + fprintf(f, " or ???]"); + } + fputc(' ', f); + } + else + fprintf(f, " "); + fprintf(f, "value "); + + if (have_desc) + pmPrintValue(f, vsp->valfmt, desc.type, vp, 1); + else { + if (vsp->valfmt == PM_VAL_INSITU) + pmPrintValue(f, vsp->valfmt, PM_TYPE_UNKNOWN, vp, 1); + else + pmPrintValue(f, vsp->valfmt, (int)vp->value.pval->vtype, vp, 1); + } + fputc('\n', f); + } +} + +void +__pmDumpResult(FILE *f, const pmResult *resp) +{ + int i, saved; + + saved = save_debug(); + fprintf(f, "pmResult dump from " PRINTF_P_PFX "%p timestamp: %d.%06d ", + resp, (int)resp->timestamp.tv_sec, (int)resp->timestamp.tv_usec); + __pmPrintStamp(f, &resp->timestamp); + fprintf(f, " numpmid: %d\n", resp->numpmid); + for (i = 0; i < resp->numpmid; i++) + dump_valueset(f, resp->vset[i]); + restore_debug(saved); +} + +void +__pmDumpHighResResult(FILE *f, const pmHighResResult *hresp) +{ + int i, saved; + + saved = save_debug(); + fprintf(f, "pmHighResResult dump from " PRINTF_P_PFX "%p timestamp: %d.%09d ", + hresp, (int)hresp->timestamp.tv_sec, (int)hresp->timestamp.tv_nsec); + __pmPrintHighResStamp(f, &hresp->timestamp); + fprintf(f, " numpmid: %d\n", hresp->numpmid); + for (i = 0; i < hresp->numpmid; i++) + dump_valueset(f, hresp->vset[i]); + restore_debug(saved); +} + +static void +print_event_summary(FILE *f, const pmValue *val, int highres) +{ + struct timespec tsstamp; + struct timeval tvstamp; + __pmTimespec *tsp; + __pmTimeval *tvp; + unsigned int flags; + size_t size; + char *base; + int nparams; + int nrecords; + int nmissed = 0; + int r; /* records */ + int p; /* parameters in a record ... */ + + if (highres) { + pmHighResEventArray *hreap = (pmHighResEventArray *)val->value.pval; + nrecords = hreap->ea_nrecords; + base = (char *)&hreap->ea_record[0]; + tsp = (__pmTimespec *)base; + tsstamp.tv_sec = tsp->tv_sec; + tsstamp.tv_nsec = tsp->tv_nsec; + } + else { + pmEventArray *eap = (pmEventArray *)val->value.pval; + nrecords = eap->ea_nrecords; + base = (char *)&eap->ea_record[0]; + tvp = (__pmTimeval *)base; + tvstamp.tv_sec = tvp->tv_sec; + tvstamp.tv_usec = tvp->tv_usec; + } + + /* walk packed event record array */ + for (r = 0; r < nrecords-1; r++) { + if (highres) { + pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base; + size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) + + sizeof(hrerp->er_nparams); + flags = hrerp->er_flags; + nparams = hrerp->er_nparams; + } + else { + pmEventRecord *erp = (pmEventRecord *)base; + size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + + sizeof(erp->er_nparams); + flags = erp->er_flags; + nparams = erp->er_nparams; + } + + if (flags & PM_EVENT_FLAG_MISSED) { + nmissed += nparams; + continue; + } + + base += size; + for (p = 0; p < nparams; p++) { + pmEventParameter *epp = (pmEventParameter *)base; + base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len); + } + } + fprintf(f, "[%d event record", nrecords); + if (nrecords != 1) + fputc('s', f); + if (nmissed > 0) + fprintf(f, " (%d missed)", nmissed); + if (nrecords > 0) { + fprintf(f, " timestamp"); + if (nrecords > 1) + fputc('s', f); + fputc(' ', f); + + if (highres) + __pmPrintHighResStamp(f, &tsstamp); + else + __pmPrintStamp(f, &tvstamp); + + if (nrecords > 1) { + fprintf(f, "..."); + if (highres) { + tsp = (__pmTimespec *)base; + tsstamp.tv_sec = tsp->tv_sec; + tsstamp.tv_nsec = tsp->tv_nsec; + __pmPrintHighResStamp(f, &tsstamp); + } + else { + tvp = (__pmTimeval *)base; + tvstamp.tv_sec = tvp->tv_sec; + tvstamp.tv_usec = tvp->tv_usec; + __pmPrintStamp(f, &tvstamp); + } + } + } + fputc(']', f); +} + +/* Print single pmValue. */ +void +pmPrintValue(FILE *f, /* output stream */ + int valfmt, /* from pmValueSet */ + int type, /* from pmDesc */ + const pmValue *val, /* value to print */ + int minwidth) /* output is at least this wide */ +{ + pmAtomValue a; + int i; + int n; + char *p; + int sts; + + if (type != PM_TYPE_UNKNOWN && + type != PM_TYPE_EVENT && + type != PM_TYPE_HIGHRES_EVENT) { + sts = pmExtractValue(valfmt, val, type, &a, type); + if (sts < 0) + type = PM_TYPE_UNKNOWN; + } + + switch (type) { + case PM_TYPE_32: + fprintf(f, "%*i", minwidth, a.l); + break; + + case PM_TYPE_U32: + fprintf(f, "%*u", minwidth, a.ul); + break; + + case PM_TYPE_64: + fprintf(f, "%*"PRIi64, minwidth, a.ll); + break; + + case PM_TYPE_U64: + fprintf(f, "%*"PRIu64, minwidth, a.ull); + break; + + case PM_TYPE_FLOAT: + fprintf(f, "%*.8g", minwidth, (double)a.f); + break; + + case PM_TYPE_DOUBLE: + fprintf(f, "%*.16g", minwidth, a.d); + break; + + case PM_TYPE_STRING: + n = (int)strlen(a.cp) + 2; + while (n < minwidth) { + fputc(' ', f); + n++; + } + fprintf(f, "\"%s\"", a.cp); + free(a.cp); + break; + + case PM_TYPE_AGGREGATE: + case PM_TYPE_UNKNOWN: + if (valfmt == PM_VAL_INSITU) { + float *fp = (float *)&val->value.lval; + __uint32_t *ip = (__uint32_t *)&val->value.lval; + int fp_bad = 0; + fprintf(f, "%*u", minwidth, *ip); +#ifdef HAVE_FPCLASSIFY + fp_bad = fpclassify(*fp) == FP_NAN; +#else +#ifdef HAVE_ISNANF + fp_bad = isnanf(*fp); +#endif +#endif + if (!fp_bad) + fprintf(f, " %*.8g", minwidth, (double)*fp); + if (minwidth > 2) + minwidth -= 2; + fprintf(f, " 0x%*x", minwidth, val->value.lval); + } + else { + int string; + int done = 0; + if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(__uint64_t)) { + __uint64_t tmp; + memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp)); + fprintf(f, "%*"PRIu64, minwidth, tmp); + done = 1; + } + if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(double)) { + double tmp; + int fp_bad = 0; + memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp)); +#ifdef HAVE_FPCLASSIFY + fp_bad = fpclassify(tmp) == FP_NAN; +#else +#ifdef HAVE_ISNAN + fp_bad = isnan(tmp); +#endif +#endif + if (!fp_bad) { + if (done) fputc(' ', f); + fprintf(f, "%*.16g", minwidth, tmp); + done = 1; + } + } + if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(float)) { + float tmp; + int fp_bad = 0; + memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp)); +#ifdef HAVE_FPCLASSIFY + fp_bad = fpclassify(tmp) == FP_NAN; +#else +#ifdef HAVE_ISNANF + fp_bad = isnanf(tmp); +#endif +#endif + if (!fp_bad) { + if (done) fputc(' ', f); + fprintf(f, "%*.8g", minwidth, (double)tmp); + done = 1; + } + } + if (val->value.pval->vlen < PM_VAL_HDR_SIZE) + fprintf(f, "pmPrintValue: negative length (%d) for aggregate value?", + (int)val->value.pval->vlen - PM_VAL_HDR_SIZE); + else { + string = 1; + for (n = 0; n < val->value.pval->vlen - PM_VAL_HDR_SIZE; n++) { + if (!isprint((int)val->value.pval->vbuf[n])) { + string = 0; + break; + } + } + if (string) { + if (done) fputc(' ', f); + n = (int)val->value.pval->vlen - PM_VAL_HDR_SIZE + 2; + while (n < minwidth) { + fputc(' ', f); + n++; + } + n = (int)val->value.pval->vlen - PM_VAL_HDR_SIZE; + fprintf(f, "\"%*.*s\"", n, n, val->value.pval->vbuf); + done = 1; + } + n = 2 * (val->value.pval->vlen - PM_VAL_HDR_SIZE) + 2; + while (n < minwidth) { + fputc(' ', f); + n++; + } + if (done) fputc(' ', f); + fputc('[', f); + p = &val->value.pval->vbuf[0]; + for (i = 0; i < val->value.pval->vlen - PM_VAL_HDR_SIZE; i++) { + fprintf(f, "%02x", *p & 0xff); + p++; + } + fputc(']', f); + } + } + if (type != PM_TYPE_UNKNOWN) + free(a.vbp); + break; + + case PM_TYPE_EVENT: /* not much we can do about minwidth */ + case PM_TYPE_HIGHRES_EVENT: + if (valfmt == PM_VAL_INSITU) { + /* + * Special case for pmlc/pmlogger where PMLC_SET_*() macros + * used to set control requests / state in the lval field + * and the pval does not really contain a valid event record + * Code here comes from PrintState() in actions.c from pmlc. + */ + fputs("[pmlc control ", f); + fputs(PMLC_GET_MAND(val->value.lval) ? "mand " : "adv ", f); + fputs(PMLC_GET_ON(val->value.lval) ? "on " : "off ", f); + if (PMLC_GET_INLOG(val->value.lval)) + fputs(PMLC_GET_AVAIL(val->value.lval) ? " " : "na ", f); + else + fputs("nl ", f); + fprintf(f, "%d]", PMLC_GET_DELTA(val->value.lval)); + } + else + print_event_summary(f, val, type != PM_TYPE_EVENT); + break; + + case PM_TYPE_NOSUPPORT: + fprintf(f, "pmPrintValue: bogus value, metric Not Supported\n"); + break; + + default: + fprintf(f, "pmPrintValue: unknown value type=%d\n", type); + } +} + +void +__pmNoMem(const char *where, size_t size, int fatal) +{ + char errmsg[PM_MAXERRMSGLEN]; + __pmNotifyErr(fatal ? LOG_ERR : LOG_WARNING, + "%s: malloc(%d) failed: %s", + where, (int)size, osstrerror_r(errmsg, sizeof(errmsg))); + if (fatal) + exit(1); +} + +/* + * this one is used just below the PMAPI to convert platform errors + * into more appropriate PMAPI error codes + */ +int +__pmMapErrno(int sts) +{ + if (sts == -EBADF || sts == -EPIPE) + sts = PM_ERR_IPC; +#ifdef IS_MINGW + else if (sts == -EINVAL) + sts = PM_ERR_IPC; +#endif + return sts; +} + +int +__pmGetTimespec(struct timespec *ts) +{ +#if defined(IS_DARWIN) + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; + return 0; +#elif defined(HAVE_CLOCK_GETTIME) + return clock_gettime(CLOCK_REALTIME, ts); +#else +#warning "No high resolution timestamp support on this platform" + struct timeval tv; + int sts; + + if ((sts = gettimeofday(&tv, NULL)) == 0) { + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; + } + return sts; +#endif +} + +/* + * difference for two on the internal timestamps + */ +double +__pmTimevalSub(const __pmTimeval *ap, const __pmTimeval *bp) +{ + return ap->tv_sec - bp->tv_sec + (double)(ap->tv_usec - bp->tv_usec)/1000000.0; +} + +/* + * print timeval timestamp in HH:MM:SS.XXX format + */ +void +__pmPrintStamp(FILE *f, const struct timeval *tp) +{ + struct tm tmp; + time_t now; + + now = (time_t)tp->tv_sec; + pmLocaltime(&now, &tmp); + fprintf(f, "%02d:%02d:%02d.%03d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(tp->tv_usec/1000)); +} + +/* + * print high resolution timestamp in HH:MM:SS.XXXXXXXXX format + */ +void +__pmPrintHighResStamp(FILE *f, const struct timespec *tp) +{ + struct tm tmp; + time_t now; + + now = (time_t)tp->tv_sec; + pmLocaltime(&now, &tmp); + fprintf(f, "%02d:%02d:%02d.%09d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(tp->tv_nsec)); +} + +/* + * print __pmTimeval timestamp in HH:MM:SS.XXX format + * (__pmTimeval variant used in PDUs, archives and internally) + */ +void +__pmPrintTimeval(FILE *f, const __pmTimeval *tp) +{ + struct tm tmp; + time_t now; + + now = (time_t)tp->tv_sec; + pmLocaltime(&now, &tmp); + fprintf(f, "%02d:%02d:%02d.%03d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, tp->tv_usec/1000); +} + +/* + * print __pmTimespec timestamp in HH:MM:SS.XXXXXXXXX format + * (__pmTimespec variant used in events, archives and internally) + */ +void +__pmPrintTimespec(FILE *f, const __pmTimespec *tp) +{ + struct tm tmp; + time_t now; + + now = (time_t)tp->tv_sec; + pmLocaltime(&now, &tmp); + fprintf(f, "%02d:%02d:%02d.%09ld", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (long)tp->tv_nsec); +} + +/* + * descriptor + */ +void +__pmPrintDesc(FILE *f, const pmDesc *desc) +{ + const char *type; + const char *sem; + static const char *unknownVal = "???"; + const char *units; + char strbuf[60]; + + if (desc->type == PM_TYPE_NOSUPPORT) { + fprintf(f, " Data Type: Not Supported\n"); + return; + } + + switch (desc->type) { + case PM_TYPE_32: + type = "32-bit int"; + break; + case PM_TYPE_U32: + type = "32-bit unsigned int"; + break; + case PM_TYPE_64: + type = "64-bit int"; + break; + case PM_TYPE_U64: + type = "64-bit unsigned int"; + break; + case PM_TYPE_FLOAT: + type = "float"; + break; + case PM_TYPE_DOUBLE: + type = "double"; + break; + case PM_TYPE_STRING: + type = "string"; + break; + case PM_TYPE_AGGREGATE: + type = "aggregate"; + break; + case PM_TYPE_AGGREGATE_STATIC: + type = "static aggregate"; + break; + case PM_TYPE_EVENT: + type = "event record array"; + break; + case PM_TYPE_HIGHRES_EVENT: + type = "highres event record array"; + break; + default: + type = unknownVal; + break; + } + fprintf(f, " Data Type: %s", type); + if (type == unknownVal) + fprintf(f, " (%d)", desc->type); + + fprintf(f," InDom: %s 0x%x\n", pmInDomStr_r(desc->indom, strbuf, sizeof(strbuf)), desc->indom); + + switch (desc->sem) { + case PM_SEM_COUNTER: + sem = "counter"; + break; + case PM_SEM_INSTANT: + sem = "instant"; + break; + case PM_SEM_DISCRETE: + sem = "discrete"; + break; + default: + sem = unknownVal; + break; + } + + fprintf(f, " Semantics: %s", sem); + if (sem == unknownVal) + fprintf(f, " (%d)", desc->sem); + + fprintf(f, " Units: "); + units = pmUnitsStr_r(&desc->units, strbuf, sizeof(strbuf)); + if (*units == '\0') + fprintf(f, "none\n"); + else + fprintf(f, "%s\n", units); +} + +/* + * print times between events + */ +void +__pmEventTrace_r(const char *event, int *first, double *sum, double *last) +{ +#ifdef PCP_DEBUG + struct timeval tv; + double now; + + __pmtimevalNow(&tv); + now = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0; + if (*first) { + *first = 0; + *sum = 0; + *last = now; + } + *sum += now - *last; + fprintf(stderr, "%s: +%4.2f = %4.2f -> %s\n", + pmProgname, now-*last, *sum, event); + *last = now; +#endif +} + +void +__pmEventTrace(const char *event) +{ +#ifdef PCP_DEBUG + static double last; + static double sum; + static int first = 1; + + __pmEventTrace_r(event, &first, &sum, &last); +#endif +} + +int +__pmParseDebug(const char *spec) +{ +#ifdef PCP_DEBUG + int val = 0; + int tmp; + const char *p; + char *pend; + int i; + + for (p = spec; *p; ) { + tmp = (int)strtol(p, &pend, 10); + if (tmp == -1) + /* special case ... -1 really means set all the bits! */ + tmp = INT_MAX; + if (*pend == '\0') { + val |= tmp; + break; + } + else if (*pend == ',') { + val |= tmp; + p = pend + 1; + } + else { + pend = strchr(p, ','); + if (pend != NULL) + *pend = '\0'; + + if (strcasecmp(p, "ALL") == 0) { + val |= INT_MAX; + if (pend != NULL) { + *pend = ','; + p = pend + 1; + } + else + p = ""; /* force termination of outer loop */ + break; + } + + for (i = 0; i < num_debug; i++) { + if (strcasecmp(p, debug_map[i].name) == 0) { + val |= debug_map[i].bit; + if (pend != NULL) { + *pend = ','; + p = pend + 1; + } + else + p = ""; /* force termination of outer loop */ + break; + } + } + + if (i == num_debug) { + if (pend != NULL) + *pend = ','; + return PM_ERR_CONV; + } + } + } + + return val; +#else + return PM_ERR_NYI; +#endif +} + +int +__pmGetInternalState(void) +{ + return pmState; +} + +void +__pmSetInternalState(int state) +{ + pmState = state; +} + + +/* + * GUI output option + */ + +#define MSGBUFLEN 256 +static FILE *fptr = NULL; +static int msgsize = 0; +static char *fname; /* temporary file name for buffering errors */ +static char *ferr; /* error output filename from PCP_STDERR */ + +#define PM_QUERYERR -1 +#define PM_USEDIALOG 0 +#define PM_USESTDERR 1 +#define PM_USEFILE 2 + +static int +pmfstate(int state) +{ + static int errtype = -1; + + if (state > PM_QUERYERR) + errtype = state; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (errtype == PM_QUERYERR) { + errtype = PM_USESTDERR; + if ((ferr = getenv("PCP_STDERR")) != NULL) { + if (strcasecmp(ferr, "DISPLAY") == 0) { + char * xconfirm = pmGetConfig("PCP_XCONFIRM_PROG"); + if (access(__pmNativePath(xconfirm), X_OK) < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s: using stderr - cannot access %s: %s\n", + pmProgname, xconfirm, osstrerror_r(errmsg, sizeof(errmsg))); + } + else + errtype = PM_USEDIALOG; + } + else if (strcmp(ferr, "") != 0) + errtype = PM_USEFILE; + } + } + PM_UNLOCK(__pmLock_libpcp); + return errtype; +} + +static int +vpmprintf(const char *msg, va_list arg) +{ + int lsize = 0; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (fptr == NULL && msgsize == 0) { /* create scratch file */ + int fd = -1; + char *tmpdir = pmGetConfig("PCP_TMPFILE_DIR"); + + if (tmpdir[0] != '\0') { + mode_t cur_umask; + + /* + * PCP_TMPFILE_DIR found in the configuration/environment, + * otherwise fall through to the stderr case + */ + +#if HAVE_MKSTEMP + fname = (char *)malloc(MAXPATHLEN+1); + if (fname == NULL) goto fail; + snprintf(fname, MAXPATHLEN, "%s/pcp-XXXXXX", tmpdir); + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + fd = mkstemp(fname); + umask(cur_umask); +#else + fname = tempnam(tmpdir, "pcp-"); + if (fname == NULL) goto fail; + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + fd = open(fname, O_RDWR|O_APPEND|O_CREAT|O_EXCL, 0600); + umask(cur_umask); +#endif /* HAVE_MKSTEMP */ + + if (fd < 0) goto fail; + if ((fptr = fdopen(fd, "a")) == NULL) { + char errmsg[PM_MAXERRMSGLEN]; +fail: + if (fname != NULL) { + fprintf(stderr, "%s: vpmprintf: failed to create \"%s\": %s\n", + pmProgname, fname, osstrerror_r(errmsg, sizeof(errmsg))); + unlink(fname); + free(fname); + } + else { + fprintf(stderr, "%s: vpmprintf: failed to create temporary file: %s\n", + pmProgname, osstrerror_r(errmsg, sizeof(errmsg))); + } + fprintf(stderr, "vpmprintf msg:\n"); + if (fd >= 0) + close(fd); + msgsize = -1; + fptr = NULL; + } + } + else + msgsize = -1; + } + + if (msgsize < 0) { + vfprintf(stderr, msg, arg); + fflush(stderr); + lsize = 0; + } + else + msgsize += (lsize = vfprintf(fptr, msg, arg)); + + PM_UNLOCK(__pmLock_libpcp); + return lsize; +} + +int +pmprintf(const char *msg, ...) +{ + va_list arg; + int lsize; + + va_start(arg, msg); + lsize = vpmprintf(msg, arg); + va_end(arg); + return lsize; +} + +int +pmflush(void) +{ + int sts = 0; + int len; + int state; + FILE *eptr = NULL; + char outbuf[MSGBUFLEN]; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if (fptr != NULL && msgsize > 0) { + fflush(fptr); + state = pmfstate(PM_QUERYERR); + if (state == PM_USEFILE) { + if ((eptr = fopen(ferr, "a")) == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "pmflush: cannot append to file '%s' (from " + "$PCP_STDERR): %s\n", ferr, osstrerror_r(errmsg, sizeof(errmsg))); + state = PM_USESTDERR; + } + } + switch (state) { + case PM_USESTDERR: + rewind(fptr); + while ((len = (int)read(fileno(fptr), outbuf, MSGBUFLEN)) > 0) { + sts = write(fileno(stderr), outbuf, len); + if (sts != len) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "pmflush: write() failed: %s\n", + osstrerror_r(errmsg, sizeof(errmsg))); + } + sts = 0; + } + break; + case PM_USEDIALOG: + /* If we're here, it means xconfirm has passed access test */ + snprintf(outbuf, sizeof(outbuf), "%s -file %s -c -B OK -icon info" + " %s -header 'PCP Information' >/dev/null", + __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG")), fname, + (msgsize > 80 ? "-useslider" : "")); + if (system(outbuf) < 0) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "%s: system failed: %s\n", pmProgname, + osstrerror_r(errmsg, sizeof(errmsg))); + sts = -oserror(); + } + break; + case PM_USEFILE: + rewind(fptr); + while ((len = (int)read(fileno(fptr), outbuf, MSGBUFLEN)) > 0) { + sts = write(fileno(eptr), outbuf, len); + if (sts != len) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "pmflush: write() failed: %s\n", + osstrerror_r(errmsg, sizeof(errmsg))); + } + sts = 0; + } + fclose(eptr); + break; + } + fclose(fptr); + fptr = NULL; + unlink(fname); + free(fname); + if (sts >= 0) + sts = msgsize; + } + + msgsize = 0; + + PM_UNLOCK(__pmLock_libpcp); + return sts; +} + +/* + * Set the pmcd client identity as exported by pmcd.client.whoami + * + * Identity is of the form + * hostname (ipaddr) <id> + * + * Assumes you already have a current host context. + */ +int +__pmSetClientId(const char *id) +{ + char *name = "pmcd.client.whoami"; + pmID pmid; + int sts; + pmResult store = { .numpmid = 1 }; + pmValueSet pmvs; + pmValueBlock *pmvb; + char host[MAXHOSTNAMELEN]; + char *ipaddr = NULL; + __pmHostEnt *servInfo; + int vblen; + + if ((sts = pmLookupName(1, &name, &pmid)) < 0) + return sts; + + /* + * Try to obtain the address and the actual host name. + * Compute the vblen as we go. + */ + vblen = 0; + (void)gethostname(host, MAXHOSTNAMELEN); + if ((servInfo = __pmGetAddrInfo(host)) != NULL) { + __pmSockAddr *addr; + void *enumIx = NULL; + char *servInfoName = NULL; + for (addr = __pmHostEntGetSockAddr(servInfo, &enumIx); + addr != NULL; + addr = __pmHostEntGetSockAddr(servInfo, &enumIx)) { + servInfoName = __pmGetNameInfo(addr); + if (servInfoName != NULL) + break; + __pmSockAddrFree(addr); + } + __pmHostEntFree(servInfo); + + /* Did we get a name? */ + if (servInfoName == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmSetClientId: __pmGetNameInfo() failed: %s\n", + osstrerror_r(errmsg, sizeof(errmsg))); + } + else { + strncpy(host, servInfoName, sizeof(host)); + host[sizeof(host) - 1] = '\0'; + free(servInfoName); + } + vblen = strlen(host) + strlen(id) + 2; + + /* Did we get an address? */ + if (addr != NULL) { + ipaddr = __pmSockAddrToString(addr); + __pmSockAddrFree(addr); + if (ipaddr == NULL) { + char errmsg[PM_MAXERRMSGLEN]; + fprintf(stderr, "__pmSetClientId: __pmSockAddrToString() failed: %s\n", + osstrerror_r(errmsg, sizeof(errmsg))); + } + else + vblen += strlen(ipaddr) + 3; + } + } + vblen += strlen(host) + strlen(id) + 2; + + /* build pmResult for pmStore() */ + pmvb = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE+vblen); + if (pmvb == NULL) { + __pmNoMem("__pmSetClientId", PM_VAL_HDR_SIZE+vblen, PM_RECOV_ERR); + return -ENOMEM; + } + pmvb->vtype = PM_TYPE_STRING; + pmvb->vlen = PM_VAL_HDR_SIZE+vblen; + strcpy(pmvb->vbuf, host); + strcat(pmvb->vbuf, " "); + if (ipaddr != NULL) { + strcat(pmvb->vbuf, "("); + strcat(pmvb->vbuf, ipaddr); + strcat(pmvb->vbuf, ") "); + free(ipaddr); + } + strcat(pmvb->vbuf, id); + + pmvs.pmid = pmid; + pmvs.numval = 1; + pmvs.valfmt = PM_VAL_SPTR; + pmvs.vlist[0].value.pval = pmvb; + pmvs.vlist[0].inst = PM_IN_NULL; + + store.vset[0] = &pmvs; + sts = pmStore(&store); + free(pmvb); + return sts; +} + +char * +__pmGetClientId(int argc, char **argv) +{ + char *clientID; + int a, need = 0; + + for (a = 0; a < argc; a++) + need += strlen(argv[a]) + 1; + clientID = (char *)malloc(need); + if (clientID) { + clientID[0] = '\0'; + for (a = 0; a < argc; a++) { + strcat(clientID, argv[a]); + if (a < argc - 1) + strcat(clientID, " "); + } + } + return clientID; +} + +int +__pmSetClientIdArgv(int argc, char **argv) +{ + char *id = __pmGetClientId(argc, argv); + int sts; + + if (id) { + sts = __pmSetClientId(id); + free(id); + return sts; + } + return -ENOMEM; +} + +/* + * Support for C environments that have lame libc implementations. + * All of these developed from first principles, so no 3rd party + * copyright or licensing issues, else used under a licence that + * is compatible with the PCP licence. + */ + +#ifndef HAVE_BASENAME +char * +basename(char *name) +{ + char *p = strrchr(name, '/'); + + if (p == NULL) + return(name); + else + return(p+1); +} +#endif /* HAVE_BASENAME */ + +#ifndef HAVE_DIRNAME +char * +dirname(char *name) +{ + char *p = strrchr(name, '/'); + + if (p == NULL) + return("."); + else { + *p = '\0'; + return(name); + } +} +#endif /* HAVE_DIRNAME */ + +/* + * Create a directory, including all of its path components. + */ +int +__pmMakePath(const char *dir, mode_t mode) +{ + char path[MAXPATHLEN], *p; + int sts; + + sts = access(dir, R_OK|W_OK|X_OK); + if (sts == 0) + return 0; + if (sts < 0 && oserror() != ENOENT) + return -1; + + strncpy(path, dir, sizeof(path)); + path[sizeof(path)-1] = '\0'; + + for (p = path+1; *p != '\0'; p++) { + if (*p == __pmPathSeparator()) { + *p = '\0'; + mkdir2(path, mode); + *p = __pmPathSeparator(); + } + } + return mkdir2(path, mode); +} + +#ifndef HAVE_STRNDUP +char * +strndup(const char *s, size_t n) +{ + char *buf; + + if ((buf = malloc(n + 1)) != NULL) { + strncpy(buf, s, n); + buf[n] = '\0'; + } + return buf; +} +#endif /* HAVE_STRNDUP */ + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c) +{ + char *result; + + if ((result = strchr(s, c)) == NULL) + result = strchr(s, '\0'); + return result; +} +#endif /* HAVE_STRCHRNUL */ + +#ifndef HAVE_SCANDIR +/* + * Scan the directory dirname, building an array of pointers to + * dirent entries using malloc(3C). select() and compare() are + * used to optionally filter and sort directory entries. + */ +int +scandir(const char *dirname, struct dirent ***namelist, + int(*select)(const_dirent *), + int(*compare)(const_dirent **, const_dirent **)) +{ + DIR *dirp; + int n = 0; + struct dirent **names = NULL; + struct dirent *dp; + struct dirent *tp; + + PM_INIT_LOCKS(); + PM_LOCK(__pmLock_libpcp); + if ((dirp = opendir(dirname)) == NULL) + return -1; + + while ((dp = readdir(dirp)) != NULL) { + if (select && (*select)(dp) == 0) + continue; + + n++; + if ((names = (struct dirent **)realloc(names, n * sizeof(dp))) == NULL) { + PM_UNLOCK(__pmLock_libpcp); + closedir(dirp); + return -1; + } + + if ((names[n-1] = tp = (struct dirent *)malloc( + sizeof(*dp)-sizeof(dp->d_name)+strlen(dp->d_name)+1)) == NULL) { + PM_UNLOCK(__pmLock_libpcp); + closedir(dirp); + return -1; + } + + tp->d_ino = dp->d_ino; +#if defined(HAVE_DIRENT_D_OFF) + tp->d_off = dp->d_off; +#else + tp->d_reclen = dp->d_reclen; +#endif + memcpy(tp->d_name, dp->d_name, strlen(dp->d_name)+1); + } + closedir(dirp); + PM_UNLOCK(__pmLock_libpcp); + *namelist = names; + + if (n && compare) + qsort(names, n, sizeof(names[0]), + (int(*)(const void *, const void *))compare); + return n; +} + +/* + * Alphabetical sort for default use + */ +int +alphasort(const_dirent **p, const_dirent **q) +{ + return strcmp((*p)->d_name, (*q)->d_name); +} +#endif /* HAVE_SCANDIR */ + +#ifndef HAVE_POW +/* + * For PCP we have not found a platform yet that needs this, but just + * in case, this implementation comes from + * http://www.netlib.org/fdlibm/e_pow.c + * + * ==================================================== + * Copyright (C) 2003 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +#ifdef HAVE_ENDIAN_H +#include <endian.h> +#else +#ifdef HAVE_SYS_ENDIAN_H +#include <sys/endian.h> +#else +bozo! +#endif +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define __HI(x) *(1+(int*)&x) +#define __LO(x) *(int*)&x +#define __HIp(x) *(1+(int*)x) +#define __LOp(x) *(int*)x +#else +#define __HI(x) *(int*)&x +#define __LO(x) *(1+(int*)&x) +#define __HIp(x) *(int*)x +#define __LOp(x) *(1+(int*)x) +#endif + +/* _pow(x,y) return x**y + * + * n + * Method: Let x = 2 * (1+f) + * 1. Compute and return log2(x) in two pieces: + * log2(x) = w1 + w2, + * where w1 has 53-24 = 29 bit trailing zeros. + * 2. Perform y*log2(x) = n+y' by simulating muti-precision + * arithmetic, where |y'|<=0.5. + * 3. Return x**y = 2**n*exp(y'*log2) + * + * Special cases: + * 1. (anything) ** 0 is 1 + * 2. (anything) ** 1 is itself + * 3. (anything) ** NAN is NAN + * 4. NAN ** (anything except 0) is NAN + * 5. +-(|x| > 1) ** +INF is +INF + * 6. +-(|x| > 1) ** -INF is +0 + * 7. +-(|x| < 1) ** +INF is +0 + * 8. +-(|x| < 1) ** -INF is +INF + * 9. +-1 ** +-INF is NAN + * 10. +0 ** (+anything except 0, NAN) is +0 + * 11. -0 ** (+anything except 0, NAN, odd integer) is +0 + * 12. +0 ** (-anything except 0, NAN) is +INF + * 13. -0 ** (-anything except 0, NAN, odd integer) is +INF + * 14. -0 ** (odd integer) = -( +0 ** (odd integer) ) + * 15. +INF ** (+anything except 0,NAN) is +INF + * 16. +INF ** (-anything except 0,NAN) is +0 + * 17. -INF ** (anything) = -0 ** (-anything) + * 18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer) + * 19. (-anything except 0 and inf) ** (non-integer) is NAN + * + * Accuracy: + * pow(x,y) returns x**y nearly rounded. In particular + * pow(integer,integer) + * always returns the correct integer provided it is + * representable. + * + * Constants : + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +bp[] = {1.0, 1.5,}, +dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ +dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */ +zero = 0.0, +one = 1.0, +two = 2.0, +two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */ +huge = 1.0e300, +tiny = 1.0e-300, + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +L1 = 5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */ +L2 = 4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */ +L3 = 3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */ +L4 = 2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */ +L5 = 2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */ +L6 = 2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */ +P1 = 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */ +P2 = -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */ +P3 = 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ +P4 = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ +P5 = 4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */ +lg2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */ +lg2_h = 6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */ +lg2_l = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */ +ovt = 8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */ +cp = 9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */ +cp_h = 9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */ +cp_l = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/ +ivln2 = 1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */ +ivln2_h = 1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/ +ivln2_l = 1.92596299112661746887e-08; /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/ + +double +pow(double x, double y) +{ + double z,ax,z_h,z_l,p_h,p_l; + double y1,t1,t2,r,s,t,u,v,w; + int i,j,k,yisint,n; + int hx,hy,ix,iy; + unsigned lx,ly; + + hx = __HI(x); lx = __LO(x); + hy = __HI(y); ly = __LO(y); + ix = hx&0x7fffffff; iy = hy&0x7fffffff; + + /* y==zero: x**0 = 1 */ + if((iy|ly)==0) return one; + + /* +-NaN return x+y */ + if(ix > 0x7ff00000 || ((ix==0x7ff00000)&&(lx!=0)) || + iy > 0x7ff00000 || ((iy==0x7ff00000)&&(ly!=0))) + return x+y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if(hx<0) { + if(iy>=0x43400000) yisint = 2; /* even integer y */ + else if(iy>=0x3ff00000) { + k = (iy>>20)-0x3ff; /* exponent */ + if(k>20) { + j = ly>>(52-k); + if((j<<(52-k))==ly) yisint = 2-(j&1); + } else if(ly==0) { + j = iy>>(20-k); + if((j<<(20-k))==iy) yisint = 2-(j&1); + } + } + } + + /* special value of y */ + if(ly==0) { + if (iy==0x7ff00000) { /* y is +-inf */ + if(((ix-0x3ff00000)|lx)==0) + return y - y; /* inf**+-1 is NaN */ + else if (ix >= 0x3ff00000)/* (|x|>1)**+-inf = inf,0 */ + return (hy>=0)? y: zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return (hy<0)?-y: zero; + } + if(iy==0x3ff00000) { /* y is +-1 */ + if(hy<0) return one/x; else return x; + } + if(hy==0x40000000) return x*x; /* y is 2 */ + if(hy==0x3fe00000) { /* y is 0.5 */ + if(hx>=0) /* x >= +0 */ + return sqrt(x); + } + } + + ax = fabs(x); + /* special value of x */ + if(lx==0) { + if(ix==0x7ff00000||ix==0||ix==0x3ff00000){ + z = ax; /*x is +-0,+-inf,+-1*/ + if(hy<0) z = one/z; /* z = (1/|x|) */ + if(hx<0) { + if(((ix-0x3ff00000)|yisint)==0) { + z = (z-z)/(z-z); /* (-1)**non-int is NaN */ + } else if(yisint==1) + z = -z; /* (x<0)**odd = -(|x|**odd) */ + } + return z; + } + } + + n = (hx>>31)+1; + + /* (x<0)**(non-int) is NaN */ + if((n|yisint)==0) return (x-x)/(x-x); + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if((n|(yisint-1))==0) s = -one;/* (-ve)**(odd int) */ + + /* |y| is huge */ + if(iy>0x41e00000) { /* if |y| > 2**31 */ + if(iy>0x43f00000){ /* if |y| > 2**64, must o/uflow */ + if(ix<=0x3fefffff) return (hy<0)? huge*huge:tiny*tiny; + if(ix>=0x3ff00000) return (hy>0)? huge*huge:tiny*tiny; + } + /* over/underflow if x is not close to one */ + if(ix<0x3fefffff) return (hy<0)? s*huge*huge:s*tiny*tiny; + if(ix>0x3ff00000) return (hy>0)? s*huge*huge:s*tiny*tiny; + /* now |1-x| is tiny <= 2**-20, suffice to compute + log(x) by x-x^2/2+x^3/3-x^4/4 */ + t = ax-one; /* t has 20 trailing zeros */ + w = (t*t)*(0.5-t*(0.3333333333333333333333-t*0.25)); + u = ivln2_h*t; /* ivln2_h has 21 sig. bits */ + v = t*ivln2_l-w*ivln2; + t1 = u+v; + __LO(t1) = 0; + t2 = v-(t1-u); + } else { + double ss,s2,s_h,s_l,t_h,t_l; + n = 0; + /* take care subnormal number */ + if(ix<0x00100000) + {ax *= two53; n -= 53; ix = __HI(ax); } + n += ((ix)>>20)-0x3ff; + j = ix&0x000fffff; + /* determine interval */ + ix = j|0x3ff00000; /* normalize ix */ + if(j<=0x3988E) k=0; /* |x|<sqrt(3/2) */ + else if(j<0xBB67A) k=1; /* |x|<sqrt(3) */ + else {k=0;n+=1;ix -= 0x00100000;} + __HI(ax) = ix; + + /* compute ss = s_h+s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5) */ + u = ax-bp[k]; /* bp[0]=1.0, bp[1]=1.5 */ + v = one/(ax+bp[k]); + ss = u*v; + s_h = ss; + __LO(s_h) = 0; + /* t_h=ax+bp[k] High */ + t_h = zero; + __HI(t_h)=((ix>>1)|0x20000000)+0x00080000+(k<<18); + t_l = ax - (t_h-bp[k]); + s_l = v*((u-s_h*t_h)-s_h*t_l); + /* compute log(ax) */ + s2 = ss*ss; + r = s2*s2*(L1+s2*(L2+s2*(L3+s2*(L4+s2*(L5+s2*L6))))); + r += s_l*(s_h+ss); + s2 = s_h*s_h; + t_h = 3.0+s2+r; + __LO(t_h) = 0; + t_l = r-((t_h-3.0)-s2); + /* u+v = ss*(1+...) */ + u = s_h*t_h; + v = s_l*t_h+t_l*ss; + /* 2/(3log2)*(ss+...) */ + p_h = u+v; + __LO(p_h) = 0; + p_l = v-(p_h-u); + z_h = cp_h*p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l*p_h+p_l*cp+dp_l[k]; + /* log2(ax) = (ss+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (double)n; + t1 = (((z_h+z_l)+dp_h[k])+t); + __LO(t1) = 0; + t2 = z_l-(((t1-t)-dp_h[k])-z_h); + } + + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + y1 = y; + __LO(y1) = 0; + p_l = (y-y1)*t1+y*t2; + p_h = y1*t1; + z = p_l+p_h; + j = __HI(z); + i = __LO(z); + if (j>=0x40900000) { /* z >= 1024 */ + if(((j-0x40900000)|i)!=0) /* if z > 1024 */ + return s*huge*huge; /* overflow */ + else { + if(p_l+ovt>z-p_h) return s*huge*huge; /* overflow */ + } + } else if((j&0x7fffffff)>=0x4090cc00 ) { /* z <= -1075 */ + if(((j-0xc090cc00)|i)!=0) /* z < -1075 */ + return s*tiny*tiny; /* underflow */ + else { + if(p_l<=z-p_h) return s*tiny*tiny; /* underflow */ + } + } + /* + * compute 2**(p_h+p_l) + */ + i = j&0x7fffffff; + k = (i>>20)-0x3ff; + n = 0; + if(i>0x3fe00000) { /* if |z| > 0.5, set n = [z+0.5] */ + n = j+(0x00100000>>(k+1)); + k = ((n&0x7fffffff)>>20)-0x3ff; /* new k for n */ + t = zero; + __HI(t) = (n&~(0x000fffff>>k)); + n = ((n&0x000fffff)|0x00100000)>>(20-k); + if(j<0) n = -n; + p_h -= t; + } + t = p_l+p_h; + __LO(t) = 0; + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + z = u+v; + w = v-(z-u); + t = z*z; + t1 = z - t*(P1+t*(P2+t*(P3+t*(P4+t*P5)))); + r = (z*t1)/(t1-two)-(w+z*w); + z = one-(r-z); + j = __HI(z); + j += (n<<20); + if((j>>20)<=0) z = scalbn(z,n); /* subnormal output */ + else __HI(z) += (n<<20); + return s*z; +} +#endif /* HAVE_POW */ + +#define PROCFS_ENTRY_SIZE 40 /* encompass any size of entry for pid */ + +#if defined(IS_DARWIN) /* No procfs on Mac OS X */ +int +__pmProcessExists(pid_t pid) +{ + struct kinfo_proc kp; + size_t len = sizeof(kp); + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) + return 0; + return (len > 0); +} +#elif defined(IS_FREEBSD) +int +__pmProcessExists(pid_t pid) +{ + /* + * kill(.., 0) returns -1 if the process exists. + */ + if (kill(pid, 0) == -1) + return 1; + else + return 0; +} +#elif defined(HAVE_PROCFS) +#define PROCFS "/proc" +#define PROCFS_PATH_SIZE (sizeof(PROCFS)+PROCFS_ENTRY_SIZE) +int +__pmProcessExists(pid_t pid) +{ + char proc_buf[PROCFS_PATH_SIZE]; + snprintf(proc_buf, sizeof(proc_buf), "%s/%" FMT_PID, PROCFS, pid); + return (access(proc_buf, F_OK) == 0); +} +#elif !defined(IS_MINGW) +!bozo! +#endif + +#if defined(HAVE_KILL) +int +__pmProcessTerminate(pid_t pid, int force) +{ + return kill(pid, force ? SIGKILL : SIGTERM); +} +#elif !defined(IS_MINGW) +!bozo! +#endif + +#if defined(HAVE_SBRK) +int +__pmProcessDataSize(unsigned long *size) +{ + static void *base; + + if (size && base) + *size = (sbrk(0) - base) / 1024; + else { + base = sbrk(0); + if (size) + *size = 0; + } + return 0; +} +#elif !defined(IS_MINGW) +#warning "Platform does not define a process datasize interface?" +int __pmProcessDataSize(unsigned long *) { return -1; } +#endif + +#if !defined(IS_MINGW) +int +__pmProcessRunTimes(double *usr, double *sys) +{ + struct tms tms; + double ticks = (double)sysconf(_SC_CLK_TCK); + + if (times(&tms) == (clock_t)-1) { + *usr = *sys = 0.0; + return -1; + } + *usr = (double)tms.tms_utime / ticks; + *sys = (double)tms.tms_stime / ticks; + return 0; +} +#endif + +#if !defined(IS_MINGW) +pid_t +__pmProcessCreate(char **argv, int *infd, int *outfd) +{ + int in[2]; + int out[2]; + pid_t pid; + + if (pipe1(in) < 0) + return -oserror(); + if (pipe1(out) < 0) + return -oserror(); + + pid = fork(); + if (pid < 0) { + return -1; + } + else if (pid) { + /* parent */ + close(in[0]); + close(out[1]); + *infd = out[0]; + *outfd = in[1]; + } + else { + /* child */ + char errmsg[PM_MAXERRMSGLEN]; + close(in[1]); + close(out[0]); + if (in[0] != 0) { + close(0); + dup2(in[0], 0); + close(in[0]); + } + if (out[1] != 1) { + close(1); + dup2(out[1], 1); + close(out[1]); + } + execvp(argv[0], argv); + fprintf(stderr, "execvp: %s\n", osstrerror_r(errmsg, sizeof(errmsg))); + exit(1); + } + return pid; +} + +int +__pmSetSignalHandler(int sig, __pmSignalHandler func) +{ + signal(sig, func); + return 0; +} + +int +__pmSetProgname(const char *program) +{ + char *p; + + /* Trim command name of leading directory components */ + if (program) + pmProgname = (char *)program; + for (p = pmProgname; pmProgname && *p; p++) { + if (*p == '/') + pmProgname = p+1; + } + return 0; +} + +int +__pmShutdown(void) +{ + int code = 0, sts; + + if ((sts = __pmShutdownLocal()) < 0 && !code) + code = sts; + if ((sts = __pmShutdownCertificates()) < 0 && !code) + code = sts; + if ((sts = __pmShutdownSecureSockets()) < 0 && !code) + code = sts; + return code; +} + +void * +__pmMemoryMap(int fd, size_t sz, int writable) +{ + int mflags = writable ? (PROT_READ | PROT_WRITE) : PROT_READ; + void *addr = mmap(NULL, sz, mflags, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) + return NULL; + return addr; +} + +void +__pmMemoryUnmap(void *addr, size_t sz) +{ + munmap(addr, sz); +} + +#if HAVE_TRACE_BACK_STACK +#include <libexc.h> +#define MAX_DEPTH 30 /* max callback procedure depth */ +#define MAX_SIZE 48 /* max function name length */ + +void +__pmDumpStack(FILE *f) +{ + __uint64_t call_addr[MAX_DEPTH]; + char *call_fn[MAX_DEPTH]; + char names[MAX_DEPTH][MAX_SIZE]; + int res; + int i; + + for (i = 0; i < MAX_DEPTH; i++) + call_fn[i] = names[i]; + res = trace_back_stack(MAX_DEPTH, call_addr, call_fn, MAX_DEPTH, MAX_SIZE); + for (i = 1; i < res; i++) { +#if defined(HAVE_64BIT_PTR) + fprintf(f, " 0x%016llx [%s]\n", call_addr[i], call_fn[i]); +#else + fprintf(f, " 0x%08lx [%s]\n", (__uint32_t)call_addr[i], call_fn[i]); +#endif + } +} + +#elif HAVE_BACKTRACE +#include <execinfo.h> +#define MAX_DEPTH 30 /* max callback procedure depth */ + +void +__pmDumpStack(FILE *f) +{ + int nframe; + void *buf[MAX_DEPTH]; + char **symbols; + int i; + + nframe = backtrace(buf, MAX_DEPTH); + if (nframe < 1) { + fprintf(f, "backtrace -> %d frames?\n", nframe); + return; + } + symbols = backtrace_symbols(buf, nframe); + if (symbols == NULL) { + fprintf(f, "backtrace_symbols failed!\n"); + return; + } + for (i = 1; i < nframe; i++) + fprintf(f, " " PRINTF_P_PFX "%p [%s]\n", buf[i], symbols[i]); +} +#else /* no known mechanism, provide a stub (called unconditionally) */ +void +__pmDumpStack(FILE *f) +{ + fprintf(f, "[No backtrace support available]\n"); +} +#endif /* HAVE_BACKTRACE */ + +#endif /* !IS_MINGW */ diff --git a/src/libpcp/src/win32.c b/src/libpcp/src/win32.c new file mode 100644 index 0000000..387b2f5 --- /dev/null +++ b/src/libpcp/src/win32.c @@ -0,0 +1,796 @@ +/* + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2008-2010 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +/* + * For the MinGW headers and library to work correctly, we need + * something newer than the default Windows 95 versions of the Win32 + * APIs. 0x0500 is the magic sauce to select the Windows 2000 version + * of the Win32 APIs, which is the minimal version needed for PCP. + * + * WINVER needs to be set before any of the MinGW headers are processed + * and we include <windows.h> from pmapi.h via platform_defs.h. + * + * Thanks to "Earnie" on the mingw-users@lists.sourceforge.net mailing + * list for this tip. + */ +#define WINVER 0x0500 + +#include "pmapi.h" +#include "impl.h" +#include <winbase.h> +#include <psapi.h> + +#define FILETIME_1970 116444736000000000ull /* 1/1/1601-1/1/1970 */ +#define HECTONANOSEC_PER_SEC 10000000ull +#define MILLISEC_PER_SEC 1000 +#define NANOSEC_PER_MILLISEC 1000000ull +#define NANOSEC_BOUND (1000000000ull - 1) +#define MAX_SIGNALS 3 /* HUP, USR1, TERM */ + +static struct { + int signal; + HANDLE eventhandle; + HANDLE waithandle; + __pmSignalHandler callback; +} signals[MAX_SIGNALS]; + +VOID CALLBACK +SignalCallback(PVOID param, BOOLEAN timerorwait) +{ + int index = (int)param; + + if (index >= 0 && index < MAX_SIGNALS) + signals[index].callback(signals[index].signal); + else + fprintf(stderr, "SignalCallback: bad signal index (%d)\n", index); +} + +static char * +MapSignals(int sig, int *index) +{ + static char name[8]; + + switch (sig) { + case SIGHUP: + *index = 0; + strcpy(name, "SIGHUP"); + break; + case SIGUSR1: + *index = 1; + strcpy(name, "SIGUSR1"); + break; + case SIGTERM: + *index = 2; + strcpy(name, "SIGTERM"); + break; + default: + return NULL; + } + return name; +} + +int +__pmSetSignalHandler(int sig, __pmSignalHandler func) +{ + int sts, index; + char *signame, evname[64]; + HANDLE eventhdl, waithdl; + + if ((signame = MapSignals(sig, &index)) < 0) + return index; + + if (signals[index].callback) { /* remove old handler */ + UnregisterWait(signals[index].waithandle); + CloseHandle(signals[index].eventhandle); + signals[index].callback = NULL; + signals[index].signal = -1; + } + + if (func == SIG_IGN) + return 0; + + sts = 0; + snprintf(evname, sizeof(evname), "PCP/%" FMT_PID "/%s", getpid(), signame); + if (!(eventhdl = CreateEvent(NULL, FALSE, FALSE, TEXT(evname)))) { + sts = GetLastError(); + fprintf(stderr, "CreateEvent::%s failed (%d)\n", signame, sts); + } + else if (!RegisterWaitForSingleObject(&waithdl, eventhdl, + SignalCallback, (PVOID)index, INFINITE, 0)) { + sts = GetLastError(); + fprintf(stderr, "RegisterWait::%s failed (%d)\n", signame, sts); + } + else { + signals[index].eventhandle = eventhdl; + signals[index].waithandle = waithdl; + signals[index].callback = func; + signals[index].signal = sig; + } + return sts; +} + +static void +sigterm_callback(int sig) +{ + exit(0); /* give atexit(3) handlers a look-in */ +} + +int +__pmSetProcessIdentity(const char *username) +{ + (void)username; + return 0; /* Not Yet Implemented */ +} + +int +__pmSetProgname(const char *program) +{ + int sts1, sts2; + char *p, *suffix = NULL; + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + + /* Trim command name of leading directory components and ".exe" suffix */ + if (program) + pmProgname = (char *)program; + for (p = pmProgname; pmProgname && *p; p++) { + if (*p == '\\' || *p == '/') + pmProgname = p + 1; + if (*p == '.') + suffix = p; + } + if (suffix && strcmp(suffix, ".exe") == 0) + *suffix = '\0'; + + /* Deal with all files in binary mode - no EOL futzing */ + _fmode = O_BINARY; + + /* + * If Windows networking is not setup, all networking calls fail; + * this even includes gethostname(2), if you can believe that. :[ + */ + sts1 = WSAStartup(wVersionRequested, &wsaData); + + /* + * Here we are emulating POSIX signals using Event objects. + * For all processes we want a SIGTERM handler, which allows + * us an opportunity to cleanly shutdown: atexit(1) handlers + * get a look-in, IOW. Other signals (HUP/USR1) are handled + * in a similar way, but only by processes that need them. + */ + sts2 = __pmSetSignalHandler(SIGTERM, sigterm_callback); + + return sts1 | sts2; +} + +void * +__pmMemoryMap(int fd, size_t sz, int writable) +{ + void *addr = NULL; + int cflags = writable ? PAGE_READWRITE : PAGE_READONLY; + + HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd), + NULL, cflags, 0, sz, NULL); + if (handle != NULL) { + int mflags = writable ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ; + addr = MapViewOfFile(handle, mflags, 0, 0, sz); + CloseHandle(handle); + if (addr == MAP_FAILED) + return NULL; + } + return addr; +} + +void +__pmMemoryUnmap(void *addr, size_t sz) +{ + (void)sz; + UnmapViewOfFile(addr); +} + +int +__pmProcessExists(pid_t pid) +{ + HANDLE ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (ph == NULL) + return 0; + CloseHandle(ph); + return 1; +} + +int +__pmProcessTerminate(pid_t pid, int force) +{ + HANDLE ph = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (ph != NULL) { + TerminateProcess(ph, 0); + CloseHandle(ph); + return 0; + } + return -ESRCH; +} + +pid_t +__pmProcessCreate(char **argv, int *infd, int *outfd) +{ + HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr; + PROCESS_INFORMATION piProcInfo; + SECURITY_ATTRIBUTES saAttr; + STARTUPINFO siStartInfo; + LPTSTR cmdline = NULL; + char *command; + int i, sz = 0; + + ZeroMemory(&saAttr, sizeof(SECURITY_ATTRIBUTES)); + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; /* pipe handles are inherited. */ + saAttr.lpSecurityDescriptor = NULL; + + /* + * Create a pipe for communication with the child process. + * Ensure that the read handle to the child process's pipe for + * STDOUT is not inherited. + */ + if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) + return -1; + SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0); + + /* + * Create a pipe for the child process's STDIN. + * Ensure that the write handle to the child process's pipe for + * STDIN is not inherited. + */ + if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) { + CloseHandle(hChildStdoutRd); + CloseHandle(hChildStdoutWr); + return -1; + } + SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0); + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdOutput = hChildStdoutWr; + siStartInfo.hStdInput = hChildStdinRd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* Flatten the argv array for the Windows CreateProcess API */ + + for (command = argv[0], i = 0; command && *command; command = argv[++i]) { + int length = strlen(command); + cmdline = realloc(cmdline, sz + length + 1); /* 1space or 1null */ + strcpy(&cmdline[sz], command); + cmdline[sz + length] = ' '; + sz += length + 1; + } + cmdline[sz - 1] = '\0'; + + if (0 == CreateProcess(NULL, + cmdline, /* command line */ + NULL, /* process security attributes */ + NULL, /* primary thread security attributes */ + TRUE, /* handles are inherited */ + 0, /* creation flags */ + NULL, /* use parent's environment */ + NULL, /* use parent's current directory */ + &siStartInfo, /* STARTUPINFO pointer */ + &piProcInfo)) /* receives PROCESS_INFORMATION */ + { + CloseHandle(hChildStdinRd); + CloseHandle(hChildStdinWr); + CloseHandle(hChildStdoutRd); + CloseHandle(hChildStdoutWr); + return -1; + } + else { + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + } + + *infd = _open_osfhandle((intptr_t)hChildStdoutRd, _O_RDONLY); + *outfd = _open_osfhandle((intptr_t)hChildStdinWr, _O_WRONLY); + return piProcInfo.dwProcessId; +} + +pid_t +__pmProcessWait(pid_t pid, int nowait, int *code, int *signal) +{ + HANDLE ph; + DWORD status; + + if (pid == (pid_t)-1 || pid == (pid_t)-2) + return -1; + if ((ph = OpenProcess(SYNCHRONIZE, FALSE, pid)) == NULL) + return -1; + if (WaitForSingleObject(ph, (DWORD)(-1L)) == WAIT_FAILED) { + CloseHandle(ph); + return -1; + } + if (GetExitCodeProcess(ph, &status)) { + CloseHandle(ph); + return -1; + } + if (code) + *code = status; + CloseHandle(ph); + *signal = -1; + return pid; +} + +int +__pmProcessDataSize(unsigned long *datasize) +{ + PROCESS_MEMORY_COUNTERS pmc; + HANDLE ph; + int sts = -1; + + if (!datasize) + return 0; + *datasize = 0UL; + ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + if (ph == NULL) + return sts; + else if (GetProcessMemoryInfo(ph, &pmc, sizeof(pmc))) { + *datasize = pmc.WorkingSetSize / 1024; + sts = 0; + } + CloseHandle(ph); + return sts; +} + +int +__pmProcessRunTimes(double *usr, double *sys) +{ + ULARGE_INTEGER ul; + FILETIME times[4]; + HANDLE ph; + int sts = -1; + + *usr = *sys = 0.0; + ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + if (ph == NULL) + return sts; + else if (GetProcessTimes(ph, ×[0], ×[1], ×[2], ×[3])) { + ul.LowPart = times[2].dwLowDateTime; + ul.HighPart = times[2].dwHighDateTime; + *sys = ul.QuadPart / 10000000.0; + ul.LowPart = times[3].dwLowDateTime; + ul.HighPart = times[3].dwHighDateTime; + *usr = ul.QuadPart / 10000000.0; + sts = 0; + } + CloseHandle(ph); + return sts; +} + +void +__pmDumpStack(FILE *f) +{ + /* TODO: StackWalk64 API */ +} + +void +__pmtimevalNow(struct timeval *tv) +{ + struct timespec ts; + union { + unsigned long long ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } now; + + GetSystemTimeAsFileTime(&now.ft); + now.ns100 -= FILETIME_1970; + ts.tv_sec = now.ns100 / HECTONANOSEC_PER_SEC; + ts.tv_nsec = (long)(now.ns100 % HECTONANOSEC_PER_SEC) * 100; + tv->tv_sec = ts.tv_sec; + tv->tv_usec = (ts.tv_nsec / 1000); +} + +int +nanosleep(const struct timespec *req, struct timespec *rem) +{ + DWORD milliseconds; + + if (req->tv_sec < 0 || req->tv_nsec < 0 || req->tv_nsec > NANOSEC_BOUND) { + SetLastError(EINVAL); + return -1; + } + milliseconds = req->tv_sec * MILLISEC_PER_SEC + + req->tv_nsec / NANOSEC_PER_MILLISEC; + SleepEx(milliseconds, TRUE); + if (rem) + memset(rem, 0, sizeof(*rem)); + return 0; +} + +unsigned int +sleep(unsigned int seconds) +{ + SleepEx(seconds * 1000, TRUE); + return 0; +} + +void setlinebuf(FILE *stream) +{ + setvbuf(stream, NULL, _IONBF, 0); /* no line buffering in Win32 */ +} + +long int +lrand48(void) +{ + return rand(); +} + +void +srand48(long int seed) +{ + srand(seed); +} + +char * +index(const char *string, int marker) +{ + char *p; + for (p = (char *)string; *p != '\0'; p++) + if (*p == marker) + return p; + return NULL; +} + +char * +rindex(const char *string, int marker) +{ + char *p; + for (p = (char *)string; *p != '\0'; p++) + ; + if (p == string) + return NULL; + for (--p; p != string; p--) + if (*p == marker) + return p; + return NULL; +} + +char * +strcasestr(const char *string, const char *substr) +{ + int i, j; + int sublen = strlen(substr); + int length = strlen(string) - sublen + 1; + + for (i = 0; i < length; i++) { + for (j = 0; j < sublen; j++) + if (toupper(string[i+j]) != toupper(substr[j])) + goto outerloop; + return (char *) substr + i; + outerloop: + continue; + } + return NULL; +} + +int +inet_pton(int family, const char *src, void *dest) +{ + struct sockaddr_storage ss = { 0 }; + char src_copy[INET6_ADDRSTRLEN + 1]; + int size; + + strncpy(src_copy, src, sizeof(src_copy)); + src_copy[sizeof(src_copy)-1] = '\0'; + size = sizeof(ss); + if (WSAStringToAddress(src_copy, family, NULL, (struct sockaddr *)&ss, &size) != 0) + return 0; + switch(family) { + case AF_INET: + *(struct in_addr *)dest = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dest = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + return 0; +} + +const char * +inet_ntop(int family, const void *src, char *dest, socklen_t size) +{ + struct sockaddr_storage ss = { .ss_family = family }; + unsigned long sz = size; + + switch(family) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; + break; + default: + return NULL; + } + if (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dest, &sz) != 0) + return NULL; + return dest; +} + +void * +dlopen(const char *filename, int flag) +{ + return LoadLibrary(filename); +} + +void * +dlsym(void *handle, const char *symbol) +{ + return GetProcAddress(handle, symbol); +} + +int +dlclose(void *handle) +{ + return FreeLibrary(handle); +} + +char * +dlerror(void) +{ + return strerror(GetLastError()); +} + +static HANDLE eventlog; +static char *eventlogPrefix; + +void +openlog(const char *ident, int option, int facility) +{ + if (eventlog) + closelog(); + eventlog = RegisterEventSource(NULL, "Application"); + if (ident) + eventlogPrefix = strdup(ident); +} + +void +syslog(int priority, const char *format, ...) +{ + va_list arg; + LPCSTR msgptr; + char logmsg[2048]; + char *p = logmsg; + int offset = 0; + DWORD eventlogPriority; + + va_start(arg, format); + + if (!eventlog) + openlog(NULL, 0, 0); + + if (eventlogPrefix) + offset = snprintf(p, sizeof(logmsg), "%s: ", eventlogPrefix); + + switch (priority) { + case LOG_EMERG: + case LOG_CRIT: + case LOG_ERR: + eventlogPriority = EVENTLOG_ERROR_TYPE; + break; + case LOG_WARNING: + case LOG_ALERT: + eventlogPriority = EVENTLOG_WARNING_TYPE; + break; + case LOG_NOTICE: + case LOG_DEBUG: + case LOG_INFO: + default: + eventlogPriority = EVENTLOG_INFORMATION_TYPE; + break; + } + msgptr = logmsg; + snprintf(p + offset, sizeof(logmsg) - offset, format, arg); + ReportEvent(eventlog, eventlogPriority, 0, 0, NULL, 1, 0, &msgptr, NULL); + va_end(arg); +} + +void +closelog(void) +{ + if (eventlog) { + DeregisterEventSource(eventlog); + if (eventlogPrefix) + free(eventlogPrefix); + eventlogPrefix = NULL; + } + eventlog = NULL; +} + +const char * +strerror_r(int errnum, char *buf, size_t buflen) +{ + /* strerror_s is missing from the MinGW string.h */ + /* we need to wait for it until we can do this: */ + /* return strerror_s(buf, buflen, errnum); */ + return strerror(errnum); +} + +/* Windows socket error codes - what a nightmare! */ +static const struct { + int err; + char *errmess; +} wsatab[] = { +/*10004*/ { WSAEINTR, "Interrupted function call" }, +/*10009*/ { WSAEBADF, "File handle is not valid" }, +/*10013*/ { WSAEACCES, "Permission denied" }, +/*10014*/ { WSAEFAULT, "Bad address" }, +/*10022*/ { WSAEINVAL, "Invalid argument" }, +/*10024*/ { WSAEMFILE, "Too many open files" }, +/*10035*/ { WSAEWOULDBLOCK, "Resource temporarily unavailable" }, +/*10036*/ { WSAEINPROGRESS, "Operation now in progress" }, +/*10037*/ { WSAEALREADY, "Operation already in progress" }, +/*10038*/ { WSAENOTSOCK, "Socket operation on nonsocket" }, +/*10039*/ { WSAEDESTADDRREQ, "Destination address required" }, +/*10040*/ { WSAEMSGSIZE, "Message too long" }, +/*10041*/ { WSAEPROTOTYPE, "Protocol wrong type for socket" }, +/*10042*/ { WSAENOPROTOOPT, "Bad protocol option" }, +/*10043*/ { WSAEPROTONOSUPPORT, "Protocol not supported" }, +/*10044*/ { WSAESOCKTNOSUPPORT, "Socket type not supported" }, +/*10045*/ { WSAEOPNOTSUPP, "Operation not supported" }, +/*10046*/ { WSAEPFNOSUPPORT, "Protocol family not supported" }, +/*10047*/ { WSAEAFNOSUPPORT, "Address family not supported by protocol family"}, +/*10048*/ { WSAEADDRINUSE, "Address already in use" }, +/*10049*/ { WSAEADDRNOTAVAIL, "Cannot assign requested address" }, +/*10050*/ { WSAENETDOWN, "Network is down" }, +/*10051*/ { WSAENETUNREACH, "Network is unreachable" }, +/*10052*/ { WSAENETRESET, "Network dropped connection on reset" }, +/*10053*/ { WSAECONNABORTED, "Software caused connection abort" }, +/*10054*/ { WSAECONNRESET, "Connection reset by peer" }, +/*10055*/ { WSAENOBUFS, "No buffer space available" }, +/*10056*/ { WSAEISCONN, "Socket is already connected" }, +/*10057*/ { WSAENOTCONN, "Socket is not connected" }, +/*10058*/ { WSAESHUTDOWN, "Cannot send after socket shutdown" }, +/*10059*/ { WSAETOOMANYREFS, "Too many references" }, +/*10060*/ { WSAETIMEDOUT, "Connection timed out" }, +/*10061*/ { WSAECONNREFUSED, "Connection refused" }, +/*10062*/ { WSAELOOP, "Cannot translate name" }, +/*10063*/ { WSAENAMETOOLONG, "Name too long" }, +/*10064*/ { WSAEHOSTDOWN, "Host is down" }, +/*10065*/ { WSAEHOSTUNREACH, "No route to host" }, +/*10066*/ { WSAENOTEMPTY, "Directory not empty" }, +/*10067*/ { WSAEPROCLIM, "Too many processes" }, +/*10070*/ { WSAESTALE, "Stale file handle reference" }, +/*10091*/ { WSASYSNOTREADY, "Network subsystem is unavailable" }, +/*10092*/ { WSAVERNOTSUPPORTED, "Winsock.dll version out of range" }, +/*10093*/ { WSANOTINITIALISED, "Successful WSAStartup not yet performed" }, +/*10101*/ { WSAEDISCON, "Graceful shutdown in progress" }, +/*10102*/ { WSAENOMORE, "No more results" }, +/*10103*/ { WSAECANCELLED, "Call has been canceled" }, +/*10104*/ { WSAEINVALIDPROCTABLE, "Procedure call table is invalid" }, +/*10105*/ { WSAEINVALIDPROVIDER, "Service provider is invalid" }, +/*10106*/ { WSAEPROVIDERFAILEDINIT, "Service provider failed to initialize" }, +/*10107*/ { WSASYSCALLFAILURE, "System call failure" }, +/*10108*/ { WSASERVICE_NOT_FOUND, "Service not found" }, +/*10109*/ { WSATYPE_NOT_FOUND, "Class type not found" }, +/*10110*/ { WSA_E_NO_MORE, "No more results" }, +/*10111*/ { WSA_E_CANCELLED, "Call was canceled" }, +/*10112*/ { WSAEREFUSED, "Database query was refused" }, +/*11001*/ { WSAHOST_NOT_FOUND, "Host not found" }, +/*11002*/ { WSATRY_AGAIN, "Nonauthoritative host not found" }, +/*11003*/ { WSANO_RECOVERY, "This is a nonrecoverable error" }, +/*11004*/ { WSANO_DATA, "Valid name, no data record of requested type" }, + { 0,"" } +}; + +const char * +wsastrerror(int code) +{ + int i; + + for (i = 0; wsatab[i].err; i++) + if (wsatab[i].err == code) + return wsatab[i].errmess; + return NULL; +} + +/* + * User and group account management using Security IDs (SIDs) + */ +int +__pmValidUserID(__pmUserID sid) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmValidGroupID(__pmGroupID sid) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmEqualUserIDs(__pmUserID sid1, __pmUserID sid2) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmEqualGroupIDs(__pmGroupID sid1, __pmGroupID sid2) +{ + return -ENOTSUP; /* NYI */ +} + +void +__pmUserIDFromString(const char *username, __pmUserID *sid) +{ + /* NYI */ +} + +void +__pmGroupIDFromString(const char *groupname, __pmGroupID *sid) +{ + /* NYI */ +} + +char * +__pmUserIDToString(__pmUserID sid, char *buffer, size_t size) +{ + return NULL; /* NYI */ +} + +char * +__pmGroupIDToString(__pmGroupID gid, char *buffer, size_t size) +{ + return NULL; /* NYI */ +} + +int +__pmUsernameToID(const char *username, __pmUserID *uidp) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmGroupnameToID(const char *groupname, __pmGroupID *gidp) +{ + return -ENOTSUP; /* NYI */ +} + +char * +__pmGroupnameFromID(__pmGroupID gid, char *buf, size_t size) +{ + return NULL; /* NYI */ +} + +char * +__pmUsernameFromID(__pmUserID uid, char *buf, size_t size) +{ + return NULL; /* NYI */ +} + +int +__pmUsersGroupIDs(const char *username, __pmGroupID **groupids, unsigned int *ngroups) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmGroupsUserIDs(const char *groupname, __pmUserID **userids, unsigned int *nusers) +{ + return -ENOTSUP; /* NYI */ +} + +int +__pmGetUserIdentity(const char *username, __pmUserID *uid, __pmGroupID *gid, int mode) +{ + return -ENOTSUP; /* NYI */ +} |