diff options
Diffstat (limited to 'src/pmcd/src')
-rw-r--r-- | src/pmcd/src/GNUmakefile | 38 | ||||
-rw-r--r-- | src/pmcd/src/agent.c | 221 | ||||
-rw-r--r-- | src/pmcd/src/client.c | 265 | ||||
-rw-r--r-- | src/pmcd/src/client.h | 58 | ||||
-rw-r--r-- | src/pmcd/src/config.c | 2526 | ||||
-rw-r--r-- | src/pmcd/src/dofetch.c | 582 | ||||
-rw-r--r-- | src/pmcd/src/dopdus.c | 1057 | ||||
-rw-r--r-- | src/pmcd/src/dostore.c | 312 | ||||
-rw-r--r-- | src/pmcd/src/pmcd.c | 1024 | ||||
-rw-r--r-- | src/pmcd/src/pmcd.h | 233 | ||||
-rw-r--r-- | src/pmcd/src/util.c | 96 |
11 files changed, 6412 insertions, 0 deletions
diff --git a/src/pmcd/src/GNUmakefile b/src/pmcd/src/GNUmakefile new file mode 100644 index 0000000..86462c3 --- /dev/null +++ b/src/pmcd/src/GNUmakefile @@ -0,0 +1,38 @@ +# +# Copyright (c) 2012-2013 Red Hat. +# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# 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. +# + +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs + +CMDTARGET = pmcd$(EXECSUFFIX) +HFILES = client.h pmcd.h +CFILES = pmcd.c config.c dofetch.c dopdus.c dostore.c client.c agent.c util.c + +LLDLIBS = $(PCPLIB) $(LIB_FOR_DLOPEN) -lpcp_pmcd +PCPLIB_LDFLAGS += -L$(TOPDIR)/src/libpcp_pmcd/$(LIBPCP_ABIDIR) + +LLDFLAGS += $(RDYNAMIC_FLAG) $(PIELDFLAGS) +LCFLAGS += $(PIECFLAGS) + +default: $(CMDTARGET) + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET) + +default_pcp: default + +install_pcp: install diff --git a/src/pmcd/src/agent.c b/src/pmcd/src/agent.c new file mode 100644 index 0000000..700a0f2 --- /dev/null +++ b/src/pmcd/src/agent.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * 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 "pmcd.h" +#if defined(HAVE_DLFCN_H) +#include <dlfcn.h> +#elif defined(HAVE_DL_H) +#include <dl.h> +#endif +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif +#if defined(HAVE_SYS_RESOURCE_H) +#include <sys/resource.h> +#endif + +/* Return a pointer to the agent that is reposible for a given domain. + * Note that the agent may not be in a connected state! + */ +AgentInfo * +FindDomainAgent(int domain) +{ + int i; + for (i = 0; i < nAgents; i++) + if (agent[i].pmDomainId == domain) + return &agent[i]; + return NULL; +} + +void +CleanupAgent(AgentInfo* aPtr, int why, int status) +{ + extern int AgentDied; +#ifndef IS_MINGW + int exit_status = status; +#endif + int reason = 0; + + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dlHandle != NULL) { +#ifdef HAVE_DLOPEN + dlclose(aPtr->ipc.dso.dlHandle); +#endif + } + pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, -1, -1); + } + else { + pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd); + if (aPtr->inFd != -1) { + if (aPtr->ipcType == AGENT_SOCKET) + __pmCloseSocket(aPtr->inFd); + else { + close(aPtr->inFd); + __pmResetIPC(aPtr->inFd); + } + aPtr->inFd = -1; + } + if (aPtr->outFd != -1) { + if (aPtr->ipcType == AGENT_SOCKET) + __pmCloseSocket(aPtr->outFd); + else { + close(aPtr->outFd); + __pmResetIPC(aPtr->outFd); + } + aPtr->outFd = -1; + } + if (aPtr->ipcType == AGENT_SOCKET && + aPtr->ipc.socket.addrDomain == AF_UNIX) { + /* remove the Unix domain socket */ + unlink(aPtr->ipc.socket.name); + } + } + + __pmNotifyErr(LOG_INFO, "CleanupAgent ...\n"); + fprintf(stderr, "Cleanup \"%s\" agent (dom %d):", aPtr->pmDomainLabel, aPtr->pmDomainId); + + if (why == AT_EXIT) { + /* waitpid has already been done */ + fprintf(stderr, " terminated"); + reason = (status << 8) | REASON_EXIT; + } + else { + if (why == AT_CONFIG) { + fprintf(stderr, " unconfigured"); + } else { + reason = REASON_PROTOCOL; + fprintf(stderr, " protocol failure for fd=%d", status); +#ifndef IS_MINGW + exit_status = -1; +#endif + } + if (aPtr->status.isChild == 1) { + pid_t pid = (pid_t)-1; + pid_t done; + int wait_status; + int slept = 0; + + if (aPtr->ipcType == AGENT_PIPE) + pid = aPtr->ipc.pipe.agentPid; + else if (aPtr->ipcType == AGENT_SOCKET) + pid = aPtr->ipc.socket.agentPid; + for ( ; ; ) { + +#if defined(HAVE_WAIT3) + done = wait3(&wait_status, WNOHANG, NULL); +#elif defined(HAVE_WAITPID) + done = waitpid((pid_t)-1, &wait_status, WNOHANG); +#else + wait_status = 0; + done = 0; +#endif + if (done == pid) { +#ifndef IS_MINGW + exit_status = wait_status; +#endif + break; + } + if (done > 0) { + continue; + } + if (slept) { + break; + } + /* give PMDA a chance to notice the close() and exit */ + sleep(1); + slept = 1; + } + } + } +#ifndef IS_MINGW + if (exit_status != -1) { + if (WIFEXITED(exit_status)) { + fprintf(stderr, ", exit(%d)", WEXITSTATUS(exit_status)); + reason = (WEXITSTATUS(exit_status) << 8) | reason; + } + else if (WIFSIGNALED(exit_status)) { + fprintf(stderr, ", signal(%d)", WTERMSIG(exit_status)); +#ifdef WCOREDUMP + if (WCOREDUMP(exit_status)) + fprintf(stderr, ", dumped core"); +#endif + reason = (WTERMSIG(exit_status) << 16) | reason; + } + } +#endif + fputc('\n', stderr); + aPtr->reason = reason; + aPtr->status.connected = 0; + aPtr->status.busy = 0; + aPtr->status.notReady = 0; + aPtr->status.flags = 0; + AgentDied = 1; + + if (_pmcd_trace_mask) + pmcd_dump_trace(stderr); + + MarkStateChanges(PMCD_DROP_AGENT); +} + +/* Wait up to total secs for agents to terminate. + * Return 0 if all terminate, else -1 + */ +int +HarvestAgents(unsigned int total) +{ + int i; + int sts; + int found; + pid_t pid; + AgentInfo *ap; + + /* + * Check for child process termination. Be careful, and ignore any + * non-agent processes found. + */ + do { +#if defined(HAVE_WAIT3) + pid = wait3(&sts, WNOHANG, NULL); +#elif defined(HAVE_WAITPID) + pid = waitpid((pid_t)-1, &sts, WNOHANG); +#else + break; +#endif + found = 0; + for ( i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (!ap->status.connected || ap->ipcType == AGENT_DSO) + continue; + + found = 1; + if (pid <= (pid_t)0) { + if (total--) { + sleep(1); + break; + } else { + return -1; + } + } + if (pid == ((ap->ipcType == AGENT_SOCKET) + ? ap->ipc.socket.agentPid + : ap->ipc.pipe.agentPid)) { + CleanupAgent(ap, AT_EXIT, sts); + break; + } + } + } while (found); + + return 0; +} diff --git a/src/pmcd/src/client.c b/src/pmcd/src/client.c new file mode 100644 index 0000000..9d46338 --- /dev/null +++ b/src/pmcd/src/client.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * 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" +#include "pmcd.h" + +#define MIN_CLIENTS_ALLOC 8 + +int maxClientFd = -1; /* largest fd for a client */ +__pmFdSet clientFds; /* for client select() */ + +static int clientSize; + +/* + * For PMDA_INTERFACE_5 or later PMDAs, post a notification that + * a context has been closed. + */ +static void +NotifyEndContext(int ctx) +{ + int i; + + for (i = 0; i < nAgents; i++) { + if (!agent[i].status.connected || + agent[i].status.busy || agent[i].status.notReady) + continue; + if (agent[i].ipcType == AGENT_DSO) { + pmdaInterface *dp = &agent[i].ipc.dso.dispatch; + if (dp->comm.pmda_interface >= PMDA_INTERFACE_5) { + if (dp->version.four.ext->e_endCallBack != NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "NotifyEndContext: DSO PMDA %s (%d) notified of context %d close\n", + agent[i].pmDomainLabel, agent[i].pmDomainId, + ctx); + } +#endif + (*(dp->version.four.ext->e_endCallBack))(ctx); + } + } + } + else { + /* + * Daemon PMDA case ... we don't know the PMDA_INTERFACE + * version, so send the notification PDU anyway, and rely on + * __pmdaMainPDU() doing the right thing. + * Do not expect a response. + * Agent may have decided to spontaneously die so don't + * bother about any return status from the __pmSendError + * either. + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "NotifyEndContext: daemon PMDA %s (%d) notified of context %d close\n", + agent[i].pmDomainLabel, agent[i].pmDomainId, ctx); + } +#endif + pmcd_trace(TR_XMIT_PDU, agent[i].inFd, PDU_ERROR, PM_ERR_NOTCONN); + __pmSendError(agent[i].inFd, ctx, PM_ERR_NOTCONN); + } + } +} + +/* Establish a new socket connection to a client */ +ClientInfo * +AcceptNewClient(int reqfd) +{ + static unsigned int seq = 0; + int i, fd; + __pmSockLen addrlen; + struct timeval now; + + i = NewClient(); + addrlen = __pmSockAddrSize(); + fd = __pmAccept(reqfd, client[i].addr, &addrlen); + if (fd == -1) { + if (neterror() == EPERM) { + __pmNotifyErr(LOG_NOTICE, "AcceptNewClient(%d): " + "Permission Denied\n", reqfd); + client[i].fd = -1; + DeleteClient(&client[i]); + return NULL; + } + else { + __pmNotifyErr(LOG_ERR, "AcceptNewClient(%d) __pmAccept: %s\n", + reqfd, netstrerror()); + Shutdown(); + exit(1); + } + } + if (fd > maxClientFd) + maxClientFd = fd; + + pmcd_openfds_sethi(fd); + + __pmFD_SET(fd, &clientFds); + __pmSetVersionIPC(fd, UNKNOWN_VERSION); /* before negotiation */ + __pmSetSocketIPC(fd); + + client[i].fd = fd; + client[i].status.connected = 1; + client[i].status.changes = 0; + memset(&client[i].attrs, 0, sizeof(__pmHashCtl)); + + /* + * Note seq needs to be unique, but we're using a free running counter + * and not bothering to check here ... unless we churn through + * 4,294,967,296 (2^32) clients while one client remains connected + * we won't have a problem + */ + client[i].seq = seq++; + __pmtimevalNow(&now); + client[i].start = now.tv_sec; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "AcceptNewClient(%d): client[%d] (fd %d)\n", reqfd, i, fd); +#endif + pmcd_trace(TR_ADD_CLIENT, i, 0, 0); + + return &client[i]; +} + +int +NewClient(void) +{ + int i, sz; + + for (i = 0; i < nClients; i++) + if (!client[i].status.connected) + break; + + if (i == clientSize) { + clientSize = clientSize ? clientSize * 2 : MIN_CLIENTS_ALLOC; + sz = sizeof(ClientInfo) * clientSize; + client = (ClientInfo *) realloc(client, sz); + if (client == NULL) { + __pmNoMem("NewClient", sz, PM_RECOV_ERR); + Shutdown(); + exit(1); + } + sz -= (sizeof(ClientInfo) * i); + memset(&client[i], 0, sz); + } + client[i].addr = __pmSockAddrAlloc(); + if (client[i].addr == NULL) { + __pmNoMem("NewClient", __pmSockAddrSize(), PM_RECOV_ERR); + Shutdown(); + exit(1); + } + if (i >= nClients) + nClients = i + 1; + return i; +} + +/* + * Expose ClientInfo struct for client #n + */ +ClientInfo * +GetClient(int n) +{ + if (0 <= n && n < nClients && client[n].status.connected) + return &client[n]; + return NULL; +} + +void +DeleteClient(ClientInfo *cp) +{ + int i; + + for (i = 0; i < nClients; i++) + if (cp == &client[i]) + break; + + if (i == nClients) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + __pmNotifyErr(LOG_ERR, "DeleteClient: tried to delete non-existent client\n"); + Shutdown(); + exit(1); + } +#endif + return; + } + if (cp->fd != -1) { + __pmFD_CLR(cp->fd, &clientFds); + __pmCloseSocket(cp->fd); + } + if (i == nClients-1) { + i--; + while (i >= 0 && !client[i].status.connected) + i--; + nClients = (i >= 0) ? i + 1 : 0; + } + if (cp->fd == maxClientFd) { + maxClientFd = -1; + for (i = 0; i < nClients; i++) { + if (client[i].fd > maxClientFd) + maxClientFd = client[i].fd; + } + } + for (i = 0; i < cp->szProfile; i++) { + if (cp->profile[i] != NULL) { + __pmFreeProfile(cp->profile[i]); + cp->profile[i] = NULL; + } + } + __pmFreeAttrsSpec(&cp->attrs); + __pmHashClear(&cp->attrs); + __pmSockAddrFree(cp->addr); + cp->addr = NULL; + cp->status.connected = 0; + cp->fd = -1; + + NotifyEndContext(cp-client); +} + +void +MarkStateChanges(int changes) +{ + int i; + + for (i = 0; i < nClients; i++) { + if (client[i].status.connected == 0) + continue; + client[i].status.changes |= changes; + } +} + +int +CheckAccountAccess(ClientInfo *cp) +{ + __pmHashNode *node; + const char *userid; + const char *groupid; + + userid = ((node = __pmHashSearch(PCP_ATTR_USERID, &cp->attrs)) ? + (const char *)node->data : NULL); + groupid = ((node = __pmHashSearch(PCP_ATTR_GROUPID, &cp->attrs)) ? + (const char *)node->data : NULL); + if (!userid || !groupid) + if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD)) + return PM_ERR_PERMISSION; + return __pmAccAddAccount(userid, groupid, &cp->denyOps); +} + +int +CheckClientAccess(ClientInfo *cp) +{ + return __pmAccAddClient(cp->addr, &cp->denyOps); +} diff --git a/src/pmcd/src/client.h b/src/pmcd/src/client.h new file mode 100644 index 0000000..2758e9f --- /dev/null +++ b/src/pmcd/src/client.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * 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 _CLIENT_H +#define _CLIENT_H + +/* The table of clients, used by pmcd */ +typedef struct { + int fd; /* Socket descriptor */ + struct { /* Status of connection to client */ + unsigned int connected : 1; /* Client connected */ + unsigned int changes : 3; /* PMCD_* bits for changes since last fetch */ + } status; + /* There is an array of profiles, as there is a profile associated + * with each client context. The array is not guaranteed to be dense. + * The context number sent with each profile/fetch is used to index it. + */ + __pmProfile **profile; /* Client context profile pointers */ + int szProfile; /* Size of array */ + unsigned int denyOps; /* Disallowed operations for client */ + __pmPDUInfo pduInfo; + unsigned int seq; /* Client sequence number (pmdapmcd) */ + time_t start; /* Time client connected (pmdapmcd) */ + __pmSockAddr *addr; /* Network address of client */ + __pmHashCtl attrs; /* Connection attributes (auth info) */ +} ClientInfo; + +PMCD_EXTERN ClientInfo *client; /* Array of clients */ +PMCD_EXTERN int nClients; /* Number of entries in array */ +extern int maxClientFd; /* largest fd for a client */ +extern __pmFdSet clientFds; /* for client select() */ +PMCD_EXTERN int this_client_id; /* client for current request */ + +/* prototypes */ +extern ClientInfo *AcceptNewClient(int); +extern int NewClient(void); +extern void DeleteClient(ClientInfo *); +extern ClientInfo *GetClient(int); +PMCD_EXTERN void ShowClients(FILE *m); +extern int CheckClientAccess(ClientInfo *); +extern int CheckAccountAccess(ClientInfo *); + +#ifdef PCP_DEBUG +extern char *nameclient(int); +#endif + +#endif /* _CLIENT_H */ diff --git a/src/pmcd/src/config.c b/src/pmcd/src/config.c new file mode 100644 index 0000000..740a0f5 --- /dev/null +++ b/src/pmcd/src/config.c @@ -0,0 +1,2526 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved. + * + * 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. + * + * PMCD routines for reading config file, creating children and + * attaching to DSOs. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmcd.h" +#include <ctype.h> +#include <sys/stat.h> +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif +#if defined(HAVE_SYS_UN_H) +#include <sys/un.h> +#endif +#if defined(HAVE_DLFCN_H) +#include <dlfcn.h> +#elif defined(HAVE_DL_H) +#include <dl.h> +#endif + +#define MIN_AGENTS_ALLOC 3 /* Number to allocate first time */ +#define LINEBUF_SIZE 200 + +/* Config file modification time */ +#if defined(HAVE_STAT_TIMESTRUC) +static struct timestruc configFileTime; +#elif defined(HAVE_STAT_TIMESPEC) +static struct timespec configFileTime; +#elif defined(HAVE_STAT_TIMESPEC_T) +static timespec_t configFileTime; +#elif defined(HAVE_STAT_TIME_T) +static time_t configFileTime; +#else +!bozo! +#endif + +int szAgents; /* Number currently allocated */ +int mapdom[MAXDOMID+2]; /* The DomainId-to-AgentIndex map */ + /* Don't use it during parsing! */ + +static FILE *inputStream; /* Input stream for scanner */ +static int scanInit; +static int scanError; /* Problem in scanner */ +static char *linebuf; /* Buffer for input stream */ +static int szLineBuf; /* Allocated size of linebuf */ +static char *token; /* Start of current token */ +static char *tokenend; /* End of current token */ +static int nLines; /* Current line of config file */ +static int linesize; /* Length of line in linebuf */ + +/* Macro to compare a string with token. The linebuf is always null terminated + * so there are no nasty boundary conditions. + */ +#define TokenIs(str) ((tokenend - token) == strlen(str) && \ + !strncasecmp(token, str, strlen(str))) + +/* Return the numeric value of token (or zero if token is not numeric). */ +static int +TokenNumVal(void) +{ + int val = 0; + char *p = token; + while (isdigit((int)*p)) { + val = val * 10 + *p - '0'; + p++; + } + return val; +} + +/* Return true if token is a numeric value */ +static int +TokenIsNumber(void) +{ + char *p; + if (token == tokenend) /* Nasty end of input case */ + return 0; + for (p = token; isdigit((int)*p); p++) + ; + return p == tokenend; +} + +/* Return a strdup-ed copy of the current token. */ +static char* +CopyToken(void) +{ + int len = (int)(tokenend - token); + char *copy = (char *)malloc(len + 1); + if (copy != NULL) { + strncpy(copy, token, len); + copy[len] = '\0'; + } + return copy; +} + +/* Get the next line from the input stream into linebuf. */ + +static void +GetNextLine(void) +{ + char *end; + int more; /* There is more line to read */ + int still_to_read; + int atEOF = 0; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "%d: GetNextLine()\n", nLines); +#endif + + if (szLineBuf == 0) { + szLineBuf = LINEBUF_SIZE; + linebuf = (char *)malloc(szLineBuf); + if (linebuf == NULL) + __pmNoMem("pmcd config: GetNextLine init", szLineBuf, PM_FATAL_ERR); + } + + linebuf[0] = '\0'; + token = linebuf; + if (feof(inputStream)) + return; + + end = linebuf; + more = 0; + still_to_read = szLineBuf; + do { + /* Read into linebuf. If more is set, the read is into a realloc()ed + * version of linebuf. In this case, more is the number of characters + * at the end of the previous batch that should be overwritten + */ + if (fgets(end, still_to_read, inputStream) == NULL) { + if (!feof(inputStream)) { + fprintf(stderr, "pmcd config[line %d]: Error: fgets failed: %s\n", + nLines, osstrerror()); + scanError = 1; + return; + } + atEOF = 1; + } + + linesize = (int)strlen(linebuf); + more = 0; + if (linesize == 0) + break; + if (linebuf[linesize - 1] != '\n') { + if (feof(inputStream)) { + /* Final input line has no '\n', so add one. If a terminating + * null fits after it, that's the line, so break out of loop. + */ + linebuf[linesize] = '\n'; + /* Add terminating null if it fits */ + if (linesize + 1 < szLineBuf) { + linebuf[++linesize] = '\0'; + break; + } + /* If no room for null, get more buffer space */ + } + more = 1; /* More buffer space required */ + } + /* Check for continued lines */ + else if (linesize > 1 && linebuf[linesize - 2] == '\\') { + linebuf[linesize - 2] = ' '; + linesize--; /* Pretend the \n isn't there */ + more = 2; /* Overwrite \n and \0 on next read */ + } + + /* Make buffer larger to accomodate more of the line. */ + if (more) { + if (szLineBuf > 10 * LINEBUF_SIZE) { + fprintf(stderr, + "pmcd config[line %d]: Error: ridiculously long line (%d characters)\n", + nLines+1, szLineBuf); + linebuf[0] = '\0'; + scanError = 1; + return; + } + szLineBuf += LINEBUF_SIZE; + if ((linebuf = realloc(linebuf, szLineBuf)) == NULL) { + static char fallback[2]; + + __pmNoMem("pmcd config: GetNextLine", szLineBuf, PM_RECOV_ERR); + linebuf = fallback; + linebuf[0] = '\0'; + scanError = 1; + return; + } + end = linebuf + linesize; + still_to_read = LINEBUF_SIZE + more; + /* *end is where the next fgets will start putting data. + * There is a special case if we are already at end of input: + * *end is the '\n' added to the line since it didn't have one. + * We are here because the terminating null wouldn't fit. + */ + if (atEOF) { + end[1] = '\0'; + linesize++; + break; + } + token = linebuf; /* We may have a new buffer */ + } + } while (more); + nLines++; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "\n===================NEWLINE=================\n\n"); + fprintf(stderr, "len = %d\nline = \"%s\"\n", (int)strlen(linebuf), linebuf); + } +#endif +} + +/* Advance through the input stream until either a non-whitespace character, a + * newline, or end of input is reached. + */ +static void +SkipWhitespace(void) +{ + while (*token) { + char ch = *token; + + if (isspace((int)ch)) + if (ch == '\n') /* Stop at end of line */ + return; + else + token++; + else if (ch == '#') { + token = &linebuf[linesize-1]; + return; + } + else + return; + } +} + +static int scanReadOnly; /* Set => don't modify input scanner */ +static int doingAccess; /* Set => parsing [access] section */ +static int tokenQuoted; /* True when token a quoted string */ + +/* Print the current token on a given stream. */ + +static void +PrintToken(FILE *stream) +{ + char *p; + if (tokenQuoted) + fputc('"', stream); + for (p = token; p < tokenend; p++) { + if (*p == '\n') + fputs("<newline>", stream); + else if (*p == '\0') + fputs("<null>", stream); + else + fputc(*p, stream); + } + if (tokenQuoted) + fputc('"', stream); +} + +/* Move to the next token in the input stream. This is done by skipping any + * non-whitespace characters to get to the end of the current token then + * skipping any whitespace and newline characters to get to the next token. + */ + +static void +FindNextToken(void) +{ + static char *rawToken; /* Used in pathological quoting case */ + char ch; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "FindNextToken() "); + fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend); + } +#endif + + do { + if (scanInit) { + if (*token == '\0') /* EOF is EOF and that's final */ + return; + if (scanError) /* Ditto for scanner errors */ + return; + + if (*token == '\n') /* If at end of line, get next line */ + GetNextLine(); + else { /* Otherwise move past "old" token */ + if (tokenQuoted) { /* Move past last quote */ + tokenend++; + tokenQuoted = 0; + } + token = tokenend; + } + SkipWhitespace(); /* Find null, newline or non-space */ + } + else { + scanInit = 1; + scanError = 0; + GetNextLine(); + SkipWhitespace(); /* Don't return yet, find tokenend */ + } + } while (doingAccess && *token == '\n'); + + /* Now we have the start of a token. Find the end. */ + + ch = *token; + if (ch == '\0' || ch == '\n') { + tokenend = token; + return; + } + + if (doingAccess) + if (ch == ',' || ch == ':' || ch == ';' || ch == '[' || ch == ']') { + tokenend = token + 1; + return; + } + + rawToken = token; /* Save real token start in case it moves */ + tokenend = token + 1; + if (ch == '#') /* For comments, token is newline */ + token = tokenend = &linebuf[linesize-1]; + else { + int inQuotes = *token == '"'; + int fixToken = 0; + + do { + int gotEnd = isspace((int)*tokenend); + + while (!gotEnd) { + switch (*tokenend) { + case '#': /* \# or # in quotes does not start a comment */ + if (*(tokenend - 1) == '\\' || inQuotes) + fixToken = 1; + else /* Comments don't need whitespace in front */ + gotEnd = 1; + break; + + case ',': + case ':': + case ';': + case '[': + case ']': + gotEnd = doingAccess && !inQuotes; + break; + + case '"': + if (*(tokenend - 1) == '\\') + fixToken = 1; + else { + if (inQuotes) { + inQuotes = 0; + gotEnd = 1; + } + } + break; + + default: + gotEnd = isspace((int)*tokenend); + } + if (gotEnd) + break; + tokenend++; + } + /* Skip any whitespace if still in quotes, but stop at end of line */ + if (inQuotes) + while (isspace((int)*tokenend) && *tokenend != '\n') + tokenend++; + } while (inQuotes && *tokenend != '\n'); + + if (inQuotes) { + scanError = 1; + *token = 0; + tokenend = token; + fprintf(stderr, + "pmcd config[line %d]: Error: unterminated quoted string\n", + nLines); + return; + } + + /* Replace any \# or \" in the token with # or " */ + if (fixToken && !scanReadOnly) { + char *p, *q; + + for (p = q = tokenend; p >= token; p--) { + if (*p == '\\' && ( p[1] == '#' || p[1] == '"') ) + continue; + *q-- = *p; + } + token = q + 1; + } + } + + /* If token originally started with a quote, token is what's inside quotes. + * Note that *rawToken is checked since *token will also be " if the + * token originally started with a \" that has been changed to ". + */ + if (*rawToken == '"') { + token++; + tokenQuoted = 1; + } + else + tokenQuoted = 0; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fputs("TOKEN = '", stderr); + PrintToken(stderr); + fputs("' ", stderr); + fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend); + } +#endif +} + +/* Move to the next line of the input stream. */ + +static void +SkipLine(void) +{ + while (*token && *token != '\n') + FindNextToken(); + FindNextToken(); /* Move over the newline */ +} + +/* From an argv, build a command line suitable for display in logs etc. */ + +static char * +BuildCmdLine(char **argv) +{ + int i, cmdLen = 0; + char *cmdLine; + char *p; + + if (argv == NULL) + return NULL; + for (i = 0; argv[i] != NULL; i++) { + cmdLen += strlen(argv[i]) + 1; /* +1 for space separator or null */ + /* any arg with whitespace appears in quotes */ + if (strpbrk(argv[i], " \t") != NULL) + cmdLen += 2; + /* any quote gets a \ prepended */ + for (p = argv[i]; *p; p++) + if (*p == '"') + cmdLen++; + } + + if ((cmdLine = (char *)malloc(cmdLen)) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: failed to build command line\n", + nLines); + __pmNoMem("pmcd config: BuildCmdLine", cmdLen, PM_RECOV_ERR); + return NULL; + } + for (i = 0, p = cmdLine; argv[i] != NULL; i++) { + int quote = strpbrk(argv[i], " \t") != NULL; + char *q; + + if (quote) + *p++ = '"'; + for (q = argv[i]; *q; q++) { + if (*q == '"') + *p++ = '\\'; + *p++ = *q; + } + if (quote) + *p++ = '"'; + if (argv[i+1] != NULL) + *p++ = ' '; + } + *p = '\0'; + return cmdLine; +} + + +/* Build an argument list suitable for an exec call from the rest of the tokens + * on the current line. + */ +char ** +BuildArgv(void) +{ + int nArgs; + char **result; + + nArgs = 0; + result = NULL; + do { + /* Make result big enough for new arg and terminating NULL pointer */ + result = (char **)realloc(result, (nArgs + 2) * sizeof(char *)); + if (result != NULL) { + if (*token != '/') + result[nArgs] = CopyToken(); + else if ((result[nArgs] = CopyToken()) != NULL) + __pmNativePath(result[nArgs]); + } + if (result == NULL || result[nArgs] == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: failed to build argument list\n", + nLines); + __pmNoMem("pmcd config: build argv", nArgs * sizeof(char *), + PM_RECOV_ERR); + if (result != NULL) { + while (nArgs >= 0) { + if (result[nArgs] != NULL) + free(result[nArgs]); + nArgs--; + } + free(result); + } + return NULL; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "argv[%d] = '%s'\n", nArgs, result[nArgs]); +#endif + + nArgs++; + FindNextToken(); + } while (*token && *token != '\n'); + result[nArgs] = NULL; + + return result; +} + +/* Return the next unused index into the agent array, extending the array + as necessary. */ +static AgentInfo * +GetNewAgent(void) +{ + AgentInfo *na; + + if (agent == NULL) { + agent = (AgentInfo*)malloc(sizeof(AgentInfo) * MIN_AGENTS_ALLOC); + if (agent == NULL) { + perror("GetNewAgentIndex: malloc"); + exit(1); + } + szAgents = MIN_AGENTS_ALLOC; + } + else if (nAgents >= szAgents) { + agent = (AgentInfo*) + realloc(agent, sizeof(AgentInfo) * 2 * szAgents); + if (agent == NULL) { + perror("GetNewAgentIndex: realloc"); + exit(1); + } + szAgents *= 2; + } + + na = agent+nAgents; nAgents++; + memset (na, 0, sizeof(AgentInfo)); + + return na; +} + +/* Free any malloc()-ed memory associated with an agent */ + +static void +FreeAgent(AgentInfo *ap) +{ + int i; + char **argv = NULL; + + free(ap->pmDomainLabel); + if (ap->ipcType == AGENT_DSO) { + free(ap->ipc.dso.pathName); + free(ap->ipc.dso.entryPoint); + } + else if (ap->ipcType == AGENT_SOCKET) { + if (ap->ipc.socket.commandLine != NULL) { + free(ap->ipc.socket.commandLine); + argv = ap->ipc.socket.argv; + } + } + else + if (ap->ipc.pipe.commandLine != NULL) { + free(ap->ipc.pipe.commandLine); + argv = ap->ipc.pipe.argv; + } + + if (argv != NULL) { + for (i = 0; argv[i] != NULL; i++) + free(argv[i]); + free(argv); + } +} + +/* Parse a DSO specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParseDso(char *pmDomainLabel, int pmDomainId) +{ + char *pathName; + char *entryPoint; + AgentInfo *newAgent; + int xlatePath = 0; + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected DSO entry point\n", nLines); + return -1; + } + if ((entryPoint = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO entry point\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected DSO pathname\n", nLines); + free(entryPoint); + return -1; + } + if (*token != '/') { + if (token[strlen(token)-1] == '\n') + token[strlen(token)-1] = '\0'; + fprintf(stderr, "pmcd config[line %d]: Error: path \"%s\" to PMDA is not absolute\n", nLines, token); + free(entryPoint); + return -1; + } + + if ((pathName = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO pathname\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + __pmNativePath(pathName); + + FindNextToken(); + if (*token != '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: too many parameters for DSO\n", + nLines); + free(entryPoint); + free(pathName); + return -1; + } + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + + newAgent->ipcType = AGENT_DSO; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->ipc.dso.pathName = pathName; + newAgent->ipc.dso.xlatePath = xlatePath; + newAgent->ipc.dso.entryPoint = entryPoint; + + return 0; +} + +/* Parse a socket specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParseSocket(char *pmDomainLabel, int pmDomainId) +{ + int addrDomain, port = -1; + char *socketName = NULL; + AgentInfo *newAgent; + + FindNextToken(); + if (TokenIs("inet")) + addrDomain = AF_INET; + else if (TokenIs("ipv6")) + addrDomain = AF_INET6; + else if (TokenIs("unix")) + addrDomain = AF_UNIX; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected socket address domain (`inet', `ipv6', or `unix')\n", + nLines); + return -1; + } + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected socket port name or number\n", + nLines); + return -1; + } + else if (TokenIsNumber()) + port = TokenNumVal(); + else + if ((socketName = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy port name\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + FindNextToken(); + + /* If an internet domain port name was specified, find the corresponding + port number. */ + + if ((addrDomain == AF_INET || addrDomain == AF_INET6) && socketName) { + struct servent *service; + + service = getservbyname(socketName, NULL); + if (service) + port = service->s_port; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: failed to get port number for port name %s\n", + nLines, socketName); + free(socketName); + return -1; + } + } + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + + newAgent->ipcType = AGENT_SOCKET; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->ipc.socket.addrDomain = addrDomain; + newAgent->ipc.socket.name = socketName; + newAgent->ipc.socket.port = port; + if (*token != '\n') { + newAgent->ipc.socket.argv = BuildArgv(); + if (newAgent->ipc.socket.argv == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n", + nLines, newAgent->pmDomainLabel); + FreeAgent(newAgent); + nAgents--; + return -1; + } + newAgent->ipc.socket.commandLine = BuildCmdLine(newAgent->ipc.socket.argv); + } + newAgent->ipc.socket.agentPid = (pid_t)-1; + + return 0; +} + +/* Parse a pipe specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParsePipe(char *pmDomainLabel, int pmDomainId) +{ + int i; + AgentInfo *newAgent; + int notReady = 0; + + FindNextToken(); + if (!TokenIs("binary")) { + fprintf(stderr, + "pmcd config[line %d]: Error: pipe PDU type expected (`binary')\n", + nLines); + return -1; + } + + do { + i = 0; + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, + "pmcd config[line %d]: Error: command to create pipe agent expected.\n", + nLines); + return -1; + } else if ((i = TokenIs ("notready"))) { + notReady = 1; + } + } while (i); + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + newAgent->ipcType = AGENT_PIPE; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->status.startNotReady = notReady; + newAgent->ipc.pipe.argv = BuildArgv(); + + if (newAgent->ipc.pipe.argv == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n", + nLines, newAgent->pmDomainLabel); + FreeAgent(newAgent); + nAgents--; + return -1; + } + newAgent->ipc.pipe.commandLine = BuildCmdLine(newAgent->ipc.pipe.argv); + + return 0; +} + +static int +ParseAccessSpec(int allow, int *specOps, int *denyOps, int *maxCons, int recursion) +{ + int op; /* >0 for specific ops, 0 otherwise */ + int haveOps = 0, haveAll = 0; + int haveComma = 0; + + if (*token == ';') { + fprintf(stderr, "pmcd config[line %d]: Error: empty or incomplete permissions list\n", + nLines); + return -1; + } + + if (!recursion) /* Set maxCons to unspecified 1st time */ + *maxCons = 0; + while (*token && *token != ';') { + op = 0; + if (TokenIs("fetch")) + op = PMCD_OP_FETCH; + else if (TokenIs("store")) + op = PMCD_OP_STORE; + else if (TokenIs("all")) { + if (haveOps) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n", + nLines); + return -1; + } + haveAll = 1; + if (recursion) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" within an \"all except\"\n", + nLines); + return -1; + } + FindNextToken(); + + /* Any "all" statement specifies permissions for all operations + * Start off with all operations in allow/disallowed state + */ + *denyOps = allow ? PMCD_OP_NONE : PMCD_OP_ALL; + + if (TokenIs("except")) { + /* Now deal with exceptions by reversing the "allow" sense */ + int sts; + + FindNextToken(); + sts = ParseAccessSpec(!allow, specOps, denyOps, maxCons, 1); + if (sts < 0) return -1; + } + *specOps = PMCD_OP_ALL; /* Do this AFTER any recursive call */ + } + else if (TokenIs("maximum") || TokenIs("unlimited")) { + int unlimited = (*token == 'u' || *token == 'U'); + + if (*maxCons) { + fprintf(stderr, + "pmcd config[line %d]: Error: connection limit already specified\n", + nLines); + return -1; + } + if (recursion && !haveOps) { + fprintf(stderr, + "pmcd config[line %d]: Error: connection limit may not immediately follow \"all except\"\n", + nLines); + return -1; + } + + /* "maximum N connections" or "unlimited connections" is not + * allowed in a disallow statement. This is a bit tricky, because + * of the recursion in "all except", which flips "allow" into + * !"allow" and recursion from 0 to 1 for the recursive call to + * this function. The required test is !XOR: "!recursion && allow" + * is an "allow" with no "except". "recursion && !allow" is an + * "allow" with an "except" anything else is a "disallow" (i.e. an + * error) + */ + if (!(recursion ^ allow)) { /* disallow statement */ + fprintf(stderr, + "pmcd config[line %d]: Error: can't specify connection limit in a disallow statement\n", + nLines); + return -1; + } + if (unlimited) + *maxCons = -1; + else { + FindNextToken(); + if (!TokenIsNumber() || TokenNumVal() <= 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: maximum connection limit must be a positive number\n", + nLines); + return -1; + } + *maxCons = TokenNumVal(); + FindNextToken(); + } + if (!TokenIs("connections")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"connections\" expected\n", + nLines); + return -1; + } + FindNextToken(); + } + else { + fprintf(stderr, "pmcd config[line %d]: Error: bad access specifier\n", + nLines); + return -1; + } + + /* If there was a specific operation mentioned, (dis)allow it */ + if (op) { + if (haveAll) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n", + nLines); + return -1; + } + haveOps = 1; + *specOps |= op; + if (allow) + *denyOps &= (~op); + else + *denyOps |= op; + FindNextToken(); + } + if (*token != ',' && *token != ';') { + fprintf(stderr, + "pmcd config[line %d]: Error: ',' or ';' expected in permission list\n", + nLines); + return -1; + } + if (*token == ',') { + haveComma = 1; + FindNextToken(); + } + else + haveComma = 0; + } + if (haveComma) { + fprintf(stderr, + "pmcd config[line %d]: Error: misplaced (trailing) ',' in permission list\n", + nLines); + return -1; + } + return 0; +} + +static int +ParseNames(char ***namesp, const char *nametype) +{ + static char **names; + static int szNames; + int nnames = 0; + int another = 1; + + /* Beware of quoted tokens of length longer than 1. e.g. ":*" */ + while (*token && another && + ((tokenend - token > 1) || (*token != ':' && *token != ';'))) { + if (nnames == szNames) { + int need; + + szNames += 8; + need = szNames * (int)sizeof(char**); + if ((names = (char **)realloc(names, need)) == NULL) + __pmNoMem("pmcd ParseNames name list", need, PM_FATAL_ERR); + } + if ((names[nnames++] = CopyToken()) == NULL) + __pmNoMem("pmcd ParseNames name", tokenend - token, PM_FATAL_ERR); + FindNextToken(); + if (*token != ',' && *token != ':') { + fprintf(stderr, + "pmcd config[line %d]: Error: ',' or ':' expected after \"%s\"\n", + nLines, names[nnames-1]); + return -1; + } + if (*token == ',') { + FindNextToken(); + another = 1; + } + else + another = 0; + } + if (nnames == 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: no %ss in allow/disallow statement\n", + nLines, nametype); + return -1; + } + if (another) { + fprintf(stderr, "pmcd config[line %d]: Error: %s expected after ','\n", + nLines, nametype); + return -1; + } + if (*token != ':') { + fprintf(stderr, "pmcd config[line %d]: Error: ':' expected after \"%s\"\n", + nLines, names[nnames-1]); + return -1; + } + *namesp = names; + return nnames; +} + +static int +ParseHosts(int allow) +{ + int sts; + int nhosts; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **hostnames; + + if ((nhosts = ParseNames(&hostnames, "host")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < nhosts; i++) + fprintf(stderr, "HOST ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + hostnames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for hosts in host access list */ + for (i = 0; i < nhosts; i++) { + if ((sts = __pmAccAddHost(hostnames[i], specOps, denyOps, maxCons)) < 0) { + if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN) + fprintf(stderr, "Warning: the following access control specification will be ignored\n"); + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for host '%s': %s\n", + nLines, hostnames[i], pmErrStr(sts)); + if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN) + ; + else + goto error; + } + else + ok = 1; + } + return ok; + +error: + for (i = 0; i < nhosts; i++) + free(hostnames[i]); + return -1; +} + +static int +ParseUsers(int allow) +{ + int sts; + int nusers; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **usernames; + + if ((nusers = ParseNames(&usernames, "user")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < nusers; i++) + fprintf(stderr, "USER ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + usernames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for users in user access list */ + for (i = 0; i < nusers; i++) { + if ((sts = __pmAccAddUser(usernames[i], specOps, denyOps, maxCons)) < 0) { + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for user '%s': %s\n", + nLines, usernames[i], pmErrStr(sts)); + goto error; + } + ok = 1; + } + return ok; + +error: + for (i = 0; i < nusers; i++) + free(usernames[i]); + return -1; +} + +static int +ParseGroups(int allow) +{ + int sts; + int ngroups; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **groupnames; + + if ((ngroups = ParseNames(&groupnames, "group")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < ngroups; i++) + fprintf(stderr, "GROUP ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + groupnames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for groups in group access list */ + for (i = 0; i < ngroups; i++) { + if ((sts = __pmAccAddGroup(groupnames[i], specOps, denyOps, maxCons)) < 0) { + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for group '%s': %s\n", + nLines, groupnames[i], pmErrStr(sts)); + goto error; + } + ok = 1; + } + return ok; + +error: + for (i = 0; i < ngroups; i++) + free(groupnames[i]); + return -1; +} + +static int +ParseAccessControls(void) +{ + int sts = 0; + int tmp; + int allow; + int naccess = 0; + int need_creds = 0; + + doingAccess = 1; + /* This gets a little tricky, because the token may be "[access]", or + * "[access" or "[". "[" and "]" can't be made special characters until + * the scanner knows it is in the access control section because the arg + * lists for agents may contain them. + */ + if (TokenIs("[access]")) + FindNextToken(); + else { + if (TokenIs("[")) { + FindNextToken(); + if (!TokenIs("access")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"access\" keyword expected\n", + nLines); + return -1; + } + } + else if (!TokenIs("[access")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"access\" keyword expected\n", + nLines); + return -1; + } + FindNextToken(); + if (*token != ']') { + fprintf(stderr, "pmcd config[line %d]: Error: ']' expected\n", nLines); + return -1; + } + FindNextToken(); + } + while (*token && !scanError) { + if (TokenIs("allow")) + allow = 1; + else if (TokenIs("disallow")) + allow = 0; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: allow or disallow statement expected\n", + nLines); + sts = -1; + while (*token && !scanError && *token != ';') + FindNextToken(); + if (*token && !scanError && *token == ';') { + FindNextToken(); + continue; + } + return -1; + } + FindNextToken(); + if (TokenIs("user") || TokenIs("users")) { + FindNextToken(); + if ((tmp = ParseUsers(allow)) < 0) + sts = -1; + else + need_creds = 1; + } else if (TokenIs("group") || TokenIs("groups")) { + FindNextToken(); + if ((tmp = ParseGroups(allow)) < 0) + sts = -1; + else + need_creds = 1; + } else if (TokenIs("host") || TokenIs("hosts")) { + FindNextToken(); + if ((tmp = ParseHosts(allow)) < 0) + sts = -1; + } else { + if ((tmp = ParseHosts(allow)) < 0) + sts = -1; + } + if (tmp > 0) + naccess++; + while (*token && !scanError && *token != ';') + FindNextToken(); + if (!*token || scanError) + return -1; + FindNextToken(); + } + if (sts != 0) + return sts; + + if (naccess == 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: no valid statements in [access] section\n", + nLines); + return -1; + } + + if (need_creds) + __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) + __pmAccDumpLists(stderr); +#endif + + return 0; +} + +/* Parse the configuration file, creating the agent list. */ +static int +ReadConfigFile(FILE *configFile) +{ + char *pmDomainLabel = NULL; + int i, pmDomainId; + int sts = 0; + + inputStream = configFile; + scanInit = 0; + scanError = 0; + doingAccess = 0; + nLines = 0; + FindNextToken(); + while (*token && !scanError) { + if (*token == '\n') /* It's a comment or blank line */ + goto doneLine; + + if (*token == '[') /* Start of access control specs */ + break; + + if ((pmDomainLabel = CopyToken()) == NULL) + __pmNoMem("pmcd config: domain label", tokenend - token + 1, PM_FATAL_ERR); + + FindNextToken(); + if (TokenIsNumber()) { + pmDomainId = TokenNumVal(); + FindNextToken(); + } + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected domain number for \"%s\" agent\n", + nLines, pmDomainLabel); + sts = -1; + goto doneLine; + } + if (pmDomainId < 0 || pmDomainId > MAXDOMID) { + fprintf(stderr, + "pmcd config[line %d]: Error: Illegal domain number (%d) for \"%s\" agent\n", + nLines, pmDomainId, pmDomainLabel); + sts = -1; + goto doneLine; + } + /* Can't use mapdom because it isn't built yet. Can't build it during + * parsing because this might be a restart parse that fails, requiring + * a revert to the old mapdom. + */ + for (i = 0; i < nAgents; i++) + if (pmDomainId == agent[i].pmDomainId) { + fprintf(stderr, + "pmcd config[line %d]: Error: domain number for \"%s\" agent clashes with \"%s\" agent\n", + nLines, pmDomainLabel, agent[i].pmDomainLabel); + sts = -1; + goto doneLine; + } + + /* + * ParseXXX routines must return + * -1 for failure and ensure a NewAgent structure has NOT been + * allocated + * 0 for success with a NewAgent structure allocated + */ + if (TokenIs("dso")) + sts = ParseDso(pmDomainLabel, pmDomainId); + else if (TokenIs("socket")) + sts = ParseSocket(pmDomainLabel, pmDomainId); + else if (TokenIs("pipe")) + sts = ParsePipe(pmDomainLabel, pmDomainId); + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected `dso', `socket' or `pipe'\n", + nLines); + sts = -1; + } +doneLine: + if (pmDomainLabel != NULL) { + free(pmDomainLabel); + pmDomainLabel = NULL; + } + SkipLine(); + } + if (scanError) { + fprintf(stderr, "pmcd config: Can't continue, giving up\n"); + sts = -1; + } + if (*token == '[' && sts != -1) + if (ParseAccessControls() < 0) + sts = -1; + return sts; +} + +static int +DoAuthentication(AgentInfo *ap, int clientID) +{ + int sts = 0; + __pmHashCtl *attrs = &client[clientID].attrs; + __pmHashNode *node; + + if ((ap->status.flags & PDU_FLAG_AUTH) == 0) + return 0; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface < PMDA_INTERFACE_6 || + ap->ipc.dso.dispatch.version.six.attribute == NULL) + return 0; + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[64]; + __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer)); + fprintf(stderr, "pmcd: send client[%d] attr %s to dso agent[%d]", + clientID, buffer, (int)(ap - agent)); + } +#endif + if ((sts = ap->ipc.dso.dispatch.version.six.attribute( + clientID, node->key, node->data, + node->data ? strlen(node->data)+1 : 0, + ap->ipc.dso.dispatch.version.six.ext)) < 0) + break; + } + } else { + /* daemon PMDA ... ship attributes */ + if (ap->status.notReady) + return PM_ERR_AGAIN; + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[64]; + __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer)); + fprintf(stderr, "pmcd: send client[%d] attr %s to daemon agent[%d]", + clientID, buffer, (int)(ap - agent)); + } +#endif + if ((sts = __pmSendAuth(ap->inFd, + clientID, node->key, node->data, + node->data ? strlen(node->data)+1 : 0)) < 0) + break; + } + } + return sts; +} + +/* + * Once a secure client arrives, we need to inform any interested PMDAs. + * Iterate over the authenticating agents and send connection attributes. + */ +int +AgentsAuthentication(int clientID) +{ + int agentID, sts = 0; + + for (agentID = 0; agentID < nAgents; agentID++) { + if (agent[agentID].status.connected && + (sts = DoAuthentication(&agent[agentID], clientID)) < 0) + break; + } + return sts; +} + +/* + * Once a PMDA has started, we need to inform it about secure clients. + * Iterate over the authenticated clients and send connection attributes + */ +int +ClientsAuthentication(AgentInfo *ap) +{ + int clientID, sts = 0; + + for (clientID = 0; clientID < nClients; clientID++) { + if (client[clientID].status.connected && + (sts = DoAuthentication(ap, clientID)) < 0) + break; + } + return sts; +} + +static int +DoAgentCreds(AgentInfo* aPtr, __pmPDU *pb) +{ + int i; + int sts = 0; + int flags = 0; + int sender = 0; + int credcount = 0; + int version = UNKNOWN_VERSION; + __pmCred *credlist = NULL; + __pmVersionCred *vcp; + + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) + return sts; + pmcd_trace(TR_RECV_PDU, aPtr->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + + for (i = 0; i < credcount; i++) { + switch (credlist[i].c_type) { + case CVERSION: + vcp = (__pmVersionCred *)&credlist[i]; + aPtr->pduVersion = version = vcp->c_version; + aPtr->status.flags = flags = vcp->c_flags; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: version creds (version=%u,flags=%x)\n", + aPtr->pduVersion, aPtr->status.flags); +#endif + break; + } + } + + if (credlist != NULL) + free(credlist); + + if (((sts = __pmSetVersionIPC(aPtr->inFd, version)) < 0) || + ((sts = __pmSetVersionIPC(aPtr->outFd, version)) < 0)) + return sts; + + if (version != UNKNOWN_VERSION) { /* finish the version exchange */ + __pmVersionCred handshake; + __pmCred *cp = (__pmCred *)&handshake; + + /* return pmcd PDU version and all flags pmcd knows about */ + handshake.c_type = CVERSION; + handshake.c_version = PDU_VERSION; + handshake.c_flags = (flags & PDU_FLAG_AUTH); + if ((sts = __pmSendCreds(aPtr->inFd, (int)getpid(), 1, cp)) < 0) + return sts; + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_CREDS, credcount); + + /* send auth attributes for existing connected clients */ + if ((flags & PDU_FLAG_AUTH) != 0 && + (sts = ClientsAuthentication(aPtr)) < 0) + return sts; + } + + return 0; +} + +/* version exchange - get a credentials PDU from 2.0 agents */ +static int +AgentNegotiate(AgentInfo *aPtr) +{ + int sts; + __pmPDU *ack; + + sts = __pmGetPDU(aPtr->outFd, ANY_SIZE, _creds_timeout, &ack); + if (sts == PDU_CREDS) { + if ((sts = DoAgentCreds(aPtr, ack)) < 0) { + fprintf(stderr, "pmcd: version exchange failed " + "for \"%s\" agent: %s\n", aPtr->pmDomainLabel, pmErrStr(sts)); + } + __pmUnpinPDUBuf(ack); + return sts; + } + + if (sts > 0) { + fprintf(stderr, "pmcd: unexpected PDU type (0x%x) at initial " + "exchange with %s PMDA\n", sts, aPtr->pmDomainLabel); + __pmUnpinPDUBuf(ack); + } + else if (sts == 0) + fprintf(stderr, "pmcd: unexpected end-of-file at initial " + "exchange with %s PMDA\n", aPtr->pmDomainLabel); + else + fprintf(stderr, "pmcd: error at initial PDU exchange with " + "%s PMDA: %s\n", aPtr->pmDomainLabel, pmErrStr(sts)); + return PM_ERR_IPC; +} + +/* Connect to an agent's socket. */ +static int +ConnectSocketAgent(AgentInfo *aPtr) +{ + int sts = 0; + int fd = -1; /* pander to gcc */ + + if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6) { + __pmSockAddr *addr; + __pmHostEnt *host; + void *enumIx; + + if ((host = __pmGetAddrInfo("localhost")) == NULL) { + fputs("pmcd: Error getting inet address for localhost\n", stderr); + goto error; + } + enumIx = NULL; + for (addr = __pmHostEntGetSockAddr(host, &enumIx); + addr != NULL; + addr = __pmHostEntGetSockAddr(host, &enumIx)) { + if (__pmSockAddrIsInet(addr)) { + /* Only consider addresses of the chosen family. */ + if (aPtr->ipc.socket.addrDomain != AF_INET) + continue; + fd = __pmCreateSocket(); + } + else if (__pmSockAddrIsIPv6(addr)) { + /* Only consider addresses of the chosen family. */ + if (aPtr->ipc.socket.addrDomain != AF_INET6) + continue; + fd = __pmCreateIPv6Socket(); + } + else { + fprintf(stderr, + "pmcd: Error creating socket for \"%s\" agent : invalid address family %d\n", + aPtr->pmDomainLabel, __pmSockAddrGetFamily(addr)); + fd = -1; + } + if (fd < 0) { + __pmSockAddrFree(addr); + continue; /* Try the next address */ + } + + __pmSockAddrSetPort(addr, aPtr->ipc.socket.port); + sts = __pmConnect(fd, (void *)addr, __pmSockAddrSize()); + __pmSockAddrFree(addr); + + if (sts == 0) + break; /* good connection */ + + /* Unsuccessful connection. */ + __pmCloseSocket(fd); + fd = -1; + } + __pmHostEntFree(host); + } + else { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + struct sockaddr_un addr; + int len; + + fd = socket(aPtr->ipc.socket.addrDomain, SOCK_STREAM, 0); + if (fd < 0) { + fprintf(stderr, + "pmcd: Error creating socket for \"%s\" agent : %s\n", + aPtr->pmDomainLabel, netstrerror()); + return -1; + } + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, aPtr->ipc.socket.name); + len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(addr.sun_path); + sts = connect(fd, (struct sockaddr *) &addr, len); +#else + fprintf(stderr, "pmcd: UNIX sockets are not supported : \"%s\" agent\n", + aPtr->pmDomainLabel); + goto error; +#endif + } + if (sts < 0) { + fprintf(stderr, "pmcd: Error connecting to \"%s\" agent : %s\n", + aPtr->pmDomainLabel, netstrerror()); + goto error; + } + aPtr->outFd = aPtr->inFd = fd; /* Sockets are bi-directional */ + pmcd_openfds_sethi(fd); + + if ((sts = AgentNegotiate(aPtr)) < 0) + goto error; + + return 0; + +error: + if (fd != -1) { + if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6) + __pmCloseSocket(fd); + else + close(fd); + } + return -1; +} + +#ifndef IS_MINGW +static pid_t +CreateAgentPOSIX(AgentInfo *aPtr) +{ + int i; + int inPipe[2]; /* Pipe for input to child */ + int outPipe[2]; /* For output to child */ + pid_t childPid = (pid_t)-1; + char **argv = NULL; + + if (aPtr->ipcType == AGENT_PIPE) { + argv = aPtr->ipc.pipe.argv; + if (pipe1(inPipe) < 0) { + fprintf(stderr, + "pmcd: input pipe create failed for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + if (pipe1(outPipe) < 0) { + fprintf(stderr, + "pmcd: output pipe create failed for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + close(inPipe[0]); + close(inPipe[1]); + return (pid_t)-1; + } + pmcd_openfds_sethi(outPipe[1]); + } + else if (aPtr->ipcType == AGENT_SOCKET) + argv = aPtr->ipc.socket.argv; + + if (argv != NULL) { /* Start a new agent if required */ + childPid = fork(); + if (childPid == (pid_t)-1) { + fprintf(stderr, "pmcd: creating child for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + if (aPtr->ipcType == AGENT_PIPE) { + close(inPipe[0]); + close(inPipe[1]); + close(outPipe[0]); + close(outPipe[1]); + } + return (pid_t)-1; + } + + if (childPid) { + /* This is the parent (PMCD) */ + if (aPtr->ipcType == AGENT_PIPE) { + close(inPipe[0]); + close(outPipe[1]); + aPtr->inFd = inPipe[1]; + aPtr->outFd = outPipe[0]; + } + } + else { + /* + * This is the child (new agent) + * make sure stderr is fd 2 + */ + dup2(fileno(stderr), STDERR_FILENO); + if (aPtr->ipcType == AGENT_PIPE) { + /* make pipe stdin for PMDA */ + dup2(inPipe[0], STDIN_FILENO); + /* make pipe stdout for PMDA */ + dup2(outPipe[1], STDOUT_FILENO); + } + else { + /* + * not a pipe, close stdin and attach stdout to stderr + */ + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + } + + for (i = 0; i <= pmcd_hi_openfds; i++) { + /* Close all except std{in,out,err} */ + if (i == STDIN_FILENO || + i == STDOUT_FILENO || + i == STDERR_FILENO) + continue; + close(i); + } + + execvp(argv[0], argv); + /* botch if reach here */ + fprintf(stderr, "pmcd: error starting %s: %s\n", + argv[0], osstrerror()); + /* avoid atexit() processing, so _exit not exit */ + _exit(1); + } + } + return childPid; +} + +#else + +static pid_t +CreateAgentWin32(AgentInfo *aPtr) +{ + SECURITY_ATTRIBUTES saAttr; + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr; + BOOL bSuccess = FALSE; + LPTSTR command = NULL; + + if (aPtr->ipcType == AGENT_PIPE) + command = (LPTSTR)aPtr->ipc.pipe.commandLine; + else if (aPtr->ipcType == AGENT_SOCKET) + command = (LPTSTR)aPtr->ipc.socket.commandLine; + + // Set the bInheritHandle flag so pipe handles are inherited + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDOUT. + if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) { + fprintf(stderr, "pmcd: stdout CreatePipe failed, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0)) { + fprintf(stderr, "pmcd: stdout SetHandleInformation, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + // Create a pipe for the child process's STDIN. + if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) { + fprintf(stderr, "pmcd: stdin CreatePipe failed, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + // Ensure the write handle to the pipe for STDIN is not inherited. + if (!SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0)) { + fprintf(stderr, "pmcd: stdin SetHandleInformation, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + // Create the child process. + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdOutput = hChildStdoutWr; + siStartInfo.hStdError = hChildStdoutWr; + siStartInfo.hStdInput = hChildStdinRd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + bSuccess = CreateProcess(NULL, command, + 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 + if (!bSuccess) { + fprintf(stderr, "pmcd: CreateProcess for \"%s\" agent: %s: %s\n", + aPtr->pmDomainLabel, command, osstrerror()); + return (pid_t)-1; + } + + aPtr->inFd = _open_osfhandle((intptr_t)hChildStdinRd, _O_WRONLY); + aPtr->outFd = _open_osfhandle((intptr_t)hChildStdoutWr, _O_RDONLY); + pmcd_openfds_sethi(aPtr->outFd); + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + CloseHandle(hChildStdoutRd); + CloseHandle(hChildStdinWr); + return piProcInfo.dwProcessId; +} +#endif + +/* Create the specified agent running at the end of a pair of pipes. */ +static int +CreateAgent(AgentInfo *aPtr) +{ + pid_t childPid; + int sts; + + fflush(stderr); + fflush(stdout); + +#ifdef IS_MINGW + childPid = CreateAgentWin32(aPtr); +#else + childPid = CreateAgentPOSIX(aPtr); +#endif + if (childPid < 0) + return (int)childPid; + + aPtr->status.isChild = 1; + if (aPtr->ipcType == AGENT_PIPE) { + aPtr->ipc.pipe.agentPid = childPid; + /* ready for version negotiation */ + if ((sts = AgentNegotiate(aPtr)) < 0) { + close(aPtr->inFd); + close(aPtr->outFd); + return sts; + } + } + else if (aPtr->ipcType == AGENT_SOCKET) + aPtr->ipc.socket.agentPid = childPid; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "pmcd: started PMDA %s (%d), pid=%" FMT_PID "\n", + aPtr->pmDomainLabel, aPtr->pmDomainId, childPid); +#endif + return 0; +} + +/* Print a table of all of the agent configuration info on a given stream. */ +void +PrintAgentInfo(FILE *stream) +{ + int i, version; + AgentInfo *aPtr; + + fputs("\nactive agent dom pid in out ver protocol parameters\n", stream); + fputs( "============ === ===== === === === ======== ==========\n", stream); + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->status.connected == 0) + continue; + fprintf(stream, "%-12s", aPtr->pmDomainLabel); + + switch (aPtr->ipcType) { + case AGENT_DSO: + fprintf(stream, " %3d %3d dso i:%d", + aPtr->pmDomainId, + aPtr->ipc.dso.dispatch.comm.pmapi_version, + aPtr->ipc.dso.dispatch.comm.pmda_interface); + fprintf(stream, " lib=%s entry=%s [" PRINTF_P_PFX "%p]\n", + aPtr->ipc.dso.pathName, aPtr->ipc.dso.entryPoint, + aPtr->ipc.dso.initFn); + break; + + case AGENT_SOCKET: + version = __pmVersionIPC(aPtr->inFd); + fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ", + aPtr->pmDomainId, aPtr->ipc.socket.agentPid, aPtr->inFd, aPtr->outFd, version); + fputs("bin ", stream); + fputs("sock ", stream); + if (aPtr->ipc.socket.addrDomain == AF_UNIX) + fprintf(stream, "dom=unix port=%s", aPtr->ipc.socket.name); + else if (aPtr->ipc.socket.addrDomain == AF_INET) { + if (aPtr->ipc.socket.name) + fprintf(stream, "dom=inet port=%s (%d)", + aPtr->ipc.socket.name, aPtr->ipc.socket.port); + else + fprintf(stream, "dom=inet port=%d", aPtr->ipc.socket.port); + } + else if (aPtr->ipc.socket.addrDomain == AF_INET6) { + if (aPtr->ipc.socket.name) + fprintf(stream, "dom=ipv6 port=%s (%d)", + aPtr->ipc.socket.name, aPtr->ipc.socket.port); + else + fprintf(stream, "dom=ipv6 port=%d", aPtr->ipc.socket.port); + } + else { + fputs("dom=???", stream); + } + if (aPtr->ipc.socket.commandLine) { + fputs(" cmd=", stream); + fputs(aPtr->ipc.socket.commandLine, stream); + } + putc('\n', stream); + break; + + case AGENT_PIPE: + version = __pmVersionIPC(aPtr->inFd); + fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ", + aPtr->pmDomainId, aPtr->ipc.pipe.agentPid, aPtr->inFd, aPtr->outFd, version); + fputs("bin ", stream); + if (aPtr->ipc.pipe.commandLine) { + fputs("pipe cmd=", stream); + fputs(aPtr->ipc.pipe.commandLine, stream); + putc('\n', stream); + } + break; + + default: + fputs("????\n", stream); + break; + } + } + fflush(stream); /* Ensure that it appears now */ +} + +/* Load the DSO for a specified agent and initialise it. */ +static int +GetAgentDso(AgentInfo *aPtr) +{ + DsoInfo *dso = &aPtr->ipc.dso; + const char *name; + unsigned int challenge; + + aPtr->status.connected = 0; + aPtr->reason = REASON_NOSTART; + + name = __pmFindPMDA(dso->pathName); + if (name == NULL) { + fprintf(stderr, "Cannot find %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); + fputc('\n', stderr); + return -1; + } + + if (name != dso->pathName) { + /* some searching was done */ + free(dso->pathName); + dso->pathName = strdup(name); + if (dso->pathName == NULL) { + __pmNoMem("pmcd config: pathName", strlen(name), PM_FATAL_ERR); + } + dso->xlatePath = 1; + } + +#if defined(HAVE_DLOPEN) + /* + * RTLD_NOW would be better in terms of detecting unresolved symbols + * now, rather than taking a SEGV later ... but various combinations + * of dynamic and static libraries used to create the DSO PMDA, + * combined with hiding symbols in the DSO PMDA may result in benign + * unresolved symbols remaining and the dlopen() would fail under + * these circumstances. + */ + dso->dlHandle = dlopen(dso->pathName, RTLD_LAZY); +#else + fprintf(stderr, "Error attaching %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); + fprintf(stderr, "No dynamic shared library support on this platform\n"); + return -1; +#endif + + if (dso->dlHandle == NULL) { + fprintf(stderr, "Error attaching %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); +#if defined(HAVE_DLOPEN) + fprintf(stderr, "%s\n\n", dlerror()); +#else + fprintf(stderr, "%s\n\n", osstrerror()); +#endif + return -1; + } + + /* Get a pointer to the DSO's init function and call it to get the agent's + dispatch table for the DSO. */ + +#if defined(HAVE_DLOPEN) + dso->initFn = (void (*)(pmdaInterface*))dlsym(dso->dlHandle, dso->entryPoint); + if (dso->initFn == NULL) { + fprintf(stderr, "Couldn't find init function `%s' in %s DSO\n", + dso->entryPoint, aPtr->pmDomainLabel); + dlclose(dso->dlHandle); + return -1; + } +#endif + + /* + * 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. + */ + dso->dispatch.domain = aPtr->pmDomainId; + + /* + * 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; + dso->dispatch.comm.pmda_interface = challenge; + dso->dispatch.comm.pmapi_version = ~PMAPI_VERSION; + + dso->dispatch.comm.flags = 0; + dso->dispatch.status = 0; + + (*dso->initFn)(&dso->dispatch); + + if (dso->dispatch.status != 0) { + /* initialization failed for some reason */ + fprintf(stderr, + "Initialization routine %s in %s DSO failed: %s\n", + dso->entryPoint, aPtr->pmDomainLabel, + pmErrStr(dso->dispatch.status)); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + if (dso->dispatch.comm.pmda_interface < PMDA_INTERFACE_2 || + dso->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) { + __pmNotifyErr(LOG_ERR, + "Unknown PMDA interface version (%d) used by DSO %s\n", + dso->dispatch.comm.pmda_interface, aPtr->pmDomainLabel); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + if (dso->dispatch.comm.pmapi_version == PMAPI_VERSION_2) + aPtr->pduVersion = PDU_VERSION2; + else { + __pmNotifyErr(LOG_ERR, + "Unsupported PMAPI version (%d) used by DSO %s\n", + dso->dispatch.comm.pmapi_version, aPtr->pmDomainLabel); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + aPtr->reason = 0; + aPtr->status.connected = 1; + aPtr->status.flags = dso->dispatch.comm.flags; + if (dso->dispatch.comm.flags & PDU_FLAG_AUTH) + ClientsAuthentication(aPtr); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "pmcd: started DSO PMDA %s (%d) using pmPMDA version=%d, " + "PDU version=%d\n", aPtr->pmDomainLabel, aPtr->pmDomainId, + dso->dispatch.comm.pmda_interface, aPtr->pduVersion); +#endif + + return 0; +} + + +/* For creating and establishing contact with agents of the PMCD. */ +static void +ContactAgents(void) +{ + int i; + int sts = 0; + int createdSocketAgents = 0; + AgentInfo *aPtr; + + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->status.connected) + continue; + switch (aPtr->ipcType) { + case AGENT_DSO: + sts = GetAgentDso(aPtr); + break; + + case AGENT_SOCKET: + if (aPtr->ipc.socket.argv) { /* Create agent if required */ + sts = CreateAgent(aPtr); + if (sts >= 0) + createdSocketAgents = 1; + + /* Don't attempt to connect yet, if the agent has just been + created, it will need time to initialise socket. */ + } + else + sts = ConnectSocketAgent(aPtr); + break; /* Connect to existing agent */ + + case AGENT_PIPE: + sts = CreateAgent(aPtr); + break; + } + aPtr->status.connected = sts == 0; + if (aPtr->status.connected) { + if (aPtr->ipcType == AGENT_DSO) + pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, -1, -1); + else + pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd); + MarkStateChanges(PMCD_ADD_AGENT); + aPtr->status.notReady = aPtr->status.startNotReady; + } + else + aPtr->reason = REASON_NOSTART; + } + + /* Allow newly created socket agents time to initialise before attempting + to connect to them. */ + + if (createdSocketAgents) { + sleep(2); /* Allow 2 second for startup */ + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->ipcType == AGENT_SOCKET && + aPtr->ipc.socket.agentPid != (pid_t)-1) { + sts = ConnectSocketAgent(aPtr); + aPtr->status.connected = sts == 0; + if (!aPtr->status.connected) + aPtr->reason = REASON_NOSTART; + } + } + } +} + +int +ParseInitAgents(char *fileName) +{ + int sts; + int i; + FILE *configFile; + struct stat statBuf; + static int firstTime = 1; + + memset(&configFileTime, 0, sizeof(configFileTime)); + configFile = fopen(fileName, "r"); + if (configFile == NULL) + fprintf(stderr, "ParseInitAgents: %s: %s\n", fileName, osstrerror()); + else if (stat(fileName, &statBuf) == -1) + fprintf(stderr, "ParseInitAgents: stat(%s): %s\n", + fileName, osstrerror()); + else { +#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T) + configFileTime = statBuf.st_mtime; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld sec\n", + (long)configFileTime); +#endif +#elif defined(HAVE_ST_MTIME_WITH_SPEC) + configFileTime = statBuf.st_mtimespec; /* struct assignment */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n", + (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec); +#endif +#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T) + configFileTime = statBuf.st_mtim; /* struct assignment */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n", + (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec); +#endif +#else +!bozo! +#endif + } + if (configFile == NULL) + return -1; + + if (firstTime) + if (__pmAccAddOp(PMCD_OP_FETCH) < 0 || __pmAccAddOp(PMCD_OP_STORE) < 0) { + fprintf(stderr, + "ParseInitAgents: __pmAccAddOp: can't create access ops\n"); + exit(1); + } + + sts = ReadConfigFile(configFile); + fclose(configFile); + + /* If pmcd is restarting, don't create/contact the agents until the results + * of the parse can be compared with the previous setup to determine + * whether anything has changed. + */ + if (!firstTime) + return sts; + + firstTime = 0; + if (sts == 0) { + ContactAgents(); + for (i = 0; i < MAXDOMID + 2; i++) + mapdom[i] = nAgents; + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) + mapdom[agent[i].pmDomainId] = i; + } + return sts; +} + +static int +AgentsDiffer(AgentInfo *a1, AgentInfo *a2) +{ + int i; + + if (a1->pmDomainId != a2->pmDomainId) + return 1; + if (a1->ipcType != a2->ipcType) + return 1; + if (a1->ipcType == AGENT_DSO) { + DsoInfo *dso1 = &a1->ipc.dso; + DsoInfo *dso2 = &a2->ipc.dso; + if (strcmp(dso1->pathName, dso2->pathName) != 0) + return 1; + if (dso1->entryPoint == NULL || dso2->entryPoint == NULL) + return 1; /* should never happen */ + if (strcmp(dso1->entryPoint, dso2->entryPoint)) + return 1; + } + else if (a1->ipcType == AGENT_SOCKET) { + SocketInfo *sock1 = &a1->ipc.socket; + SocketInfo *sock2 = &a2->ipc.socket; + + if (sock1 == NULL || sock2 == NULL) + return 1; /* should never happen */ + if (sock1->addrDomain != sock2->addrDomain) + return 1; + /* The names don't really matter, it's the port that counts */ + if (sock1->port != sock2->port) + return 1; + if ((sock1->commandLine == NULL && sock2->commandLine != NULL) || + (sock1->commandLine != NULL && sock2->commandLine == NULL)) + return 1; + if (sock1->argv != NULL && sock2->argv != NULL) { + /* Don't just compare commandLines, changes may be cosmetic */ + for (i = 0; sock1->argv[i] != NULL && sock2->argv[i] != NULL; i++) + if (strcmp(sock1->argv[i], sock2->argv[i])) + return 1; + if (sock1->argv[i] != NULL || sock2->argv[i] != NULL) + return 1; + } + else if ((sock1->argv == NULL && sock2->argv != NULL) || + (sock1->argv != NULL && sock2->argv == NULL)) + return 1; + } + + else { + PipeInfo *pipe1 = &a1->ipc.pipe; + PipeInfo *pipe2 = &a2->ipc.pipe; + + if (pipe1 == NULL || pipe2 == NULL) + return 1; /* should never happen */ + if ((pipe1->commandLine == NULL && pipe2->commandLine != NULL) || + (pipe1->commandLine != NULL && pipe2->commandLine == NULL)) + return 1; + if (pipe1->argv != NULL && pipe2->argv != NULL) { + /* Don't just compare commandLines, changes may be cosmetic */ + for (i = 0; pipe1->argv[i] != NULL && pipe2->argv[i] != NULL; i++) + if (strcmp(pipe1->argv[i], pipe2->argv[i])) + return 1; + if (pipe1->argv[i] != NULL || pipe2->argv[i] != NULL) + return 1; + } + else if ((pipe1->argv == NULL && pipe2->argv != NULL) || + (pipe1->argv != NULL && pipe2->argv == NULL)) + return 1; + } + return 0; +} + +/* Make the "dest" agent the equivalent of an existing "src" agent. + * This assumes that the agents are identical according to AgentsDiffer(), and + * that they have distinct copies of the fields compared therein. + * Note that only the the low level PDU I/O information is copied here. + */ +static void +DupAgent(AgentInfo *dest, AgentInfo *src) +{ + dest->inFd = src->inFd; + dest->outFd = src->outFd; + dest->profClient = src->profClient; + dest->profIndex = src->profIndex; + /* IMPORTANT: copy the status, connections stay connected */ + memcpy(&dest->status, &src->status, sizeof(dest->status)); + if (src->ipcType == AGENT_DSO) { + dest->ipc.dso.dlHandle = src->ipc.dso.dlHandle; + memcpy(&dest->ipc.dso.dispatch, &src->ipc.dso.dispatch, + sizeof(dest->ipc.dso.dispatch)); + /* initFn should never be needed */ + dest->ipc.dso.initFn = (DsoInitPtr)0; + } + else if (src->ipcType == AGENT_SOCKET) + dest->ipc.socket.agentPid = src->ipc.socket.agentPid; + else + dest->ipc.pipe.agentPid = src->ipc.pipe.agentPid; +} + +void +ParseRestartAgents(char *fileName) +{ + int sts; + int i, j; + struct stat statBuf; + AgentInfo *oldAgent; + int oldNAgents; + AgentInfo *ap; + __pmFdSet fds; + + /* Clean up any deceased agents. We haven't seen an agent's death unless + * a PDU transfer involving the agent has occurred. This cleans up others + * as well. + */ + __pmFD_ZERO(&fds); + j = -1; + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.connected && + (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE)) { + + __pmFD_SET(ap->outFd, &fds); + if (ap->outFd > j) + j = ap->outFd; + } + } + if (++j) { + /* any agent with output ready has either closed the file descriptor or + * sent an unsolicited PDU. Clean up the agent in either case. + */ + struct timeval timeout = {0, 0}; + + sts = __pmSelectRead(j, &fds, &timeout); + if (sts > 0) { + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.connected && + (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE) && + __pmFD_ISSET(ap->outFd, &fds)) { + + /* try to discover more ... */ + __pmPDU *pb; + sts = __pmGetPDU(ap->outFd, ANY_SIZE, TIMEOUT_NEVER, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == 0) + pmcd_trace(TR_EOF, ap->outFd, -1, -1); + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, -1, sts); + if (sts > 0) + __pmUnpinPDUBuf(pb); + } + + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + } + else if (sts < 0) + fprintf(stderr, "pmcd: deceased agents select: %s\n", + netstrerror()); + } + + /* gather any deceased children */ + HarvestAgents(0); + + if (stat(fileName, &statBuf) == -1) { + fprintf(stderr, "ParseRestartAgents: stat(%s): %s\n", + fileName, osstrerror()); + fprintf(stderr, "Configuration left unchanged\n"); + return; + } + + /* If the config file's modification time hasn't changed, just try to + * restart any deceased agents + */ +#if defined(HAVE_ST_MTIME_WITH_SPEC) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n", + (long)statBuf.st_mtimespec.tv_sec, (long)statBuf.st_mtimespec.tv_nsec); +#endif + if (statBuf.st_mtimespec.tv_sec == configFileTime.tv_sec && + statBuf.st_mtimespec.tv_nsec == configFileTime.tv_nsec) { +#elif defined(HAVE_STAT_TIMESPEC_T) || defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n", + (long)statBuf.st_mtim.tv_sec, (long)statBuf.st_mtim.tv_nsec); +#endif + if (statBuf.st_mtim.tv_sec == configFileTime.tv_sec && + statBuf.st_mtim.tv_nsec == configFileTime.tv_nsec) { +#elif defined(HAVE_STAT_TIME_T) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld sec\n", + (long)configFileTime); +#endif + if (statBuf.st_mtime == configFileTime) { +#else +!bozo! +#endif + fprintf(stderr, "Configuration file '%s' unchanged\n", fileName); + fprintf(stderr, "Restarting any deceased agents:\n"); + j = 0; + for (i = 0; i < nAgents; i++) + if (!agent[i].status.connected) { + fprintf(stderr, " \"%s\" agent\n", + agent[i].pmDomainLabel); + j++; + } + if (j == 0) + fprintf(stderr, " (no agents required restarting)\n"); + else { + putc('\n', stderr); + ContactAgents(); + for (i = 0; i < nAgents; i++) { + mapdom[agent[i].pmDomainId] = + agent[i].status.connected ? i : nAgents; + } + + MarkStateChanges(PMCD_RESTART_AGENT); + } + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + return; + } + + /* Save the current agent[] and host access tables, Reset the internal + * state of the config file parser and re-parse the config file. + */ + oldAgent = agent; + oldNAgents = nAgents; + agent = NULL; + nAgents = 0; + szAgents = 0; + scanInit = 0; + scanError = 0; + if (__pmAccSaveLists() < 0) { + fprintf(stderr, "Error saving access controls\n"); + sts = -2; + } + else + sts = ParseInitAgents(fileName); + + /* If the config file had errors or there were no valid agents in the new + * config file, ignore it and stick with the old setup. + */ + if (sts < 0 || nAgents == 0) { + if (sts == -1) + fprintf(stderr, + "Configuration file '%s' has errors\n", fileName); + else + fprintf(stderr, + "Configuration file '%s' has no valid agents\n", + fileName); + fprintf(stderr, "Configuration left unchanged\n"); + agent = oldAgent; + nAgents = oldNAgents; + if (sts != -2 && __pmAccRestoreLists() < 0) { + fprintf(stderr, "Error restoring access controls!\n"); + exit(1); + } + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + return; + } + + /* Reconcile the old and new agent tables, creating or destroying agents + * as reqired. + */ + for (j = 0; j < oldNAgents; j++) + oldAgent[j].status.restartKeep = 0; + + for (i = 0; i < nAgents; i++) + for (j = 0; j < oldNAgents; j++) + if (!AgentsDiffer(&agent[i], &oldAgent[j]) && + oldAgent[j].status.connected) { + DupAgent(&agent[i], &oldAgent[j]); + oldAgent[j].status.restartKeep = 1; + } + + for (j = 0; j < oldNAgents; j++) { + if (oldAgent[j].status.connected && !oldAgent[j].status.restartKeep) + CleanupAgent(&oldAgent[j], AT_CONFIG, 0); + FreeAgent(&oldAgent[j]); + } + free(oldAgent); + __pmAccFreeSavedLists(); + + /* Start the new agents */ + ContactAgents(); + for (i = 0; i < MAXDOMID + 2; i++) + mapdom[i] = nAgents; + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) + mapdom[agent[i].pmDomainId] = i; + + /* Now recalculate the access controls for each client and update the + * connection count in the ACL entries matching the client (and account). + * If the client is no longer permitted the connection because of a change + * in permissions or connection limit, the client's connection is closed. + */ + for (i = 0; i < nClients; i++) { + ClientInfo *cp = &client[i]; + + if (!cp->status.connected) + continue; + if ((sts = CheckClientAccess(cp)) >= 0) + sts = CheckAccountAccess(cp); + if (sts < 0) { + /* ignore errors, the client is being terminated in any case */ + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + __pmSendError(cp->fd, FROM_ANON, sts); + CleanupClient(cp, sts); + } + } + + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + + /* Gather any deceased children, some may be PMDAs that were + * terminated by CleanupAgent or killed and had not exited + * when the previous harvest() was done + */ + HarvestAgents(0); +} diff --git a/src/pmcd/src/dofetch.c b/src/pmcd/src/dofetch.c new file mode 100644 index 0000000..0e73fe2 --- /dev/null +++ b/src/pmcd/src/dofetch.c @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * + * 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" +#include "pmcd.h" + +/* Freq. histogram: pmids for each agent in current fetch request */ + +static int *aFreq; + +/* Routine to break a list of pmIDs up into sublists of metrics within the + * same metric domain. The resulting lists are returned via a pointer to an + * array of per-domain lists as defined by the struct below. Any metrics for + * which no agent exists are collected into a list at the end of the list of + * valid lists. This list has domain = -1 and is used to indicate the end of + * the list of pmID lists. + */ + +typedef struct { + int domain; + int listSize; + pmID *list; +} DomPmidList; + +static DomPmidList * +SplitPmidList(int nPmids, pmID *pmidList) +{ + int i, j; + static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */ + static int nDoms = 0; /* No. of entries in two tables above */ + int nGood; + static int currentSize = 0; + int resultSize; + static DomPmidList *result; + pmID *resultPmids; + + /* Allocate the frequency histogram and array for mapping from agent to + * result list index. Because a SIGHUP reconfiguration may have caused a + * change in the number of agents, reallocation using a new size may be + * necessary. + * There are nAgents + 1 entries in the aFreq and resIndex arrays. The + * last entry in each is used for the pmIDs for which no agent could be + * found. + */ + if (nAgents > nDoms) { + nDoms = nAgents; + if (resIndex != NULL) + free(resIndex); + if (aFreq != NULL) + free(aFreq); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + aFreq = (int *)malloc((nAgents + 1) * sizeof(int)); + if (resIndex == NULL || aFreq == NULL) { + __pmNoMem("SplitPmidList.resIndex", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + } + + memset(aFreq, 0, (nAgents + 1) * sizeof(aFreq[0])); + + if (nPmids == 1) { + /* FastTrack this case */ + for (i = 0; i < nAgents; i++) + resIndex[i] = 1; + i = mapdom[((__pmID_int *)&pmidList[0])->domain]; + aFreq[i] = 1; + resIndex[i] = 0; + nGood = i == nAgents ? 0 : 1; + goto doit; + } + + /* + * Build a frequency histogram of metric domains (use aFreq[nAgents], + * via mapdom[] for pmids for which there is no agent). + */ + for (i = 0; i < nPmids; i++) { + j = mapdom[((__pmID_int *)&pmidList[i])->domain]; + aFreq[j]++; + } + + /* Build the mapping between agent index and the position of the agent's + * subset of the pmidList in the returned result's DomPmidList. + */ + nGood = 0; + for (i = 0; i < nAgents; i++) + if (aFreq[i]) + nGood++; + + /* nGood is the number of "valid" result pmid lists. It is also the INDEX + * of the BAD list in the resulting list of DomPmidLists). + */ + j = 0; + for (i = 0; i < nAgents; i++) + resIndex[i] = (aFreq[i]) ? j++ : nGood; + resIndex[nAgents] = nGood; /* For the "bad" list */ + + /* Now malloc up a single heap block for the resulting list of pmID lists. + * First is a list of (nDoms + 1) DomPmidLists (the last is a sentinel with + * a domain of -1), then come the pmID lists pointed to by the + * DomPmidLists. + */ +doit: + resultSize = (nGood + 1) * (int)sizeof(DomPmidList); + resultSize += nPmids * sizeof(pmID); + if (resultSize > currentSize) { + if (currentSize > 0) + free(result); + result = (DomPmidList *)malloc(resultSize); + if (result == NULL) { + __pmNoMem("SplitPmidList.result", resultSize, PM_FATAL_ERR); + } + currentSize = resultSize; + } + + resultPmids = (pmID *)&result[nGood + 1]; + if (nPmids == 1) { + /* more FastTrack */ + if (nGood) { + /* domain known, otherwise things fixed up below */ + i = mapdom[((__pmID_int *)&pmidList[0])->domain]; + j = resIndex[i]; + result[j].domain = agent[i].pmDomainId; + result[j].listSize = 0; + result[j].list = resultPmids; + resultPmids++; + } + } + else { + for (i = 0; i < nAgents; i++) { + if (aFreq[i]) { + j = resIndex[i]; + result[j].domain = agent[i].pmDomainId; + result[j].listSize = 0; + result[j].list = resultPmids; + resultPmids += aFreq[i]; + } + } + } + result[nGood].domain = -1; /* Set up the "bad" list */ + result[nGood].listSize = 0; + result[nGood].list = resultPmids; + + for (i = 0; i < nPmids; i++) { + j = resIndex[mapdom[((__pmID_int *)&pmidList[i])->domain]]; + result[j].list[result[j].listSize++] = pmidList[i]; + } + return result; +} + +/* Build a pmResult indicating that no values are available for the pmID list + * supplied. + */ + +static pmResult * +MakeBadResult(int npmids, pmID *list, int sts) +{ + int need; + int i; + pmValueSet *vSet; + pmResult *result; + + need = (int)sizeof(pmResult) + + (npmids - 1) * (int)sizeof(pmValueSet *); + /* npmids - 1 because there is already 1 pmValueSet* in a pmResult */ + result = (pmResult *)malloc(need); + if (result == NULL) { + __pmNoMem("MakeBadResult.result", need, PM_FATAL_ERR); + } + result->numpmid = npmids; + for (i = 0; i < npmids; i++) { + vSet = (pmValueSet *)malloc(sizeof(pmValueSet)); + if (vSet == NULL) { + __pmNoMem("MakeBadResult.vSet", sizeof(pmValueSet), PM_FATAL_ERR); + } + result->vset[i] = vSet; + vSet->pmid = list[i]; + vSet->numval = sts; + } + return result; +} + +static pmResult * +SendFetch(DomPmidList *dpList, AgentInfo *aPtr, ClientInfo *cPtr, int ctxnum) +{ + pmResult *result = NULL; + int sts = 0; + static __pmTimeval when = {0, 0}; /* Agents never see archive requests */ + int bad = 0; + int i; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "SendFetch %d metrics to PMDA domain %d ", + dpList->listSize, dpList->domain); + switch (aPtr->ipcType) { + case AGENT_DSO: + fprintf(stderr, "(dso)\n"); + break; + + case AGENT_SOCKET: + fprintf(stderr, "(socket)\n"); + break; + + case AGENT_PIPE: + fprintf(stderr, "(pipe)\n"); + break; + + default: + fprintf(stderr, "(type %d unknown!)\n", aPtr->ipcType); + break; + } + for (i = 0; i < dpList->listSize; i++) + fprintf(stderr, " pmid[%d] %s\n", i, pmIDStr(dpList->list[i])); + } +#endif + + /* status.madeDsoResult is only used for DSO agents so don't waste time by + * checking that the agent is a DSO first. + */ + aPtr->status.madeDsoResult = 0; + + if (aPtr->profClient != cPtr || ctxnum != aPtr->profIndex) { + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client; + sts = aPtr->ipc.dso.dispatch.version.any.profile(cPtr->profile[ctxnum], + aPtr->ipc.dso.dispatch.version.any.ext); + } + else { + if (aPtr->status.notReady == 0) { + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_PROFILE, ctxnum); + if ((sts = __pmSendProfile(aPtr->inFd, cPtr - client, + ctxnum, cPtr->profile[ctxnum])) < 0) { + pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_PROFILE, sts); + } + } else { + sts = PM_ERR_AGAIN; + } + + } + if (sts >= 0) { + aPtr->profClient = cPtr; + aPtr->profIndex = ctxnum; + } + } + + if (sts >= 0) { + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client; + sts = aPtr->ipc.dso.dispatch.version.any.fetch(dpList->listSize, + dpList->list, &result, + aPtr->ipc.dso.dispatch.version.any.ext); + if (sts >= 0) { + if (result == NULL) { + __pmNotifyErr(LOG_WARNING, + "\"%s\" agent (DSO) returned a null result\n", + aPtr->pmDomainLabel); + sts = PM_ERR_PMID; + bad = 1; + } + else { + if (result->numpmid != dpList->listSize) { + __pmNotifyErr(LOG_WARNING, + "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n", + aPtr->pmDomainLabel, + result->numpmid,dpList->listSize); + sts = PM_ERR_PMID; + bad = 2; + } + } + } + } + else { + if (aPtr->status.notReady == 0) { + /* agent is ready for PDUs */ + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_FETCH, dpList->listSize); + if ((sts = __pmSendFetch(aPtr->inFd, cPtr - client, ctxnum, &when, + dpList->listSize, dpList->list)) < 0) + pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_FETCH, sts); + } + else { + /* agent is not ready for PDUs */ + sts = PM_ERR_AGAIN; + } + } + } + + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + switch (bad) { + case 0: + fprintf(stderr, "FETCH error: \"%s\" agent : %s\n", + aPtr->pmDomainLabel, pmErrStr(sts)); + break; + case 1: + fprintf(stderr, "\"%s\" agent (DSO) returned a null result\n", + aPtr->pmDomainLabel); + break; + case 2: + fprintf(stderr, "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n", + aPtr->pmDomainLabel, + result->numpmid, dpList->listSize); + break; + } +#endif + if (aPtr->ipcType == AGENT_DSO) { + aPtr->status.madeDsoResult = 1; + sts = 0; + } + else if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) + CleanupAgent(aPtr, AT_COMM, aPtr->inFd); + + result = MakeBadResult(dpList->listSize, dpList->list, sts); + } + + return result; +} + +int +DoFetch(ClientInfo *cip, __pmPDU* pb) +{ + int i, j; + int sts; + int ctxnum; + __pmTimeval when; + int nPmids; + pmID *pmidList; + static pmResult *endResult = NULL; + static int maxnpmids = 0; /* sizes endResult */ + DomPmidList *dList; /* NOTE: NOT indexed by agent index */ + static int nDoms = 0; + static pmResult **results = NULL; + static int *resIndex = NULL; + __pmFdSet waitFds; + __pmFdSet readyFds; + int nWait; + int maxFd; + struct timeval timeout; + + if (nAgents > nDoms) { + if (results != NULL) + free(results); + if (resIndex != NULL) + free(resIndex); + results = (pmResult **)malloc((nAgents + 1) * sizeof (pmResult *)); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + if (results == NULL || resIndex == NULL) { + __pmNoMem("DoFetch.results", (nAgents + 1) * sizeof (pmResult *) + (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + nDoms = nAgents; + } + memset(results, 0, (nAgents + 1) * sizeof(results[0])); + + sts = __pmDecodeFetch(pb, &ctxnum, &when, &nPmids, &pmidList); + if (sts < 0) + return sts; + + /* Check that a profile has been received from the specified context */ + if (ctxnum < 0 || ctxnum >= cip->szProfile || + cip->profile[ctxnum] == NULL) { + __pmUnpinPDUBuf(pb); + if (ctxnum < 0 || ctxnum >= cip->szProfile) + __pmNotifyErr(LOG_ERR, "DoFetch: bad ctxnum=%d\n", ctxnum); + else + __pmNotifyErr(LOG_ERR, "DoFetch: no profile for ctxnum=%d\n", ctxnum); + return PM_ERR_NOPROFILE; + } + + if (nPmids > maxnpmids) { + int need; + if (endResult != NULL) + free(endResult); + need = (int)sizeof(pmResult) + (nPmids - 1) * (int)sizeof(pmValueSet *); + if ((endResult = (pmResult *)malloc(need)) == NULL) { + __pmNoMem("DoFetch.endResult", need, PM_FATAL_ERR); + } + maxnpmids = nPmids; + } + + dList = SplitPmidList(nPmids, pmidList); + + /* For each domain in the split pmidList, dispatch the per-domain subset + * of pmIDs to the appropriate agent. For DSO agents, the pmResult will + * come back immediately. If a request cannot be sent to an agent, a + * suitable pmResult (containing metric not available values) will be + * returned. + */ + __pmFD_ZERO(&waitFds); + nWait = 0; + maxFd = -1; + for (i = 0; dList[i].domain != -1; i++) { + j = mapdom[dList[i].domain]; + results[j] = SendFetch(&dList[i], &agent[j], cip, ctxnum); + if (results[j] == NULL) { /* Wait for agent's response */ + int fd = agent[j].outFd; + agent[j].status.busy = 1; + __pmFD_SET(fd, &waitFds); + if (fd > maxFd) + maxFd = fd; + nWait++; + } + } + /* Construct pmResult for bad-pmID list */ + if (dList[i].listSize != 0) + results[nAgents] = MakeBadResult(dList[i].listSize, dList[i].list, PM_ERR_NOAGENT); + + /* Wait for results to roll in from agents */ + while (nWait > 0) { + __pmFD_COPY(&readyFds, &waitFds); + if (nWait > 1) { + timeout.tv_sec = _pmcd_timeout; + timeout.tv_usec = 0; + + sts = __pmSelectRead(maxFd+1, &readyFds, &timeout); + + if (sts == 0) { + __pmNotifyErr(LOG_INFO, "DoFetch: select timeout"); + + /* Timeout, terminate agents with undelivered results */ + for (i = 0; i < nAgents; i++) { + if (agent[i].status.busy) { + /* Find entry in dList for this agent */ + for (j = 0; dList[j].domain != -1; j++) + if (dList[j].domain == agent[i].pmDomainId) + break; + results[i] = MakeBadResult(dList[j].listSize, + dList[j].list, + PM_ERR_NOAGENT); + pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_RESULT, 0); + CleanupAgent(&agent[i], AT_COMM, agent[i].inFd); + } + } + break; + } + else if (sts < 0) { + /* this is not expected to happen! */ + __pmNotifyErr(LOG_ERR, "DoFetch: fatal select failure: %s\n", + netstrerror()); + Shutdown(); + exit(1); + } + } + + /* Read results from agents that have them ready */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + int pinpdu; + if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds)) + continue; + ap->status.busy = 0; + __pmFD_CLR(ap->outFd, &waitFds); + nWait--; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_RESULT) { + if ((sts = __pmDecodeResult(pb, &results[i])) >= 0) + if (results[i]->numpmid != aFreq[i]) { + pmFreeResult(results[i]); + sts = PM_ERR_IPC; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_ERR, "DoFetch: \"%s\" agent given %d pmIDs, returned %d\n", + ap->pmDomainLabel, aFreq[i], results[i]->numpmid); +#endif + } + } + else { + if (sts == PDU_ERROR) { + int s; + if ((s = __pmDecodeError(pb, &sts)) < 0) + sts = s; + else if (sts >= 0) + sts = PM_ERR_GENERIC; + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + } + else if (sts >= 0) { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_RESULT, sts); + sts = PM_ERR_IPC; + } + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (sts < 0) { + /* Find entry in dList for this agent */ + for (j = 0; dList[j].domain != -1; j++) + if (dList[j].domain == agent[i].pmDomainId) + break; + results[i] = MakeBadResult(dList[j].listSize, + dList[j].list, sts); + + if (sts == PM_ERR_PMDANOTREADY) { + /* the agent is indicating it can't handle PDUs for now */ + int k; + extern int CheckError(AgentInfo *ap, int sts); + + for (k = 0; k < dList[j].listSize; k++) + results[i]->vset[k]->numval = PM_ERR_AGAIN; + sts = CheckError(&agent[i], sts); + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "RESULT error from \"%s\" agent : %s\n", + ap->pmDomainLabel, pmErrStr(sts)); + } +#endif + if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT) + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + } + + endResult->numpmid = nPmids; + __pmtimevalNow(&endResult->timestamp); + /* The order of the pmIDs in the per-domain results is the same as in the + * original request, but on a per-domain basis. resIndex is an array of + * indeces (one per agent) of the next metric to be retrieved from each + * per-domain result's vset. + */ + memset(resIndex, 0, (nAgents + 1) * sizeof(resIndex[0])); + + for (i = 0; i < nPmids; i++) { + j = mapdom[((__pmID_int *)&pmidList[i])->domain]; + endResult->vset[i] = results[j]->vset[resIndex[j]++]; + } + pmcd_trace(TR_XMIT_PDU, cip->fd, PDU_RESULT, endResult->numpmid); + + sts = 0; + if (cip->status.changes) { + /* notify client of PMCD state change */ + sts = __pmSendError(cip->fd, FROM_ANON, (int)cip->status.changes); + if (sts > 0) + sts = 0; + cip->status.changes = 0; + } + if (sts == 0) + sts = __pmSendResult(cip->fd, FROM_ANON, endResult); + + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cip->fd, PDU_RESULT, sts); + CleanupClient(cip, sts); + } + + /* + * pmFreeResult() all the accumulated results. + */ + for (i = 0; dList[i].domain != -1; i++) { + j = mapdom[dList[i].domain]; + if (agent[j].ipcType == AGENT_DSO && agent[j].status.connected && + !agent[j].status.madeDsoResult) + /* Living DSO's manage their own pmResult skeleton unless + * MakeBadResult was called to create the result. The value sets + * within the skeleton need to be freed though! + */ + __pmFreeResultValues(results[j]); + else + /* For others it is dynamically allocated in __pmDecodeResult or + * MakeBadResult + */ + pmFreeResult(results[j]); + } + if (results[nAgents] != NULL) + pmFreeResult(results[nAgents]); + __pmUnpinPDUBuf(pmidList); + return 0; +} diff --git a/src/pmcd/src/dopdus.c b/src/pmcd/src/dopdus.c new file mode 100644 index 0000000..7eb71c4 --- /dev/null +++ b/src/pmcd/src/dopdus.c @@ -0,0 +1,1057 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * 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 "pmcd.h" + +/* Check returned error from a client. + * If client returns ready/not_ready status change, check then update agent + * status. + * If the client goes from not_ready to ready, it sends an unsolicited error + * PDU. If this happens, the retry flag indicates that the expected response + * is yet to arrive, and that the caller should try reading + * and the expected response will follow it. + */ +int +CheckError(AgentInfo *ap, int sts) +{ + int retSts; + + if (sts == PM_ERR_PMDANOTREADY) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "%s agent (%s) sent NOT READY\n", + ap->pmDomainLabel, + ap->status.notReady ? "not ready" : "ready"); +#endif + if (ap->status.notReady == 0) { + ap->status.notReady = 1; + retSts = PM_ERR_AGAIN; + } + else + retSts = PM_ERR_IPC; + } + else if (sts == PM_ERR_PMDAREADY) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "%s agent (%s) sent unexpected READY\n", + ap->pmDomainLabel, + ap->status.notReady ? "not ready" : "ready"); +#endif + retSts = PM_ERR_IPC; + } + else + retSts = sts; + + return retSts; +} + +int +DoText(ClientInfo *cp, __pmPDU* pb) +{ + int sts, s; + int ident; + int type; + AgentInfo *ap; + char *buffer = NULL; + + if ((sts = __pmDecodeTextReq(pb, &ident, &type)) < 0) + return sts; + + if ((ap = FindDomainAgent(((__pmID_int *)&ident)->domain)) == NULL) + return PM_ERR_PMID; + else if (!ap->status.connected) + return PM_ERR_NOAGENT; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.text(ident, type, &buffer, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_TEXT_REQ, ident); + sts = __pmSendTextReq(ap->inFd, cp - client, ident, type); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_TEXT) + sts = __pmDecodeText(pb, &ident, &buffer); + else if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_TEXT, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_TEXT, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_TEXT_REQ, sts); + } + + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE)) + CleanupAgent(ap, AT_COMM, ap->inFd); + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_TEXT, ident); + sts = __pmSendText(cp->fd, FROM_ANON, ident, buffer); + if (sts < 0 && ap->ipcType != AGENT_DSO) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_TEXT, sts); + CleanupClient(cp, sts); + } + if (ap->ipcType != AGENT_DSO) { + /* daemon PMDAs have a malloc'd buffer */ + free(buffer); + } + } + return sts; +} + +int +DoProfile(ClientInfo *cp, __pmPDU *pb) +{ + __pmProfile *newProf; + int ctxnum, sts, i; + + sts = __pmDecodeProfile(pb, &ctxnum, &newProf); + if (sts >= 0) { + /* Allocate more profile pointers if required */ + if (ctxnum >= cp->szProfile) { + __pmProfile **newProfPtrs; + int need, oldSize = cp->szProfile; + + if (ctxnum - cp->szProfile < 4) + cp->szProfile += 4; + else + cp->szProfile = ctxnum + 1; + need = cp->szProfile * (int)sizeof(__pmProfile *); + if ((newProfPtrs = (__pmProfile **)malloc(need)) == NULL) { + cp->szProfile = oldSize; + __pmNoMem("DoProfile.newProfPtrs", need, PM_RECOV_ERR); + __pmFreeProfile(newProf); + return -oserror(); + } + + /* Copy any old pointers and zero the newly allocated ones */ + if ((need = oldSize * (int)sizeof(__pmProfile *))) { + memcpy(newProfPtrs, cp->profile, need); + free(cp->profile); /* But not the __pmProfile ptrs! */ + } + need = (cp->szProfile - oldSize) * (int)sizeof(__pmProfile *); + memset(&newProfPtrs[oldSize], 0, need); + cp->profile = newProfPtrs; + } + else /* cp->profile is big enough */ + if (cp->profile[ctxnum] != NULL) + __pmFreeProfile(cp->profile[ctxnum]); + cp->profile[ctxnum] = newProf; + + /* "Invalidate" any references to the client context's profile in the + * agents to which the old profile was last sent + */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + + if (ap->profClient == cp && ap->profIndex == ctxnum) + ap->profClient = NULL; + } + } + return sts; +} + +int +DoDesc(ClientInfo *cp, __pmPDU *pb) +{ + int sts, s; + pmID pmid; + AgentInfo *ap; + pmDesc desc = {0}; + int fdfail = -1; + + if ((sts = __pmDecodeDescReq(pb, &pmid)) < 0) + return sts; + + if ((ap = FindDomainAgent(((__pmID_int *)&pmid)->domain)) == NULL) + return PM_ERR_PMID; + else if (!ap->status.connected) + return PM_ERR_NOAGENT; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.desc(pmid, &desc, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_DESC_REQ, (int)pmid); + sts = __pmSendDescReq(ap->inFd, cp - client, pmid); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_DESC) + sts = __pmDecodeDesc(pb, &desc); + else if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_DESC, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_DESC, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_DESC_REQ, sts); + fdfail = ap->inFd; + } + } + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_DESC, (int)desc.pmid); + sts = __pmSendDesc(cp->fd, FROM_ANON, &desc); + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_DESC, sts); + CleanupClient(cp, sts); + } + } + else + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + + return sts; +} + +int +DoInstance(ClientInfo *cp, __pmPDU* pb) +{ + int sts, s; + __pmTimeval when; + pmInDom indom; + int inst; + char *name; + __pmInResult *inresult = NULL; + AgentInfo *ap; + int fdfail = -1; + + sts = __pmDecodeInstanceReq(pb, &when, &indom, &inst, &name); + if (sts < 0) + return sts; + if (when.tv_sec != 0 || when.tv_usec != 0) { + /* + * we have no idea how to do anything but current, yet! + * + * TODO EXCEPTION PCP 2.0 ... + * this may be left over from the pmvcr days, and can be tossed? + * ... leaving it here is benign + */ + if (name != NULL) free(name); + return PM_ERR_NYI; + } + if ((ap = FindDomainAgent(((__pmInDom_int *)&indom)->domain)) == NULL) { + if (name != NULL) free(name); + return PM_ERR_INDOM; + } + else if (!ap->status.connected) { + if (name != NULL) free(name); + return PM_ERR_NOAGENT; + } + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.instance(indom, inst, name, + &inresult, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) { + if (name != NULL) free(name); + return PM_ERR_AGAIN; + } + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_INSTANCE_REQ, (int)indom); + sts = __pmSendInstanceReq(ap->inFd, cp - client, &when, indom, inst, name); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_INSTANCE) + sts = __pmDecodeInstance(pb, &inresult); + else if (sts == PDU_ERROR) { + inresult = NULL; + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_INSTANCE, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_INSTANCE, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_INSTANCE_REQ, sts); + fdfail = ap->inFd; + } + } + if (name != NULL) free(name); + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_INSTANCE, (int)(inresult->indom)); + sts = __pmSendInstance(cp->fd, FROM_ANON, inresult); + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_INSTANCE, sts); + CleanupClient(cp, sts); + } + __pmFreeInResult(inresult); + } + else + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + + return sts; +} + +/* + * This handler is for remote versions of pmNameAll or pmNameID. + * Note: only one pmid for the list should be sent. + */ +int +DoPMNSIDs(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int op_sts = 0; + int numnames = 0; + pmID idlist[1]; + char **namelist = NULL; + AgentInfo *ap = NULL; + int fdfail = -1; + + if ((sts = __pmDecodeIDList(pb, 1, idlist, &op_sts)) < 0) + goto fail; + + if ((sts = pmNameAll(idlist[0], &namelist)) < 0) { + /* + * failure may be a real failure, or could be a metric within a + * dynamic sutree of the PMNS + */ + if ((ap = FindDomainAgent(((__pmID_int *)&idlist[0])->domain)) == NULL) { + sts = PM_ERR_NOAGENT; + goto fail; + } + if (!ap->status.connected) { + sts = PM_ERR_NOAGENT; + goto fail; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.name(idlist[0], &namelist, + ap->ipc.dso.dispatch.version.four.ext); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + sts = PM_ERR_PMID; + } + } + else { + /* daemon PMDA ... ship request on */ + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_IDS, 1); + sts = __pmSendIDList(ap->inFd, cp - client, 1, &idlist[0], 0); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, &namelist, NULL); + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendIDList failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + } + if (sts < 0) goto fail; + } + + numnames = sts; + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, namelist, NULL)) < 0){ + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + goto fail; + } + /* fall through OK */ + +fail: + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + if (namelist) free(namelist); + return sts; +} + +/* + * This handler is for the remote version of pmLookupName. + */ +int +DoPMNSNames(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int numids = 0; + pmID *idlist = NULL; + char **namelist = NULL; + int i; + AgentInfo *ap = NULL; + + if ((sts = __pmDecodeNameList(pb, &numids, &namelist, NULL)) < 0) + goto done; + + if ((idlist = (pmID *)calloc(numids, sizeof(int))) == NULL) { + sts = -oserror(); + goto done; + } + + sts = pmLookupName(numids, namelist, idlist); + for (i = 0; i < numids; i++) { + if (idlist[i] == PM_ID_NULL) continue; + if (pmid_domain(idlist[i]) == DYNAMIC_PMID && pmid_item(idlist[i]) == 0) { + int lsts; + int domain = pmid_cluster(idlist[i]); + /* + * don't return <domain>.*.* ... all return paths from here + * must either set a valid PMID in idlist[i] or indicate + * the first error in the return from pmLookupName + */ + idlist[i] = PM_ID_NULL; /* default case if cannot translate */ + if ((ap = FindDomainAgent(domain)) == NULL) { + if (sts > 0) sts = PM_ERR_NOAGENT; + continue; + } + if (!ap->status.connected) { + if (sts > 0) sts = PM_ERR_NOAGENT; + continue; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + lsts = ap->ipc.dso.dispatch.version.four.pmid(namelist[i], &idlist[i], + ap->ipc.dso.dispatch.version.four.ext); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + lsts = PM_ERR_NAME; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + lsts = PM_ERR_AGAIN; + else { + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_NAMES, 1); + lsts = __pmSendNameList(ap->inFd, cp - client, 1, &namelist[i], NULL); + if (lsts >= 0) { + int pinpdu; + pinpdu = lsts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (lsts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (lsts == PDU_PMNS_IDS) { + int xsts; + lsts = __pmDecodeIDList(pb, 1, &idlist[i], &xsts); + if (lsts >= 0) + lsts = xsts; + } + else if (lsts == PDU_ERROR) { + __pmDecodeError(pb, &lsts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_IDS, lsts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts); + lsts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendNameList failed */ + lsts = __pmMapErrno(lsts); + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_PMNS_NAMES, lsts); + fdfail = ap->inFd; + } + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (lsts == PM_ERR_IPC || lsts == PM_ERR_TIMEOUT || lsts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + /* + * only set error status to the current error status + * if this is the first error for this set of metrics + */ + if (lsts < 0 && sts > 0) sts = lsts; + } + } + + if (sts < 0) { + /* If get an error which should be passed back along + * with valid data to the client + * then do NOT fail -> return status with the id-list. + */ + if (sts != PM_ERR_NAME && sts != PM_ERR_NONLEAF && sts != PM_ERR_NOAGENT) + goto done; + } + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_IDS, numids); + if ((sts = __pmSendIDList(cp->fd, FROM_ANON, numids, idlist, sts)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_IDS, sts); + CleanupClient(cp, sts); + goto done; + } + +done: + if (idlist) free(idlist); + if (namelist) free(namelist); + + return sts; +} + +/* + * This handler is for the remote version of pmGetChildren. + */ +int +DoPMNSChild(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int numnames = 0; + char *name = NULL; + char **offspring = NULL; + int *statuslist = NULL; + int subtype; + char *namelist[1]; + pmID idlist[1]; + + if ((sts = __pmDecodeChildReq(pb, &name, &subtype)) < 0) + goto done; + + namelist[0] = name; + sts = pmLookupName(1, namelist, idlist); + if (sts == 1 && pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) { + int domain = pmid_cluster(idlist[0]); + AgentInfo *ap = NULL; + if ((ap = FindDomainAgent(domain)) == NULL) { + sts = PM_ERR_NOAGENT; + goto done; + } + if (!ap->status.connected) { + sts = PM_ERR_NOAGENT; + goto done; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.children(name, 0, &offspring, &statuslist, + ap->ipc.dso.dispatch.version.four.ext); + if (sts < 0) + goto done; + if (subtype == 0) { + if (statuslist) free(statuslist); + statuslist = NULL; + } + } + else { + /* Not PMDA_INTERFACE_4 or later */ + sts = PM_ERR_NAME; + goto done; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + sts = PM_ERR_AGAIN; + else { + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_CHILD, 1); + sts = __pmSendChildReq(ap->inFd, cp - client, name, subtype); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, + &offspring, &statuslist); + if (sts >= 0) { + sts = numnames; + if (subtype == 0) { + free(statuslist); + statuslist = NULL; + } + } + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendChildReq failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + } + else { + if (subtype == 0) { + if ((sts = pmGetChildren(name, &offspring)) < 0) + goto done; + } + else { + if ((sts = pmGetChildrenStatus(name, &offspring, &statuslist)) < 0) + goto done; + } + } + + numnames = sts; + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, offspring, statuslist)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + } + +done: + if (name) free(name); + if (offspring) free(offspring); + if (statuslist) free(statuslist); + return sts; +} + +/*************************************************************************/ + +static char **travNL; /* list of names for traversal */ +static char *travNL_ptr; /* pointer into travNL */ +static int travNL_num; /* number of names in list */ +static int travNL_strlen; /* number of bytes of names */ +static int travNL_i; /* array index */ + +static void +AddLengths(const char *name) +{ + travNL_strlen += strlen(name) + 1; + travNL_num++; +} + +static void +BuildNameList(const char *name) +{ + travNL[travNL_i++] = travNL_ptr; + strcpy(travNL_ptr, name); + travNL_ptr += strlen(name) + 1; +} + +/* + * handle dynamic PMNS entries in remote version of pmTraversePMNS. + * + * num_names and names[] is the result of pmTraversePMNS for the + * loaded PMNS ... need to preserve the semantics of this in the + * end result, so names[] and all of the name[i] strings are in a + * single malloc block + */ +static void +traverse_dynamic(ClientInfo *cp, char *start, int *num_names, char ***names) +{ + int sts; + int i; + char **offspring; + int *statuslist; + char *namelist[1]; + pmID idlist[1]; + int fake = 0; + + /* + * if we get any errors in the setup (unexpected), simply skip + * that name[i] entry and move on ... any client using the associated + * name[i] will get an error later, e.g. when trying to fetch the + * pmDesc + * + * process in reverse order so stitching does not disturb the ones + * we've not processed yet + */ + if (*num_names == 0) { + /* + * special case, where starting point is _below_ the dynamic + * node in the PMNS known to pmcd (or name is simply invalid) ... + * fake a single name in the list so far ... names[] does not hold + * the string value as well, but this is OK because names[0] will + * be rebuilt * replacing "name" (or cleaned up at the end) ... + * note travNL_strlen initialization so resize below is correct + */ + fake = 1; + *names = (char **)malloc(sizeof((*names)[0])); + if (*names == NULL) + return; + (*names)[0] = start; + *num_names = 1; + travNL_strlen = strlen(start) + 1; + } + for (i = *num_names-1; i >= 0; i--) { + offspring = NULL; + namelist[0] = (*names)[i]; + sts = pmLookupName(1, namelist, idlist); + if (sts < 1) + continue; + if (pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) { + int domain = pmid_cluster(idlist[0]); + AgentInfo *ap; + if ((ap = FindDomainAgent(domain)) == NULL) + continue; + if (!ap->status.connected) + continue; + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.children(namelist[0], 1, &offspring, &statuslist, + ap->ipc.dso.dispatch.version.four.ext); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "traverse_dynamic: DSO PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0])); + if (sts < 0) + fprintf(stderr, "%s\n", pmErrStr(sts)); + else { + int j; + fprintf(stderr, "%d names\n", sts); + for (j = 0; j < sts; j++) { + fprintf(stderr, " %s\n", offspring[j]); + } + } + } +#endif + if (sts < 0) + continue; + if (statuslist) free(statuslist); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + continue; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + continue; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_TRAVERSE, 1); + sts = __pmSendTraversePMNSReq(ap->inFd, cp - client, namelist[0]); + if (sts >= 0) { + int numnames; + __pmPDU *pb; + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, + &offspring, &statuslist); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "traverse_dynamic: daemon PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0])); + if (sts < 0) + fprintf(stderr, "%s\n", pmErrStr(sts)); + else { + int j; + fprintf(stderr, "%d names\n", sts); + for (j = 0; j < sts; j++) { + fprintf(stderr, " %s\n", offspring[j]); + } + } + } +#endif + if (statuslist) { + free(statuslist); + statuslist = NULL; + } + if (sts >= 0) { + sts = numnames; + } + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendChildReq failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + } + /* Stitching ... remove names[i] and add sts names from offspring[] */ + if (offspring) { + int j; + int k; /* index for copying to new[] */ + int ii; /* index for copying from names[] */ + char **new; + char *p; /* string copy dest ptr */ + int new_len; + + fake = 0; /* don't need to undo faking */ + new_len = travNL_strlen - strlen(namelist[0]) - 1; + for (j = 0; j < sts; j++) + new_len += strlen(offspring[j]) + 1; + new = (char **)malloc(new_len + (*num_names - 1 + sts)*sizeof(new[0])); + if (new == NULL) { + /* tough luck! */ + free(offspring); + continue; + } + *num_names = *num_names - 1 + sts; + p = (char *)&new[*num_names]; + ii = 0; + for (k = 0; k < *num_names; k++) { + if (k < i || k >= i+sts) { + /* copy across old name */ + if (k == i+sts) + ii++; /* skip name than new ones replaced */ + strcpy(p, (*names)[ii]); + ii++; + } + else { + /* stitch in new name */ + strcpy(p, offspring[k-i]); + } + new[k] = p; + p += strlen(p) + 1; + } + + free(offspring); + free(*names); + *names = new; + travNL_strlen = new_len; + } + } + + if (fake == 1) { + /* + * need to undo initial faking as this name is simply not valid! + */ + *num_names = 0; + free(*names); + *names = NULL; + travNL_strlen = 0; + } + +} + +/* + * This handler is for the remote version of pmTraversePMNS. + * + * Notes: + * We are building up a name-list and giving it to + * __pmSendNameList. + * This is a bit inefficient but convenient. + * It would really be better to build up a PDU buffer + * directly and not do the extra copying ! + */ +int +DoPMNSTraverse(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + char *name = NULL; + int travNL_need = 0; + + travNL = NULL; + + if ((sts = __pmDecodeTraversePMNSReq(pb, &name)) < 0) + goto done; + + travNL_strlen = 0; + travNL_num = 0; + if ((sts = pmTraversePMNS(name, AddLengths)) < 0) + goto check; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "DoPMNSTraverse: %d names below %s after pmTraversePMNS\n", travNL_num, name); + } +#endif + + /* for each ptr, string bytes, and string terminators */ + travNL_need = travNL_num * (int)sizeof(char*) + travNL_strlen; + + if ((travNL = (char**)malloc(travNL_need)) == NULL) { + sts = -oserror(); + goto done; + } + + travNL_i = 0; + travNL_ptr = (char*)&travNL[travNL_num]; + sts = pmTraversePMNS(name, BuildNameList); + +check: + /* + * sts here is last result of calling pmTraversePMNS() ... may need + * this later + * for dynamic PMNS entries, travNL_num will be 0 (PM_ERR_PMID from + * pmTraversePMNS()). + */ + traverse_dynamic(cp, name, &travNL_num, &travNL); + if (travNL_num < 1) + goto done; + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, travNL_num); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, travNL_num, travNL, NULL)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + goto done; + } + +done: + if (name) free(name); + if (travNL) free(travNL); + return sts; +} + +/*************************************************************************/ + +int +DoCreds(ClientInfo *cp, __pmPDU *pb) +{ + int i, sts, flags = 0, version = 0, sender = 0, credcount = 0; + __pmCred *credlist = NULL; + __pmVersionCred *vcp; + + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) + return sts; + pmcd_trace(TR_RECV_PDU, cp->fd, PDU_CREDS, credcount); + + for (i = 0; i < credcount; i++) { + switch(credlist[i].c_type) { + case CVERSION: + vcp = (__pmVersionCred *)&credlist[i]; + flags = vcp->c_flags; + version = vcp->c_version; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: version cred (%u) flags=%x\n", vcp->c_version, vcp->c_flags); +#endif + break; + + default: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: Error: bogus cred type %d\n", credlist[i].c_type); +#endif + sts = PM_ERR_IPC; + break; + } + } + if (credlist != NULL) + free(credlist); + + if (sts >= 0 && version) + sts = __pmSetVersionIPC(cp->fd, version); + if (sts >= 0 && flags) { + /* + * new client has arrived; may want encryption, authentication, etc + * complete the handshake (depends on features requested), continue + * on to check access is allowed for the authenticated persona, and + * finally notify any interested PMDAs + */ + if ((sts = __pmSecureServerHandshake(cp->fd, flags, &cp->attrs)) < 0) + return sts; + } + if ((sts = CheckAccountAccess(cp)) < 0) /* host access done already */ + return sts; + else if (sts > 0) /* account authentication successful - inform PMDAs */ + sts = AgentsAuthentication(cp - client); + /* else: no account-based authentication needed, so finish successfully */ + + return sts; +} diff --git a/src/pmcd/src/dostore.c b/src/pmcd/src/dostore.c new file mode 100644 index 0000000..8e6071a --- /dev/null +++ b/src/pmcd/src/dostore.c @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * 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" +#include "pmcd.h" +#include <assert.h> + +/* Routine to split a result into a list of results, each containing metrics + * from a single domain. The end of the list is marked by a pmResult with a + * numpmid of zero. Any pmids for which there is no agent will be in the + * second to last pmResult which will have a negated numpmid value. + */ + +pmResult ** +SplitResult(pmResult *res) +{ + int i, j; + static int *aFreq = NULL; /* Freq. histogram: pmids for each agent */ + static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */ + static int nDoms = 0; /* No. of entries in two tables above */ + int nGood; + int need; + pmResult **results; + + /* Allocate the frequency histogram and array for mapping from agent to + * result list index. Because a SIGHUP reconfiguration may have caused a + * change in the number of agents, reallocation using a new size may be + * necessary. + * There are nAgents + 1 entries in the aFreq and resIndex arrays. The + * last entry in each is used for the pmIDs for which no agent could be + * found. + */ + if (nAgents > nDoms) { + nDoms = nAgents; + if (aFreq != NULL) + free(aFreq); + if (resIndex != NULL) + free(resIndex); + aFreq = (int *)malloc((nAgents + 1) * sizeof(int)); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + if (aFreq == NULL || resIndex == NULL) { + __pmNoMem("SplitResult.freq", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + } + + /* Build a frequency histogram of metric domains (use aFreq[nAgents] for + * pmids for which there is no agent). + */ + for (i = 0; i <= nAgents; i++) + aFreq[i] = 0; + for (i = 0; i < res->numpmid; i++) { + int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain; + for (j = 0; j < nAgents; j++) + if (agent[j].pmDomainId == dom && agent[j].status.connected) + break; + aFreq[j]++; + } + + /* Initialise resIndex and allocate the results structures */ + nGood = 0; + for (i = 0; i < nAgents; i++) + if (aFreq[i]) { + resIndex[i] = nGood; + nGood++; + } + resIndex[nAgents] = nGood; + + need = nGood + 1 + ((aFreq[nAgents]) ? 1 : 0); + need *= sizeof(pmResult *); + if ((results = (pmResult **) malloc(need)) == NULL) { + __pmNoMem("SplitResult.results", need, PM_FATAL_ERR); + } + j = 0; + for (i = 0; i <= nAgents; i++) + if (aFreq[i]) { + need = (int)sizeof(pmResult) + (aFreq[i] - 1) * (int)sizeof(pmValueSet *); + results[j] = (pmResult *) malloc(need); + if (results[j] == NULL) { + __pmNoMem("SplitResult.domain", need, PM_FATAL_ERR); + } + results[j]->numpmid = aFreq[i]; + j++; + } + + /* Make the "end of list" pmResult */ + if ((results[j] = (pmResult *) malloc(sizeof(pmResult))) == NULL) { + __pmNoMem("SplitResult.domain", sizeof(pmResult), PM_FATAL_ERR); + } + results[j]->numpmid = 0; + + /* Foreach vset in res, find it's pmResult in the per domain results array + * and copy a pointer to the vset to the next available position in the per + * domain result. + */ + for (i = 0; i <= nAgents; i++) + aFreq[i] = 0; + for (i = 0; i < res->numpmid; i++) { + int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain; + for (j = 0; j < nAgents; j++) + if (dom == agent[j].pmDomainId && agent[j].status.connected) + break; + results[resIndex[j]]->vset[aFreq[j]] = res->vset[i]; + aFreq[j]++; + } + + /* Flip the sign of numpmids in the "bad list" */ + if (aFreq[nAgents]) { + int bad = resIndex[nAgents]; + results[bad]->numpmid = -results[bad]->numpmid; + } + + return results; +} + +int +DoStore(ClientInfo *cp, __pmPDU* pb) +{ + int sts; + int s = 0; + AgentInfo *ap; + pmResult *result; + pmResult **dResult; + int i; + __pmFdSet readyFds; + __pmFdSet waitFds; + int nWait = 0; + int maxFd = -1; + int badStore; /* != 0 => store to nonexistent agent */ + int notReady = 0; /* != 0 => store to agent that's not ready */ + struct timeval timeout; + + + if ((sts = __pmDecodeResult(pb, &result)) < 0) + return sts; + + dResult = SplitResult(result); + + /* Send the per-domain results to their respective agents */ + + __pmFD_ZERO(&waitFds); + for (i = 0; dResult[i]->numpmid > 0; i++) { + int fd; + ap = FindDomainAgent(((__pmID_int *)&dResult[i]->vset[0]->pmid)->domain); + /* If it's in a "good" list, pmID has agent that is connected */ + assert(ap != NULL); + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + s = ap->ipc.dso.dispatch.version.any.store(dResult[i], + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady == 0) { + /* agent is ready for PDUs */ + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_RESULT, dResult[i]->numpmid); + s = __pmSendResult(ap->inFd, cp - client, dResult[i]); + if (s >= 0) { + ap->status.busy = 1; + fd = ap->outFd; + __pmFD_SET(fd, &waitFds); + if (fd > maxFd) + maxFd = fd; + nWait++; + } + else if (s == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || s == -EPIPE) { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_RESULT, sts); + CleanupAgent(ap, AT_COMM, ap->inFd); + } + } + else + /* agent is not ready for PDUs */ + notReady = 1; + } + if (s < 0) { + sts = s; + continue; + } + } + + /* If there was no agent for one or more pmIDs, there will be a "bad list" + * with a negated numpmid value. Store as many "good" pmIDs as possible + * but remember that there were homeless ones. + */ + badStore = dResult[i]->numpmid < 0; + + /* Collect error PDUs containing store status from each active agent */ + + while (nWait > 0) { + __pmFD_COPY(&readyFds, &waitFds); + if (nWait > 1) { + timeout.tv_sec = _pmcd_timeout; + timeout.tv_usec = 0; + + s = __pmSelectRead(maxFd+1, &readyFds, &timeout); + + if (s == 0) { + __pmNotifyErr(LOG_INFO, "DoStore: select timeout"); + + /* Timeout, terminate agents that haven't responded */ + for (i = 0; i < nAgents; i++) { + if (agent[i].status.busy) { + pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_ERROR, 0); + CleanupAgent(&agent[i], AT_COMM, agent[i].inFd); + } + } + sts = PM_ERR_IPC; + break; + } + else if (sts < 0) { + /* this is not expected to happen! */ + __pmNotifyErr(LOG_ERR, "DoStore: fatal select failure: %s\n", + netstrerror()); + Shutdown(); + exit(1); + } + } + + for (i = 0; i < nAgents; i++) { + int pinpdu; + ap = &agent[i]; + if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds)) + continue; + ap->status.busy = 0; + __pmFD_CLR(ap->outFd, &waitFds); + nWait--; + pinpdu = s = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (s > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, s, (int)((__psint_t)pb & 0xffffffff)); + if (s == PDU_ERROR) { + int ss; + if ((ss = __pmDecodeError(pb, &s)) < 0) + sts = ss; + else { + if (s < 0) { + extern int CheckError(AgentInfo *, int); + + sts = CheckError(ap, s); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + } + } + } + else { + /* Agent protocol error */ + if (s < 0) + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, s); + else + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, s); + sts = PM_ERR_IPC; + } + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT)) + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + + /* Only one error code can be returned, so "no agent" or "not + * ready" errors have precedence over all except IPC and TIMEOUT + * protocol failures. + * Note that we make only a weak effort to return the most + * appropriate error status because clients interested in the + * outcome should be using pmStore on individual metric/instances + * if the outcome is important. In particular, in multi-agent + * stores, an earlier PM_ERR_IPC error can be "overwritten" by a + * subsequent less serious error. + */ + if (sts != PM_ERR_IPC && sts != PM_ERR_TIMEOUT) { + if (badStore) { + sts = PM_ERR_NOAGENT; + } + else if (notReady) { + sts = PM_ERR_AGAIN; + } + } + + if (sts >= 0) { + /* send PDU_ERROR, even if result was 0 */ + int s; + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, 0); + s = __pmSendError(cp->fd, FROM_ANON, 0); + if (s < 0) + CleanupClient(cp, s); + } + + pmFreeResult(result); + i = 0; + do { + s = dResult[i]->numpmid; + free(dResult[i]); + i++; + } while (s); /* numpmid == 0 terminates list */ + free(dResult); + + return sts; +} diff --git a/src/pmcd/src/pmcd.c b/src/pmcd/src/pmcd.c new file mode 100644 index 0000000..1bafee5 --- /dev/null +++ b/src/pmcd/src/pmcd.c @@ -0,0 +1,1024 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * 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 "pmcd.h" +#include "impl.h" +#include <sys/stat.h> +#include <assert.h> + +#define SHUTDOWNWAIT 12 /* < PMDAs wait previously used in rc_pcp */ +#define MAXPENDING 5 /* maximum number of pending connections */ +#define FDNAMELEN 40 /* maximum length of a fd description */ +#define STRINGIFY(s) #s +#define TO_STRING(s) STRINGIFY(s) + +static char *FdToString(int); +static void ResetBadHosts(void); + +int AgentDied; /* for updating mapdom[] */ +static int timeToDie; /* For SIGINT handling */ +static int restart; /* For SIGHUP restart */ +static int maxReqPortFd; /* Largest request port fd */ +static char configFileName[MAXPATHLEN]; /* path to pmcd.conf */ +static char *logfile = "pmcd.log"; /* log file name */ +static int run_daemon = 1; /* run as a daemon, see -f */ +int _creds_timeout = 3; /* Timeout for agents credential PDU */ +static char *fatalfile = "/dev/tty";/* fatal messages at startup go here */ +static char *pmnsfile = PM_NS_DEFAULT; +static char *username; +static char *certdb; /* certificate database path (NSS) */ +static char *dbpassfile; /* certificate database password file */ +static int dupok; /* set to 1 for -N pmnsfile */ +static char sockpath[MAXPATHLEN]; /* local unix domain socket path */ + +#ifdef HAVE_SA_SIGINFO +static pid_t killer_pid; +static uid_t killer_uid; +#endif +static int killer_sig; + +static void +DontStart(void) +{ + FILE *tty; + FILE *log; + + __pmNotifyErr(LOG_ERR, "pmcd not started due to errors!\n"); + + if ((tty = fopen(fatalfile, "w")) != NULL) { + fflush(stderr); + fprintf(tty, "NOTE: pmcd not started due to errors! "); + if ((log = fopen(logfile, "r")) != NULL) { + int c; + fprintf(tty, "Log file \"%s\" contains ...\n", logfile); + while ((c = fgetc(log)) != EOF) + fputc(c, tty); + fclose(log); + } + else + fprintf(tty, "Log file \"%s\" has vanished!\n", logfile); + fclose(tty); + } + /* + * We are often called after the request ports have been opened. If we don't + * explicitely close them, then the unix domain socket file (if any) will be + * left in the file system, causing "address already in use" the next time + * pmcd starts. + */ + __pmServerCloseRequestPorts(); + + exit(1); +} + +static pmLongOptions longopts[] = { + PMAPI_OPTIONS_HEADER("General options"), + PMOPT_DEBUG, + PMOPT_NAMESPACE, + PMOPT_DUPNAMES, + PMOPT_HELP, + PMAPI_OPTIONS_HEADER("Service options"), + { "", 0, 'A', 0, "disable service advertisement" }, + { "foreground", 0, 'f', 0, "run in the foreground" }, + { "hostname", 1, 'H', "HOST", "set the hostname to be used for pmcd.hostname metric" }, + { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" }, + PMAPI_OPTIONS_HEADER("Configuration options"), + { "config", 1, 'c', "PATH", "path to configuration file" }, + { "certdb", 1, 'C', "PATH", "path to NSS certificate database" }, + { "passfile", 1, 'P', "PATH", "password file for certificate database access" }, + { "", 1, 'L', "BYTES", "maximum size for PDUs from clients [default 65536]" }, + { "", 1, 'q', "TIME", "PMDA initial negotiation timeout (seconds) [default 3]" }, + { "", 1, 't', "TIME", "PMDA response timeout (seconds) [default 5]" }, + PMAPI_OPTIONS_HEADER("Connection options"), + { "interface", 1, 'i', "ADDR", "accept connections on this IP address" }, + { "port", 1, 'p', "N", "accept connections on this port" }, + { "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmcd.socket]" }, + PMAPI_OPTIONS_HEADER("Diagnostic options"), + { "trace", 1, 'T', "FLAG", "Event trace control" }, + { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" }, + { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" }, + PMAPI_OPTIONS_END +}; + +static pmOptions opts = { + .flags = PM_OPTFLAG_POSIX, + .short_options = "Ac:C:D:fH:i:l:L:N:n:p:P:q:s:St:T:U:x:?", + .long_options = longopts, +}; + +static void +ParseOptions(int argc, char *argv[], int *nports) +{ + int c; + int sts; + char *endptr; + int usage = 0; + int val; + + endptr = pmGetConfig("PCP_PMCDCONF_PATH"); + strncpy(configFileName, endptr, sizeof(configFileName)-1); + + while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) { + switch (c) { + + case 'A': /* disable pmcd service advertising */ + __pmServerClearFeature(PM_SERVER_FEATURE_DISCOVERY); + break; + + case 'c': /* configuration file */ + strncpy(configFileName, opts.optarg, sizeof(configFileName)-1); + break; + + case 'C': /* path to NSS certificate database */ + certdb = opts.optarg; + break; + + case 'D': /* debug flag */ + sts = __pmParseDebug(opts.optarg); + if (sts < 0) { + pmprintf("%s: unrecognized debug flag specification (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } + pmDebug |= sts; + break; + + case 'f': + /* foreground, i.e. do _not_ run as a daemon */ + run_daemon = 0; + break; + + case 'i': + /* one (of possibly several) interfaces for client requests */ + __pmServerAddInterface(opts.optarg); + break; + + case 'H': + /* use the given name as the pmcd.hostname for this host */ + _pmcd_hostname = opts.optarg; + break; + + case 'l': + /* log file name */ + logfile = opts.optarg; + break; + + case 'L': /* Maximum size for PDUs from clients */ + val = (int)strtol(opts.optarg, NULL, 0); + if (val <= 0) { + pmprintf("%s: -L requires a positive value\n", pmProgname); + opts.errors++; + } else { + __pmSetPDUCeiling(val); + } + break; + + case 'N': + dupok = 1; + /*FALLTHROUGH*/ + case 'n': + /* name space file name */ + pmnsfile = opts.optarg; + break; + + case 'p': + if (__pmServerAddPorts(opts.optarg) < 0) { + pmprintf("%s: -p requires a positive numeric argument (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } else { + *nports += 1; + } + break; + + case 'P': /* password file for certificate database access */ + dbpassfile = opts.optarg; + break; + + case 'q': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val <= 0.0) { + pmprintf("%s: -q requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _creds_timeout = val; + } + break; + + case 's': /* path to local unix domain socket */ + snprintf(sockpath, sizeof(sockpath), "%s", opts.optarg); + break; + + case 'S': /* only allow authenticated clients */ + __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD); + break; + + case 't': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val < 0.0) { + pmprintf("%s: -t requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _pmcd_timeout = val; + } + break; + + case 'T': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val < 0) { + pmprintf("%s: -T requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _pmcd_trace_mask = val; + } + break; + + case 'U': + username = opts.optarg; + break; + + case 'x': + fatalfile = opts.optarg; + break; + + case '?': + usage = 1; + break; + + default: + opts.errors++; + break; + } + } + + if (usage || opts.errors || opts.optind < argc) { + pmUsageMessage(&opts); + if (usage) + exit(0); + DontStart(); + } +} + +/* + * Determine which clients (if any) have sent data to the server and handle it + * as required. + */ +void +HandleClientInput(__pmFdSet *fdsPtr) +{ + int sts; + int i; + __pmPDU *pb; + __pmPDUHdr *php; + ClientInfo *cp; + + for (i = 0; i < nClients; i++) { + int pinpdu; + if (!client[i].status.connected || !__pmFD_ISSET(client[i].fd, fdsPtr)) + continue; + + cp = &client[i]; + this_client_id = i; + + pinpdu = sts = __pmGetPDU(cp->fd, LIMIT_SIZE, _pmcd_timeout, &pb); + if (sts > 0) { + pmcd_trace(TR_RECV_PDU, cp->fd, sts, (int)((__psint_t)pb & 0xffffffff)); + } else { + CleanupClient(cp, sts); + continue; + } + + php = (__pmPDUHdr *)pb; + if (__pmVersionIPC(cp->fd) == UNKNOWN_VERSION && php->type != PDU_CREDS) { + /* old V1 client protocol, no longer supported */ + sts = PM_ERR_IPC; + CleanupClient(cp, sts); + __pmUnpinPDUBuf(pb); + continue; + } + + if (pmDebug & DBG_TRACE_APPL0) + ShowClients(stderr); + + switch (php->type) { + case PDU_PROFILE: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoProfile(cp, pb); + break; + + case PDU_FETCH: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoFetch(cp, pb); + break; + + case PDU_INSTANCE_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoInstance(cp, pb); + break; + + case PDU_DESC_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoDesc(cp, pb); + break; + + case PDU_TEXT_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoText(cp, pb); + break; + + case PDU_RESULT: + sts = (cp->denyOps & PMCD_OP_STORE) ? + PM_ERR_PERMISSION : DoStore(cp, pb); + break; + + case PDU_PMNS_IDS: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSIDs(cp, pb); + break; + + case PDU_PMNS_NAMES: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSNames(cp, pb); + break; + + case PDU_PMNS_CHILD: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSChild(cp, pb); + break; + + case PDU_PMNS_TRAVERSE: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSTraverse(cp, pb); + break; + + case PDU_CREDS: + sts = DoCreds(cp, pb); + break; + + default: + sts = PM_ERR_IPC; + } + if (sts < 0) { + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "PDU: %s client[%d]: %s\n", + __pmPDUTypeStr(php->type), i, pmErrStr(sts)); + /* Make sure client still alive before sending. */ + if (cp->status.connected) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + sts = __pmSendError(cp->fd, FROM_ANON, sts); + if (sts < 0) + __pmNotifyErr(LOG_ERR, "HandleClientInput: " + "error sending Error PDU to client[%d] %s\n", i, pmErrStr(sts)); + } + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } +} + +/* Called to shutdown pmcd in an orderly manner */ + +void +Shutdown(void) +{ + int i; + + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + if (!ap->status.connected) + continue; + if (ap->inFd != -1) { + if (__pmSocketIPC(ap->inFd)) + __pmCloseSocket(ap->inFd); + else + close(ap->inFd); + } + if (ap->outFd != -1) { + if (__pmSocketIPC(ap->outFd)) + __pmCloseSocket(ap->outFd); + else + close(ap->outFd); + } + if (ap->ipcType == AGENT_SOCKET && + ap->ipc.socket.addrDomain == AF_UNIX) { + /* remove the Unix domain socket */ + unlink(ap->ipc.socket.name); + } + } + if (HarvestAgents(SHUTDOWNWAIT) < 0) { + /* terminate with prejudice any still remaining */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + if (ap->status.connected) { + pid_t pid = ap->ipcType == AGENT_SOCKET ? + ap->ipc.socket.agentPid : ap->ipc.pipe.agentPid; + __pmProcessTerminate(pid, 1); + } + } + } + for (i = 0; i < nClients; i++) + if (client[i].status.connected) + __pmCloseSocket(client[i].fd); + __pmServerCloseRequestPorts(); + __pmSecureServerShutdown(); + __pmNotifyErr(LOG_INFO, "pmcd Shutdown\n"); + fflush(stderr); +} + +static void +SignalShutdown(void) +{ +#ifdef HAVE_SA_SIGINFO +#if DESPERATE + char buf[256]; +#endif + if (killer_pid != 0) { + __pmNotifyErr(LOG_INFO, "pmcd caught %s from pid=%" FMT_PID " uid=%d\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM", killer_pid, killer_uid); +#if DESPERATE + __pmNotifyErr(LOG_INFO, "Try to find process in ps output ...\n"); + sprintf(buf, "sh -c \". \\$PCP_DIR/etc/pcp.env; ( \\$PCP_PS_PROG \\$PCP_PS_ALL_FLAGS | \\$PCP_AWK_PROG 'NR==1 {print} \\$2==%" FMT_PID " {print}' )\"", killer_pid); + system(buf); +#endif + } + else { + __pmNotifyErr(LOG_INFO, "pmcd caught %s from unknown process\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM"); + } +#else + __pmNotifyErr(LOG_INFO, "pmcd caught %s\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM"); +#endif + Shutdown(); + exit(0); +} + +static void +SignalRestart(void) +{ + time_t now; + + time(&now); + __pmNotifyErr(LOG_INFO, "\n\npmcd RESTARTED at %s", ctime(&now)); + fprintf(stderr, "\nCurrent PMCD clients ...\n"); + ShowClients(stderr); + ResetBadHosts(); + ParseRestartAgents(configFileName); +} + +static void +SignalReloadPMNS(void) +{ + int sts; + + /* Reload PMNS if necessary. + * Note: this will only stat() the base name i.e. ASCII pmns, + * typically $PCP_VAR_DIR/pmns/root and not $PCP_VAR_DIR/pmns/root.bin . + * This is considered a very low risk problem, as the binary + * PMNS is always compiled from the ASCII version; + * when one changes so should the other. + * This caveat was allowed to make the code a lot simpler. + */ + if (__pmHasPMNSFileChanged(pmnsfile)) { + __pmNotifyErr(LOG_INFO, "Reloading PMNS \"%s\"", + (pmnsfile==PM_NS_DEFAULT)?"DEFAULT":pmnsfile); + pmUnloadNameSpace(); + if (dupok) + sts = pmLoadASCIINameSpace(pmnsfile, 1); + else + sts = pmLoadNameSpace(pmnsfile); + if (sts < 0) { + __pmNotifyErr(LOG_ERR, "PMNS \"%s\" load failed: %s", + (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile, + pmErrStr(sts)); + } + } + else { + __pmNotifyErr(LOG_INFO, "PMNS file \"%s\" is unchanged", + (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile); + } +} + +/* Process I/O on file descriptors from agents that were marked as not ready + * to handle PDUs. + */ +static int +HandleReadyAgents(__pmFdSet *readyFds) +{ + int i, s, sts; + int fd; + int reason; + int ready = 0; + AgentInfo *ap; + __pmPDU *pb; + + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.notReady) { + fd = ap->outFd; + if (__pmFD_ISSET(fd, readyFds)) { + int pinpdu; + + /* Expect an error PDU containing PM_ERR_PMDAREADY */ + reason = AT_COMM; /* most errors are protocol failures */ + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) { + sts = s; + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts); + } + else { + /* sts is the status code from the error PDU */ + if (pmDebug && DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, + "%s agent (not ready) sent %s status(%d)\n", + ap->pmDomainLabel, + sts == PM_ERR_PMDAREADY ? + "ready" : "unknown", sts); + if (sts == PM_ERR_PMDAREADY) { + ap->status.notReady = 0; + sts = 1; + ready++; + } + else { + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts); + sts = PM_ERR_IPC; + } + } + } + else { + if (sts < 0) + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + else + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (ap->ipcType != AGENT_DSO && sts <= 0) + CleanupAgent(ap, reason, fd); + } + } + } + return ready; +} + +static void +CheckNewClient(__pmFdSet * fdset, int rfd, int family) +{ + int s, sts, accepted = 1; + __uint32_t challenge; + ClientInfo *cp; + + if (__pmFD_ISSET(rfd, fdset)) { + if ((cp = AcceptNewClient(rfd)) == NULL) + return; /* Accept failed and no client added */ + + sts = __pmAccAddClient(cp->addr, &cp->denyOps); +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (sts >= 0 && family == AF_UNIX) { + if ((sts = __pmServerSetLocalCreds(cp->fd, &cp->attrs)) < 0) { + __pmNotifyErr(LOG_ERR, + "ClientLoop: error extracting local credentials: %s", + pmErrStr(sts)); + } + } +#endif + if (sts >= 0) { + memset(&cp->pduInfo, 0, sizeof(cp->pduInfo)); + cp->pduInfo.version = PDU_VERSION; + cp->pduInfo.licensed = 1; + if (__pmServerHasFeature(PM_SERVER_FEATURE_SECURE)) + cp->pduInfo.features |= (PDU_FLAG_SECURE | PDU_FLAG_SECURE_ACK); + if (__pmServerHasFeature(PM_SERVER_FEATURE_COMPRESS)) + cp->pduInfo.features |= PDU_FLAG_COMPRESS; + if (__pmServerHasFeature(PM_SERVER_FEATURE_AUTH)) /* optionally */ + cp->pduInfo.features |= PDU_FLAG_AUTH; + if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD)) /* required */ + cp->pduInfo.features |= PDU_FLAG_CREDS_REQD; + challenge = *(__uint32_t *)(&cp->pduInfo); + sts = 0; + } + else { + challenge = 0; + accepted = 0; + } + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + + /* reset (no meaning, use fd table to version) */ + cp->pduInfo.version = UNKNOWN_VERSION; + + s = __pmSendXtendError(cp->fd, FROM_ANON, sts, htonl(challenge)); + if (s < 0) { + __pmNotifyErr(LOG_ERR, + "ClientLoop: error sending Conn ACK PDU to new client %s\n", + pmErrStr(s)); + if (sts >= 0) + /* + * prefer earlier failure status if any, else + * use the one from __pmSendXtendError() + */ + sts = s; + accepted = 0; + } + if (!accepted) + CleanupClient(cp, sts); + } +} + +/* Loop, synchronously processing requests from clients. */ + +static void +ClientLoop(void) +{ + int i, fd, sts; + int maxFd; + int checkAgents; + int reload_ns = 0; + __pmFdSet readableFds; + + for (;;) { + + /* Figure out which file descriptors to wait for input on. Keep + * track of the highest numbered descriptor for the select call. + */ + readableFds = clientFds; + maxFd = maxClientFd + 1; + + /* If an agent was not ready, it may send an ERROR PDU to indicate it + * is now ready. Add such agents to the list of file descriptors. + */ + checkAgents = 0; + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + + if (ap->status.notReady) { + fd = ap->outFd; + __pmFD_SET(fd, &readableFds); + if (fd > maxFd) + maxFd = fd + 1; + checkAgents = 1; + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, + "not ready: check %s agent on fd %d (max = %d)\n", + ap->pmDomainLabel, fd, maxFd); + } + } + + sts = __pmSelectRead(maxFd, &readableFds, NULL); + if (sts > 0) { + if (pmDebug & DBG_TRACE_APPL0) + for (i = 0; i <= maxClientFd; i++) + if (__pmFD_ISSET(i, &readableFds)) + fprintf(stderr, "DATA: from %s (fd %d)\n", + FdToString(i), i); + __pmServerAddNewClients(&readableFds, CheckNewClient); + if (checkAgents) + reload_ns = HandleReadyAgents(&readableFds); + HandleClientInput(&readableFds); + } + else if (sts == -1 && neterror() != EINTR) { + __pmNotifyErr(LOG_ERR, "ClientLoop select: %s\n", netstrerror()); + break; + } + if (restart) { + restart = 0; + reload_ns = 1; + SignalRestart(); + } + if (reload_ns) { + reload_ns = 0; + SignalReloadPMNS(); + } + if (timeToDie) { + SignalShutdown(); + break; + } + if (AgentDied) { + AgentDied = 0; + for (i = 0; i < nAgents; i++) { + if (!agent[i].status.connected) + mapdom[agent[i].pmDomainId] = nAgents; + } + } + } +} + +#ifdef HAVE_SA_SIGINFO +static void +SigIntProc(int sig, siginfo_t *sip, void *x) +{ + killer_sig = sig; + if (sip != NULL) { + killer_pid = sip->si_pid; + killer_uid = sip->si_uid; + } + timeToDie = 1; +} +#elif IS_MINGW +static void +SigIntProc(int sig) +{ + SignalShutdown(); +} +#else +static void +SigIntProc(int sig) +{ + killer_sig = sig; + signal(SIGINT, SigIntProc); + signal(SIGTERM, SigIntProc); + timeToDie = 1; +} +#endif + +#ifdef IS_MINGW +static void +SigHupProc(int sig) +{ + SignalRestart(); + SignalReloadPMNS(); +} +#else +static void +SigHupProc(int sig) +{ + signal(SIGHUP, SigHupProc); + restart = 1; +} +#endif + +static void +SigBad(int sig) +{ + if (pmDebug & DBG_TRACE_DESPERATE) { + __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig); + + /* -D desperate on the command line to enable traceback, + * if we have platform support for it + */ + fprintf(stderr, "\nProcedure call traceback ...\n"); + __pmDumpStack(stderr); + fflush(stderr); + } + _exit(sig); +} + +int +main(int argc, char *argv[]) +{ + int sts; + int nport = 0; + char *envstr; +#ifdef HAVE_SA_SIGINFO + static struct sigaction act; +#endif + + umask(022); + __pmProcessDataSize(NULL); + __pmGetUsername(&username); + __pmSetInternalState(PM_STATE_PMCS); + __pmServerSetFeature(PM_SERVER_FEATURE_DISCOVERY); + + if ((envstr = getenv("PMCD_PORT")) != NULL) + nport = __pmServerAddPorts(envstr); + ParseOptions(argc, argv, &nport); + if (nport == 0) + __pmServerAddPorts(TO_STRING(SERVER_PORT)); + + /* Set the local socket path. A message will be generated into the log + * if this fails, but it is not fatal, since other connection options + * may exist. + */ + __pmServerSetLocalSocket(sockpath); + + /* Set the service spec. This will cause our service to be advertised on + * the network if that is supported. + */ + __pmServerSetServiceSpec(PM_SERVER_SERVICE_SPEC); + + if (run_daemon) { + fflush(stderr); + StartDaemon(argc, argv); + } + +#ifdef HAVE_SA_SIGINFO + act.sa_sigaction = SigIntProc; + act.sa_flags = SA_SIGINFO; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); +#else + __pmSetSignalHandler(SIGINT, SigIntProc); + __pmSetSignalHandler(SIGTERM, SigIntProc); +#endif + __pmSetSignalHandler(SIGHUP, SigHupProc); + __pmSetSignalHandler(SIGBUS, SigBad); + __pmSetSignalHandler(SIGSEGV, SigBad); + + if ((sts = __pmServerOpenRequestPorts(&clientFds, MAXPENDING)) < 0) + DontStart(); + maxReqPortFd = maxClientFd = sts; + + __pmOpenLog(pmProgname, logfile, stderr, &sts); + /* close old stdout, and force stdout into same stream as stderr */ + fflush(stdout); + close(fileno(stdout)); + sts = dup(fileno(stderr)); + /* if this fails beware of the sky falling in */ + assert(sts >= 0); + + if (dupok) + sts = pmLoadASCIINameSpace(pmnsfile, 1); + else + sts = pmLoadNameSpace(pmnsfile); + if (sts < 0) { + fprintf(stderr, "Error: pmLoadNameSpace: %s\n", pmErrStr(sts)); + DontStart(); + } + + if (ParseInitAgents(configFileName) < 0) { + /* error already reported in ParseInitAgents() */ + DontStart(); + } + + if (nAgents <= 0) { + fprintf(stderr, "Error: No PMDAs found in the configuration file \"%s\"\n", + configFileName); + DontStart(); + } + + if (run_daemon) { + if (__pmServerCreatePIDFile(PM_SERVER_SERVICE_SPEC, PM_FATAL_ERR) < 0) + DontStart(); + if (__pmSetProcessIdentity(username) < 0) + DontStart(); + } + + if (__pmSecureServerSetup(certdb, dbpassfile) < 0) + DontStart(); + + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + fprintf(stderr, "\npmcd: PID = %" FMT_PID, getpid()); + fprintf(stderr, ", PDU version = %u\n", PDU_VERSION); + __pmServerDumpRequestPorts(stderr); + fflush(stderr); + + /* all the work is done here */ + ClientLoop(); + + Shutdown(); + exit(0); +} + +/* The bad host list is a list of IP addresses for hosts that have had clients + * cleaned up because of an access violation (permission or connection limit). + * This is used to ensure that the message printed in PMCD's log file when a + * client is terminated like this only appears once per host. That stops the + * log from growing too large if repeated access violations occur. + * The list is cleared when PMCD is reconfigured. + */ + +static int nBadHosts; +static int szBadHosts; +static __pmSockAddr **badHost; + +static int +AddBadHost(struct __pmSockAddr *hostId) +{ + int i, need; + + for (i = 0; i < nBadHosts; i++) + if (__pmSockAddrCompare(hostId, badHost[i]) == 0) + /* already there */ + return 0; + + /* allocate more entries if required */ + if (nBadHosts == szBadHosts) { + szBadHosts += 8; + need = szBadHosts * (int)sizeof(badHost[0]); + if ((badHost = (__pmSockAddr **)realloc(badHost, need)) == NULL) { + __pmNoMem("pmcd.AddBadHost", need, PM_FATAL_ERR); + } + } + badHost[nBadHosts++] = __pmSockAddrDup(hostId); + return 1; +} + +static void +ResetBadHosts(void) +{ + if (szBadHosts) { + while (nBadHosts > 0) { + --nBadHosts; + free (badHost[nBadHosts]); + } + free(badHost); + } + nBadHosts = 0; + szBadHosts = 0; + badHost = NULL; +} + +void +CleanupClient(ClientInfo *cp, int sts) +{ + char *caddr; + int i, msg; + int force; + + force = pmDebug & DBG_TRACE_APPL0; + + if (sts != 0 || force) { + /* for access violations, only print the message if this host hasn't + * been dinged for an access violation since startup or reconfiguration + */ + if (sts == PM_ERR_PERMISSION || sts == PM_ERR_CONNLIMIT) { + if ( (msg = AddBadHost(cp->addr)) ) { + caddr = __pmSockAddrToString(cp->addr); + fprintf(stderr, "access violation from host %s\n", caddr); + free(caddr); + } + } + else + msg = 0; + + if (msg || force) { + for (i = 0; i < nClients; i++) { + if (cp == &client[i]) + break; + } + fprintf(stderr, "endclient client[%d]: (fd %d) %s (%d)\n", + i, cp->fd, pmErrStr(sts), sts); + } + } + + /* If the client is being cleaned up because its connection was refused + * don't do this because it hasn't actually contributed to the connection + * count + */ + if (sts != PM_ERR_PERMISSION && sts != PM_ERR_CONNLIMIT) + __pmAccDelClient(cp->addr); + + pmcd_trace(TR_DEL_CLIENT, cp->fd, sts, 0); + DeleteClient(cp); + + if (maxClientFd < maxReqPortFd) + maxClientFd = maxReqPortFd; + + for (i = 0; i < nAgents; i++) + if (agent[i].profClient == cp) + agent[i].profClient = NULL; +} + +/* Convert a file descriptor to a string describing what it is for. */ +static char * +FdToString(int fd) +{ + static char fdStr[FDNAMELEN]; + static char *stdFds[4] = {"*UNKNOWN FD*", "stdin", "stdout", "stderr"}; + int i; + + if (fd >= -1 && fd < 3) + return stdFds[fd + 1]; + if (__pmServerRequestPortString(fd, fdStr, FDNAMELEN) != NULL) + return fdStr; + for (i = 0; i < nClients; i++) + if (client[i].status.connected) { + if (fd == client[i].fd) { + sprintf(fdStr, "client[%d] input socket", i); + return fdStr; + } + } + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) { + if (fd == agent[i].inFd) { + sprintf(fdStr, "agent[%d] input", i); + return fdStr; + } + else if (fd == agent[i].outFd) { + sprintf(fdStr, "agent[%d] output", i); + return fdStr; + } + } + return stdFds[0]; +} diff --git a/src/pmcd/src/pmcd.h b/src/pmcd/src/pmcd.h new file mode 100644 index 0000000..ba8be5e --- /dev/null +++ b/src/pmcd/src/pmcd.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PMCD_H +#define _PMCD_H + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" + +#ifdef IS_MINGW +#ifdef PMCD_INTERNAL +#define PMCD_INTERN __declspec(dllexport) +#define PMCD_EXTERN +#else +#define PMCD_INTERN +#define PMCD_EXTERN __declspec(dllimport) +#endif +#else /*!MINGW*/ +#define PMCD_INTERN +#define PMCD_EXTERN extern +#endif + +#include "client.h" + +/* Structures of type-specific info for each kind of domain agent-PMCD + * connection (DSO, socket, pipe). + */ + +typedef void (*DsoInitPtr)(pmdaInterface*); + +typedef struct { + char *pathName; /* Where the DSO lives */ + int xlatePath; /* translated pathname? */ + char *entryPoint; /* Name of the entry point */ + void *dlHandle; /* Handle for DSO */ + DsoInitPtr initFn; /* Function to initialise DSO */ + /* and return dispatch table */ + pmdaInterface dispatch; /* Dispatch table for dso agent */ +} DsoInfo; + +typedef struct { + int addrDomain; /* AF_UNIX, AF_INET or AF_INET6 */ + int port; /* Port number if an INET socket */ + char *name; /* Port name if supplied for INET */ + /* or socket name for UNIX */ + char *commandLine; /* Optional command to start agent */ + char* *argv; /* Arg list built from commandLine */ + pid_t agentPid; /* Process ID of agent if PMCD started */ +} SocketInfo; + +typedef struct { + char* commandLine; /* Command line to use for child */ + char* *argv; /* Arg list built from command line */ + pid_t agentPid; /* Process ID of the agent */ +} PipeInfo; + +/* The agent table and its size. */ + +typedef struct { + int pmDomainId; /* PMD identifier */ + int ipcType; /* DSO, socket or pipe */ + int pduVersion; /* PDU_VERSION for this agent */ + int inFd, outFd; /* For input to/output from agent */ + int done; /* Set when processed for this Fetch */ + ClientInfo *profClient; /* Last client to send profile to agent */ + int profIndex; /* Index of profile that client sent */ + char *pmDomainLabel; /* Textual label for agent's PMD */ + struct { /* Status of agent */ + unsigned int + connected : 1, /* Agent connected to pmcd */ + busy : 1, /* Processing a request */ + isChild : 1, /* Is a child process of pmcd */ + madeDsoResult : 1, /* Pmcd made a "bad" pmResult (DSO only) */ + restartKeep : 1, /* Keep agent if set during restart */ + notReady : 1, /* Agent not ready to process PDUs */ + startNotReady : 1, /* Agent starts in non-ready state */ + unused : 9, /* Zero-padded, unused space */ + flags : 16; /* Agent-supplied connection flags */ + } status; + int reason; /* if ! connected */ + union { /* per-ipcType info */ + DsoInfo dso; + SocketInfo socket; + PipeInfo pipe; + } ipc; +} AgentInfo; + +PMCD_EXTERN AgentInfo *agent; /* Array of domain agent structs */ +PMCD_EXTERN int nAgents; /* Number of agents in array */ + +/* + * DomainId-to-AgentIndex map + * 9 bits of DomainId, max value is 510 because 511 is special (see + * DYNAMIC_PMID in impl.h) + */ +#define MAXDOMID 510 +extern int mapdom[]; /* the map */ + +/* Domain agent-PMCD connection types (AgentInfo.ipcType) */ + +#define AGENT_DSO 0 +#define AGENT_SOCKET 1 +#define AGENT_PIPE 2 + +/* Masks for operations used in access controls for clients. */ +#define PMCD_OP_FETCH 0x1 +#define PMCD_OP_STORE 0x2 + +#define PMCD_OP_NONE 0x0 +#define PMCD_OP_ALL 0x3 + +/* Agent termination reasons */ +#define AT_CONFIG 1 +#define AT_COMM 2 +#define AT_EXIT 3 + +/* + * Agent termination reasons for "reason" in AgentInfo, and pmcd.agent.state + * as exported by PMCD PMDA ... these encode the low byte, next byte contains + * exit status and next byte encodes signal + */ + /* 0 connected */ + /* 1 connected, not ready */ +#define REASON_EXIT 2 +#define REASON_NOSTART 4 +#define REASON_PROTOCOL 8 + +extern AgentInfo *FindDomainAgent(int); +extern void CleanupAgent(AgentInfo *, int, int); +extern int HarvestAgents(unsigned int); + +/* timeout to PMDAs (secs) */ +PMCD_EXTERN int _pmcd_timeout; + +/* timeout for credentials */ +extern int _creds_timeout; + +/* global PMCD PMDA variables */ + +/* + * trace types + */ + +#define TR_ADD_CLIENT 1 +#define TR_DEL_CLIENT 2 +#define TR_ADD_AGENT 3 +#define TR_DEL_AGENT 4 +#define TR_EOF 5 +#define TR_XMIT_PDU 7 +#define TR_RECV_PDU 8 +#define TR_WRONG_PDU 9 +#define TR_XMIT_ERR 10 +#define TR_RECV_TIMEOUT 11 +#define TR_RECV_ERR 12 + +/* + * trace control + */ +PMCD_EXTERN int _pmcd_trace_mask; +PMCD_EXTERN int _pmcd_trace_nbufs; + +/* + * trace mask bits + */ +#define TR_MASK_CONN 1 +#define TR_MASK_PDU 2 +#define TR_MASK_NOBUF 256 + +/* + * routines + */ +extern void pmcd_init_trace(int); +extern void pmcd_trace(int, int, int, int); +extern void pmcd_dump_trace(FILE *); +extern int pmcd_load_libpcp_pmda(void); + +/* + * PDU handling routines + */ +extern int DoFetch(ClientInfo *, __pmPDU *); +extern int DoProfile(ClientInfo *, __pmPDU *); +extern int DoDesc(ClientInfo *, __pmPDU *); +extern int DoInstance(ClientInfo *, __pmPDU *); +extern int DoText(ClientInfo *, __pmPDU *); +extern int DoStore(ClientInfo *, __pmPDU *); +extern int DoCreds(ClientInfo *, __pmPDU *); +extern int DoPMNSIDs(ClientInfo *, __pmPDU *); +extern int DoPMNSNames(ClientInfo *, __pmPDU *); +extern int DoPMNSChild(ClientInfo *, __pmPDU *); +extern int DoPMNSTraverse(ClientInfo *, __pmPDU *); + +/* + * General purpose routines + */ +extern void StartDaemon(int, char **); +extern void Shutdown(void); +extern int ParseInitAgents(char *); +extern void ParseRestartAgents(char *); +extern void PrintAgentInfo(FILE *); +extern void MarkStateChanges(int); +extern void CleanupClient(ClientInfo *, int); +extern int ClientsAuthentication(AgentInfo *); +extern int AgentsAuthentication(int); +extern pmResult **SplitResult(pmResult *); + +/* + * Highest known file descriptor used for a Client or an Agent connection. + * This is reported in the pmcd.openfds metric. + */ +PMCD_EXTERN int pmcd_hi_openfds; +extern void pmcd_openfds_sethi(int fd); + +/* Explicitly requested hostname (pmcd.hostname metric) */ +PMCD_EXTERN char *_pmcd_hostname; + +#endif /* _PMCD_H */ diff --git a/src/pmcd/src/util.c b/src/pmcd/src/util.c new file mode 100644 index 0000000..094d400 --- /dev/null +++ b/src/pmcd/src/util.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2009 Aconex. All Rights Reserved. + * + * 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 "pmcd.h" + +#ifdef IS_MINGW + +void +StartDaemon(int argc, char **argv) +{ + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + LPTSTR cmdline = NULL; + int i, sz = 3; /* -f\0 */ + + for (i = 0; i < argc; i++) + sz += strlen(argv[i]) + 1; + if ((cmdline = malloc(sz)) == NULL) { + __pmNotifyErr(LOG_ERR, "StartDaemon: no memory"); + exit(1); + } + for (sz = i = 0; i < argc; i++) + sz += sprintf(cmdline, "%s ", argv[i]); + sprintf(cmdline + sz, "-f"); + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + + if (0 == CreateProcess( + NULL, cmdline, + NULL, NULL, /* process and thread attributes */ + FALSE, /* inherit handles */ + CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS, + NULL, /* environment (from parent) */ + NULL, /* current directory */ + &siStartInfo, /* STARTUPINFO pointer */ + &piProcInfo)) { /* receives PROCESS_INFORMATION */ + __pmNotifyErr(LOG_ERR, "StartDaemon: CreateProcess"); + /* but keep going */ + } + else { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } +} + +#else + +/* Based on Stevens (Unix Network Programming, p.83) */ +void +StartDaemon(int argc, char **argv) +{ + pid_t childpid; + + (void)argc; (void)argv; + +#if defined(HAVE_TERMIO_SIGNALS) + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTSTP, SIG_IGN); +#endif + + if ((childpid = fork()) < 0) + __pmNotifyErr(LOG_ERR, "StartDaemon: fork"); + /* but keep going */ + else if (childpid > 0) { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } + + /* not a process group leader, lose controlling tty */ + if (setsid() == -1) + __pmNotifyErr(LOG_WARNING, "StartDaemon: setsid"); + /* but keep going */ + + close(0); + /* don't close other fd's -- we know that only good ones are open! */ + + /* don't chdir("/") -- we still need to open pmcd.log */ +} +#endif |