diff options
Diffstat (limited to 'src/libpcp/src/context.c')
-rw-r--r-- | src/libpcp/src/context.c | 1038 |
1 files changed, 1038 insertions, 0 deletions
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 |