summaryrefslogtreecommitdiff
path: root/src/pmcd/src/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmcd/src/config.c')
-rw-r--r--src/pmcd/src/config.c2526
1 files changed, 2526 insertions, 0 deletions
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);
+}