diff options
Diffstat (limited to 'src/libpcp_trace/src/pdu.c')
-rw-r--r-- | src/libpcp_trace/src/pdu.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/src/libpcp_trace/src/pdu.c b/src/libpcp_trace/src/pdu.c new file mode 100644 index 0000000..ad3c039 --- /dev/null +++ b/src/libpcp_trace/src/pdu.c @@ -0,0 +1,419 @@ +/* + * Copyright (c) 1997-2000,2003 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2012 Red Hat. 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 <signal.h> +#include "pmapi.h" +#include "impl.h" +#include "trace.h" +#include "trace_dev.h" + +typedef struct { + __pmTracePDU *pdubuf; + int len; +} more_ctl; +static more_ctl *more; +static int maxfd = -1; + +static char * +pdutypestr(int type) +{ + if (type == TRACE_PDU_ACK) return "ACK"; + else if (type == TRACE_PDU_DATA) return "DATA"; + else { + static char buf[20]; + snprintf(buf, sizeof(buf), "TYPE-%d?", type); + return buf; + } +} + +static void +moreinput(int fd, __pmTracePDU *pdubuf, int len) +{ +#ifdef PMTRACE_DEBUG + if (__pmstate & PMTRACE_STATE_PDU) { + __pmTracePDUHdr *php = (__pmTracePDUHdr *)pdubuf; + __pmTracePDU *p; + int j, jend; + char *q; + + jend = (php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU); + fprintf(stderr, "moreinput: fd=%d pdubuf=0x%p len=%d\n", + fd, pdubuf, len); + fprintf(stderr, "Piggy-back PDU: %s addr=0x%p len=%d from=%d", + pdutypestr(php->type), php, php->len, php->from); + fprintf(stderr, "%03d: ", 0); + p = (__pmTracePDU *)php; + + /* for Purify ... */ + q = (char *)p + php->len; + while (q < (char *)p + jend*sizeof(__pmTracePDU)) + *q++ = '~'; /* buffer end */ + + for (j = 0; j < jend; j++) { + if ((j % 8) == 0) + fprintf(stderr, "\n%03d: ", j); + fprintf(stderr, "%8x ", p[j]); + } + putc('\n', stderr); + } +#endif + if (fd > maxfd) { + int next = maxfd + 1; + if ((more = (more_ctl *)realloc(more, (fd+1)*sizeof(more[0]))) == NULL) +{ + fprintf(stderr, "realloc failed (%d bytes): %s\n", + (fd+1)*(int)sizeof(more[0]), osstrerror()); + return; + } + maxfd = fd; + while (next <= maxfd) { + more[next].pdubuf = NULL; + next++; + } + } + + __pmtracepinPDUbuf(pdubuf); + more[fd].pdubuf = pdubuf; + more[fd].len = len; +} + +int +__pmtracemoreinput(int fd) +{ + if (fd < 0 || fd > maxfd) + return 0; + + return more[fd].pdubuf == NULL ? 0 : 1; +} + +void +__pmtracenomoreinput(int fd) +{ + if (fd < 0 || fd > maxfd) + return; + + if (more[fd].pdubuf != NULL) { + __pmtraceunpinPDUbuf(more[fd].pdubuf); + more[fd].pdubuf = NULL; + } +} + +static int +pduread(int fd, char *buf, int len, int mode, int timeout) +{ + /* + * handle short reads that may split a PDU ... + */ + int status = 0; + int have = 0; + __pmFdSet onefd; + static int done_default = 0; + static struct timeval def_wait = { 10, 0 }; + + if (timeout == TRACE_TIMEOUT_DEFAULT) { + if (!done_default) { + double def_timeout; + char *timeout_str; + char *end_ptr; + + if ((timeout_str = getenv(TRACE_ENV_REQTIMEOUT)) != NULL) { + def_timeout = strtod(timeout_str, &end_ptr); + if (*end_ptr != '\0' || def_timeout < 0.0) { + status = PMTRACE_ERR_ENVFORMAT; + return status; + } + 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; + } + } + + while (len) { + struct timeval wait; + /* + * either never timeout (i.e. block forever), or timeout + */ + if (timeout != TRACE_TIMEOUT_NEVER) { + if (timeout > 0) { + wait.tv_sec = timeout; + wait.tv_usec = 0; + } + else + wait = def_wait; + __pmFD_ZERO(&onefd); + __pmFD_SET(fd, &onefd); + status = __pmSelectRead(fd+1, &onefd, &wait); + if (status == 0) + return PMTRACE_ERR_TIMEOUT; + else if (status < 0) { + setoserror(neterror()); + return status; + } + } + status = (int)__pmRead(fd, buf, len); + if (status <= 0) { /* EOF or error */ + setoserror(neterror()); + return status; + } + if (mode == -1) + /* special case, see __pmtracegetPDU */ + return status; + have += status; + buf += status; + len -= status; + } + + return have; +} + + +/* + * 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 + * __pmtracexmitPDU 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. + */ + +int +__pmtracexmitPDU(int fd, __pmTracePDU *pdubuf) +{ + int n, len; + __pmTracePDUHdr *php = (__pmTracePDUHdr *)pdubuf; + +#if defined(HAVE_SIGPIPE) + SIG_PF user_onpipe; + user_onpipe = signal(SIGPIPE, SIG_IGN); + if (user_onpipe != SIG_DFL) /* put user handler back */ + signal(SIGPIPE, user_onpipe); +#endif + + php->from = (__int32_t)getpid(); +#ifdef PMTRACE_DEBUG + if (__pmstate & PMTRACE_STATE_PDU) { + int j; + int jend = (php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU); + char *p; + + /* for Purify ... */ + p = (char *)pdubuf + php->len; + while (p < (char *)pdubuf + jend*sizeof(__pmTracePDU)) + *p++ = '~'; /* buffer end */ + + fprintf(stderr, "[%d]__pmtracexmitPDU: %s fd=%d len=%d", + php->from, pdutypestr(php->type), 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); + n = (int)__pmWrite(fd, pdubuf, len); + php->len = ntohl(php->len); + php->from = ntohl(php->from); + php->type = ntohl(php->type); + + if (n != len) + return -oserror(); + + return n; +} + + +int +__pmtracegetPDU(int fd, int timeout, __pmTracePDU **result) +{ + int need, len; + char *handle; + static int maxsize = TRACE_PDU_CHUNK; + __pmTracePDU *pdubuf; + __pmTracePDU *pdubuf_prev; + __pmTracePDUHdr *php; + + /* + * This stuff is a little tricky. What we try to do is read() + * an amount of data equal to the largest PDU we have (or are + * likely to have) seen thus far. In the majority of cases + * this returns exactly one PDU's worth, i.e. read() returns + * a length equal to php->len. + * + * For this to work, we have a special "mode" of -1 + * to pduread() which means read, but return after the + * first read(), rather than trying to read up to the request + * length with multiple read()s, which would of course "hang" + * after the first PDU arrived. + * + * We need to handle the following tricky cases: + * 1. We get _more_ than we need for a single PDU -- happens + * when PDU's arrive together. This requires "moreinput" + * to handle leftovers here (it gets even uglier if we + * have part, but not all of the second PDU). + * 2. We get _less_ than we need for a single PDU -- this + * requires at least another read(), and possibly acquiring + * another pdubuf and doing a memcpy() for the partial PDU + * from the earlier call. + */ + if (__pmtracemoreinput(fd)) { + /* some leftover from last time ... handle -> start of PDU */ + pdubuf = more[fd].pdubuf; + len = more[fd].len; + __pmtracenomoreinput(fd); + } + else { + if ((pdubuf = __pmtracefindPDUbuf(maxsize)) == NULL) + return -oserror(); + len = pduread(fd, (void *)pdubuf, maxsize, -1, timeout); + } + php = (__pmTracePDUHdr *)pdubuf; + + if (len < (int)sizeof(__pmTracePDUHdr)) { + if (len == -1) { + if (oserror() == ECONNRESET|| + oserror() == ETIMEDOUT || oserror() == ENETDOWN || + oserror() == ENETUNREACH || oserror() == EHOSTDOWN || + oserror() == EHOSTUNREACH || oserror() == ECONNREFUSED) + /* + * failed as a result of pmdatrace 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) + * + * treat this like end of file on input + * + * from irix/kern/fs/nfs/bds.c seems like all of the + * following are peers here: + * ECONNRESET (pmdatrace 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?) + */ + len = 0; + else + fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s", + fd, osstrerror()); + } + else if (len > 0) + fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: len=%d, not %d?", + fd, len, (int)sizeof(__pmTracePDUHdr)); + else if (len == PMTRACE_ERR_TIMEOUT) + return PMTRACE_ERR_TIMEOUT; + else if (len < 0) + fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s", fd, pmtraceerrstr(len)); + return len ? PMTRACE_ERR_IPC : 0; + } + + php->len = ntohl(php->len); + if (php->len < 0) { + fprintf(stderr, "__pmtracegetPDU: fd=%d illegal len=%d in hdr\n", fd, php->len); + return PMTRACE_ERR_IPC; + } + + if (len == php->len) + /* return below */ + ; + else if (len > php->len) { + /* + * read more than we need for this one, save it up for next time + */ + handle = (char *)pdubuf; + moreinput(fd, (__pmTracePDU *)&handle[php->len], len - php->len); + } + else { + int tmpsize; + + /* + * need to read more ... + */ + __pmtracepinPDUbuf(pdubuf); + pdubuf_prev = pdubuf; + if (php->len > maxsize) + tmpsize = TRACE_PDU_CHUNK * ( 1 + php->len / TRACE_PDU_CHUNK); + else + tmpsize = maxsize; + if ((pdubuf = __pmtracefindPDUbuf(tmpsize)) == NULL) { + __pmtraceunpinPDUbuf(pdubuf_prev); + return -oserror(); + } + if (php->len > maxsize) + maxsize = tmpsize; + memmove((void *)pdubuf, (void *)php, len); + __pmtraceunpinPDUbuf(pdubuf_prev); + php = (__pmTracePDUHdr *)pdubuf; + need = php->len - len; + handle = (char *)pdubuf; + /* block until all of the PDU is received this time */ + len = pduread(fd, (void *)&handle[len], need, 0, timeout); + if (len != need) { + if (len == PMTRACE_ERR_TIMEOUT) + return PMTRACE_ERR_TIMEOUT; + if (len < 0) + fprintf(stderr, "__pmtracegetPDU: error (%d) fd=%d: %s\n", (int)oserror(), fd, osstrerror()); + else + fprintf(stderr, "__pmtracegetPDU: len=%d, not %d? (fd=%d)\n", len, need, fd); + fprintf(stderr, "hdr: len=0x%08x type=0x%08x from=0x%08x\n", + php->len, (int)ntohl(php->type), (int)ntohl(php->from)); + return PMTRACE_ERR_IPC; + } + } + + *result = (__pmTracePDU *)php; + php->type = ntohl(php->type); + php->from = ntohl(php->from); +#ifdef PMTRACE_DEBUG + if (__pmstate & PMTRACE_STATE_PDU) { + int j; + int jend = (int)(php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU); + char *p; + + /* for Purify ... */ + p = (char *)*result + php->len; + while (p < (char *)*result + jend*sizeof(__pmTracePDU)) + *p++ = '~'; /* buffer end */ + + fprintf(stderr, "[%" FMT_PID "]__pmtracegetPDU: %s fd=%d len=%d from=%d", + getpid(), pdutypestr(php->type), 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 + + return php->type; +} |