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