summaryrefslogtreecommitdiff
path: root/src/libpcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libpcp')
-rw-r--r--src/libpcp/GNUlocaldefs.324
-rw-r--r--src/libpcp/GNUmakefile31
-rw-r--r--src/libpcp/src/AF.c511
-rw-r--r--src/libpcp/src/GNUlocaldefs.coverage11
-rw-r--r--src/libpcp/src/GNUlocaldefs.debug8
-rw-r--r--src/libpcp/src/GNUmakefile160
-rw-r--r--src/libpcp/src/access.c1875
-rw-r--r--src/libpcp/src/accounts.c586
-rw-r--r--src/libpcp/src/auxconnect.c1389
-rw-r--r--src/libpcp/src/auxserver.c940
-rw-r--r--src/libpcp/src/avahi.c800
-rw-r--r--src/libpcp/src/avahi.h31
-rwxr-xr-xsrc/libpcp/src/check-statics502
-rw-r--r--src/libpcp/src/checksum.c37
-rw-r--r--src/libpcp/src/config.c377
-rw-r--r--src/libpcp/src/connect.c447
-rw-r--r--src/libpcp/src/connectlocal.c692
-rw-r--r--src/libpcp/src/context.c1038
-rw-r--r--src/libpcp/src/derive.c1864
-rw-r--r--src/libpcp/src/derive.h101
-rw-r--r--src/libpcp/src/derive_fetch.c1317
-rw-r--r--src/libpcp/src/desc.c87
-rw-r--r--src/libpcp/src/discovery.c323
-rw-r--r--src/libpcp/src/endian.c361
-rw-r--r--src/libpcp/src/err.c303
-rw-r--r--src/libpcp/src/events.c735
-rw-r--r--src/libpcp/src/exports472
-rw-r--r--src/libpcp/src/fault.c314
-rw-r--r--src/libpcp/src/fetch.c271
-rw-r--r--src/libpcp/src/fetchlocal.c177
-rw-r--r--src/libpcp/src/freeresult.c84
-rw-r--r--src/libpcp/src/getdate.y1274
-rw-r--r--src/libpcp/src/getopt.c1518
-rw-r--r--src/libpcp/src/hash.c202
-rw-r--r--src/libpcp/src/help.c116
-rw-r--r--src/libpcp/src/instance.c352
-rw-r--r--src/libpcp/src/internal.h277
-rw-r--r--src/libpcp/src/interp.c1714
-rw-r--r--src/libpcp/src/ipc.c275
-rw-r--r--src/libpcp/src/lock.c284
-rw-r--r--src/libpcp/src/logconnect.c472
-rw-r--r--src/libpcp/src/logcontrol.c54
-rw-r--r--src/libpcp/src/logmeta.c847
-rw-r--r--src/libpcp/src/logportmap.c454
-rw-r--r--src/libpcp/src/logutil.c2461
-rw-r--r--src/libpcp/src/loop.c871
-rw-r--r--src/libpcp/src/optfetch.c709
-rw-r--r--src/libpcp/src/p_auth.c103
-rw-r--r--src/libpcp/src/p_creds.c103
-rw-r--r--src/libpcp/src/p_desc.c118
-rw-r--r--src/libpcp/src/p_error.c168
-rw-r--r--src/libpcp/src/p_fetch.c101
-rw-r--r--src/libpcp/src/p_instance.c291
-rw-r--r--src/libpcp/src/p_lcontrol.c194
-rw-r--r--src/libpcp/src/p_lrequest.c85
-rw-r--r--src/libpcp/src/p_lstatus.c116
-rw-r--r--src/libpcp/src/p_pmns.c576
-rw-r--r--src/libpcp/src/p_profile.c223
-rw-r--r--src/libpcp/src/p_result.c652
-rw-r--r--src/libpcp/src/p_text.c150
-rw-r--r--src/libpcp/src/pdu.c574
-rw-r--r--src/libpcp/src/pdubuf.c249
-rw-r--r--src/libpcp/src/pmns.c2559
-rw-r--r--src/libpcp/src/probe.c590
-rw-r--r--src/libpcp/src/probe.h23
-rw-r--r--src/libpcp/src/profile.c385
-rw-r--r--src/libpcp/src/rtime.c761
-rw-r--r--src/libpcp/src/secureconnect.c1575
-rw-r--r--src/libpcp/src/secureserver.c719
-rw-r--r--src/libpcp/src/sortinst.c37
-rw-r--r--src/libpcp/src/spec.c1077
-rw-r--r--src/libpcp/src/store.c103
-rw-r--r--src/libpcp/src/stuffvalue.c84
-rw-r--r--src/libpcp/src/tv.c126
-rw-r--r--src/libpcp/src/tz.c541
-rw-r--r--src/libpcp/src/units.c1107
-rw-r--r--src/libpcp/src/util.c2351
-rw-r--r--src/libpcp/src/win32.c796
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(&registered.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(&registered.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], &registered.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 *)&registered.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, &times[0], &times[1], &times[2], &times[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 */
+}