summaryrefslogtreecommitdiff
path: root/src/libpcp_gui/src/record.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libpcp_gui/src/record.c')
-rw-r--r--src/libpcp_gui/src/record.c758
1 files changed, 758 insertions, 0 deletions
diff --git a/src/libpcp_gui/src/record.c b/src/libpcp_gui/src/record.c
new file mode 100644
index 0000000..3a388e8
--- /dev/null
+++ b/src/libpcp_gui/src/record.c
@@ -0,0 +1,758 @@
+/*
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#include "pmapi.h"
+#include "pmafm.h"
+#include "impl.h"
+#include <sys/stat.h>
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+/*
+ * some extended state, make sure these values are different to
+ * the PM_REC_* macros in <pmapi.h>
+ */
+#define PM_REC_BEGIN 81
+#define PM_REC_HOST 82
+
+static int state = PM_REC_OFF; /* where you are up to ... */
+static char *dir; /* directory containing Archive Folio files */
+static char *base; /* unique basename */
+static FILE *f_folio; /* Archive Folio goes here */
+static FILE *f_replay; /* current replay config file */
+static int _replay; /* can replay? */
+static char *_folio; /* remember the folio name */
+static char *_creator; /* remember the creator name */
+static int _seendefault; /* seen default host? */
+
+typedef struct _record {
+ struct _record *next;
+ int state;
+ char *host; /* host name */
+ int isdefault; /* is this the default host? */
+ pmRecordHost public; /* exposed to the caller */
+ char *base; /* archive base */
+ char *logfile; /* for -l ... not to be confused */
+ /* with public.logfile which is the */
+ /* full pathname */
+ char *config; /* for -c */
+ int argc;
+ char **argv;
+} record_t;
+
+static record_t *record;
+static int n_record;
+#ifndef IS_MINGW /* not yet ported */
+static int n_alive;
+#endif
+static char tbuf[MAXPATHLEN]; /* used for mktemp(), messages, ... */
+
+/*
+ * initialize, and return stdio stream for writing replay config
+ * (if any)
+ */
+FILE *
+pmRecordSetup(const char *folio, const char *creator, int replay)
+{
+ char *p;
+ char c;
+ time_t now;
+ int sts;
+ int fd = -1;
+ static char host[MAXHOSTNAMELEN];
+ char foliopath[MAXPATHLEN];
+ char *temp = NULL; /* for unlink() */
+ record_t *rp;
+ mode_t cur_umask;
+
+ if (state != PM_REC_OFF) {
+ /* already begun w/out end */
+ setoserror(EINVAL);
+ return NULL;
+ }
+
+ if (access(folio, F_OK) == 0) {
+ setoserror(EEXIST);
+ return NULL;
+ }
+
+ if (_folio != NULL)
+ free(_folio);
+ if ((_folio = strdup(folio)) == NULL)
+ return NULL;
+
+ if (_creator != NULL)
+ free(_creator);
+ if ((_creator = strdup(creator)) == NULL)
+ return NULL;
+
+ if ((f_folio = fopen(folio, "w")) == NULL)
+ return NULL;
+
+ dir = NULL;
+ base = NULL;
+
+ /*
+ * have folio file created ... get unique base string
+ */
+ tbuf[0] = '\0';
+ strcpy(foliopath, folio);
+ if ((p = strrchr(foliopath, '/')) != NULL) {
+ /* folio name contains a slash */
+ p++;
+ c = *p;
+ *p = '\0';
+ if (strcmp(foliopath, "./") != 0) {
+ if (dir != NULL)
+ free(dir);
+ if ((dir = strdup(foliopath)) == NULL)
+ goto failed;
+ strcpy(tbuf, dir);
+ strcat(tbuf, "/");
+ }
+ *p = c;
+ }
+ strcat(tbuf, "XXXXXX");
+#if HAVE_MKSTEMP
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ if ((fd = mkstemp(tbuf)) < 0) {
+ umask(cur_umask);
+ goto failed;
+ }
+ umask(cur_umask);
+#else
+ if (mktemp(tbuf) == NULL)
+ goto failed;
+
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ if ((fd = open(tbuf, O_CREAT | O_EXCL | O_RDWR, 0600)) < 0) {
+ umask(cur_umask);
+ goto failed;
+ }
+ umask(cur_umask);
+#endif
+ /*
+ * file named tbuf is never used, it is the basename for the real
+ * files we create. remember it so we can cleanup.
+ */
+ temp = strdup(tbuf);
+
+ if (dir == NULL)
+ p = tbuf;
+ else
+ p = strrchr(tbuf, '/') + 1;
+
+ if ((base = strdup(p)) == NULL)
+ goto failed;
+
+ /*
+ * folio preamble ...
+ */
+ fprintf(f_folio, "PCPFolio\nVersion: 1\n");
+ fprintf(f_folio, "# use pmafm(1) to process this PCP Archive Folio\n#\n");
+ time(&now);
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ host[MAXHOSTNAMELEN-1] = '\0';
+ fprintf(f_folio, "Created: on %s at %s", host, ctime(&now));
+ fprintf(f_folio, "Creator: %s", creator);
+ if (replay)
+ fprintf(f_folio, " %s", base);
+ fprintf(f_folio, "\n# Host Basename\n#\n");
+
+ _replay = replay;
+ if (_replay) {
+ f_replay = fdopen(fd, "w");
+ if (f_replay == NULL)
+ goto failed;
+ }
+ else
+ f_replay = fopen("/dev/null", "r");
+
+ n_record = 0;
+ for (rp = record; rp != NULL; rp = rp->next) {
+ rp->state = PM_REC_BEGIN;
+ rp->isdefault = 0;
+ if (rp->host != NULL) {
+ free(rp->host);
+ rp->host = NULL;
+ }
+ if (rp->base != NULL) {
+ free(rp->base);
+ rp->base = NULL;
+ }
+ if (rp->logfile != NULL) {
+ free(rp->logfile);
+ rp->logfile = NULL;
+ }
+ if (rp->config != NULL) {
+ free(rp->config);
+ rp->config = NULL;
+ }
+ if (rp->argv != NULL) {
+ free(rp->argv);
+ rp->argv = NULL;
+ }
+ rp->argc = 0;
+ if (rp->public.f_config != NULL) {
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ }
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ if (rp->public.logfile != NULL) {
+ free(rp->public.logfile);
+ rp->public.logfile = NULL;
+ }
+ rp->public.pid = (pid_t)0;
+ rp->public.status = -1;
+ }
+
+ state = PM_REC_BEGIN;
+ _seendefault = 0;
+
+ if (temp) {
+ unlink(temp);
+ free(temp);
+ }
+ return f_replay;
+
+failed:
+ sts = oserror();
+ if (dir != NULL)
+ free(dir);
+ if (base != NULL)
+ free(base);
+ unlink(folio);
+ fclose(f_folio);
+ f_folio = NULL;
+ if (fd >= 0)
+ close(fd);
+ setoserror(sts);
+ if (temp) {
+ unlink(temp);
+ free(temp);
+ }
+ return NULL;
+}
+
+/*
+ * need to log another host in this folio ... must come here
+ * at least once, but may be more than once before the recording
+ * commences
+ */
+int
+pmRecordAddHost(const char *host, int isdefault, pmRecordHost **rhp)
+{
+ char *p;
+ int c;
+ int sts;
+ record_t *rp;
+
+ *rhp = NULL; /* in case of errors */
+
+ if (state != PM_REC_BEGIN && state != PM_REC_HOST)
+ /* botched order of calls ... */
+ return -EINVAL;
+
+ if (isdefault && _seendefault)
+ /* only one default allowed per session! */
+ return -EINVAL;
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state == PM_REC_BEGIN)
+ break;
+ }
+ if (rp == NULL) {
+ /* need another one */
+ if ((rp = (record_t *)malloc(sizeof(record_t))) == NULL)
+ return -oserror();
+ rp->next = record;
+ record = rp;
+ rp->isdefault = 0;
+ rp->host = NULL;
+ rp->base = NULL;
+ rp->logfile = NULL;
+ rp->config = NULL;
+ rp->argv = NULL;
+ rp->argc = 0;
+ rp->public.f_config = NULL;
+ rp->public.fd_ipc = -1;
+ rp->public.logfile = NULL;
+ rp->public.pid = (pid_t)0;
+ rp->public.status = -1;
+ }
+
+ rp->isdefault = isdefault;
+
+ if (dir != NULL)
+ strcpy(tbuf, dir);
+ else
+ tbuf[0] = '\0';
+ strcat(tbuf, base);
+ p = &tbuf[strlen(tbuf)];
+ strcat(tbuf, ".");
+ strcat(tbuf, host);
+ strcat(tbuf, ".config");
+ c = '\0';
+ if (access(tbuf, F_OK) == 0) {
+ p[0] = 'a';
+ p[1] = '.';
+ p[2] = '\0';
+ strcat(p, host);
+ strcat(p, ".config");
+ while (p[0] <= 'z') {
+ if (access(tbuf, F_OK) != 0) {
+ c = p[0];
+ break;
+ }
+ p[0]++;
+ }
+ if (c == '\0') {
+ setoserror(EEXIST);
+ goto failed;
+ }
+ }
+
+ if ((rp->host = strdup(host)) == NULL)
+ goto failed;
+
+ if ((rp->public.f_config = fopen(tbuf, "w")) == NULL)
+ goto failed;
+
+ if ((rp->base = malloc(strlen(base)+1+strlen(host)+3)) == NULL)
+ goto failed;
+ strcpy(rp->base, base);
+ p = &rp->base[strlen(rp->base)];
+ if (c != '\0') {
+ *p++ = c;
+ }
+ *p++ = '.';
+ *p = '\0';
+ strcat(p, host);
+
+ if ((rp->logfile = malloc(strlen(rp->base)+5)) == NULL)
+ goto failed;
+ strcpy(rp->logfile, rp->base);
+ strcat(rp->logfile, ".log");
+
+ if ((rp->config = malloc(strlen(rp->base)+8)) == NULL)
+ goto failed;
+ strcpy(rp->config, rp->base);
+ strcat(rp->config, ".config");
+
+ /* construct full pathname */
+ rp->public.logfile = malloc(MAXPATHLEN);
+ if (rp->public.logfile != NULL) {
+ int sep = __pmPathSeparator();
+ if (dir != NULL && __pmAbsolutePath(dir))
+ strcpy(rp->public.logfile, dir);
+ else {
+ if (getcwd(rp->public.logfile, MAXPATHLEN) == NULL)
+ goto failed;
+ }
+
+ sts = strlen(rp->public.logfile);
+ if (rp->public.logfile[sts - 1] != sep) {
+ rp->public.logfile[sts] = sep;
+ rp->public.logfile[sts+1] = '\0';
+ }
+ strcat(rp->public.logfile, rp->logfile);
+ }
+ else {
+ /* malloc failure ... */
+ goto failed;
+ }
+
+ n_record++;
+ state = rp->state = PM_REC_HOST;
+ *rhp = &rp->public;
+
+ return 0;
+
+failed:
+ sts = -oserror();
+ if (rp->public.f_config != NULL) {
+ unlink(tbuf);
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ }
+ if (rp->host != NULL)
+ free(rp->host);
+ if (rp->base != NULL)
+ free(rp->base);
+ if (rp->logfile != NULL)
+ free(rp->logfile);
+ if (rp->config != NULL)
+ free(rp->config);
+ *rhp = NULL;
+ return sts;
+}
+
+#ifndef IS_MINGW /* not yet ported */
+/*
+ * simple control protocol between here and pmlogger
+ * - only write from app to pmlogger
+ * - no ack
+ * - commands are
+ * V<number>\n - version
+ * F<folio>\n - folio name
+ * P<name>\n - launcher's name
+ * R[<msg>]\n - launcher knows how to replay
+ * D[<msg>]\n - detach from launcher
+ * Q[<msg>]\n - quit pmlogger
+ * ?[<msg>]\n - display session status
+ */
+static int
+xmit_to_logger(int fd, char tag, const char *msg)
+{
+ int sts;
+#ifdef HAVE_SIGPIPE
+ SIG_PF user_onpipe;
+#endif
+
+ if (fd < 0)
+ return PM_ERR_IPC;
+
+#ifdef HAVE_SIGPIPE
+ user_onpipe = signal(SIGPIPE, SIG_IGN);
+#endif
+ sts = (int)write(fd, &tag, 1);
+ if (sts != 1)
+ goto fail;
+
+ if (msg != NULL) {
+ int len = (int)strlen(msg);
+ sts = (int)write(fd, msg, len);
+ if (sts != len)
+ goto fail;
+ }
+
+ sts = (int)write(fd, "\n", 1);
+ if (sts != 1)
+ goto fail;
+
+#ifdef HAVE_SIGPIPE
+ signal(SIGPIPE, user_onpipe);
+#endif
+ return 0;
+
+fail:
+ if (oserror() == EPIPE)
+ sts = PM_ERR_IPC;
+ else
+ sts = -oserror();
+#ifdef HAVE_SIGPIPE
+ signal(SIGPIPE, user_onpipe);
+#endif
+ return sts;
+}
+#endif
+
+int
+pmRecordControl(pmRecordHost *rhp, int request, const char *msg)
+{
+#ifndef IS_MINGW /* not yet ported */
+ pid_t pid;
+ record_t *rp;
+ int sts;
+ int ok;
+ int cmd;
+ int mypipe[2];
+ static int maxseenfd = -1;
+
+ /*
+ * harvest old and smelly pmlogger instances
+ */
+ while ((pid = waitpid(-1, &sts, WNOHANG)) > 0) {
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (pid == rp->public.pid) {
+ rp->public.status = sts;
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ break;
+ }
+ }
+ }
+
+ sts = 0;
+
+ switch (request) {
+
+ case PM_REC_ON:
+ if (state != PM_REC_HOST || rhp != NULL) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST)
+ continue;
+ if (rp->isdefault) {
+ fprintf(f_folio, "%-15s %-23s %s\n", "Archive:", rp->host, rp->base);
+ break;
+ }
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST || rp->isdefault)
+ continue;
+ fprintf(f_folio, "%-15s %-23s %s\n", "Archive:", rp->host, rp->base);
+ }
+
+ fflush(stderr);
+ fflush(stdout);
+ if (_replay)
+ fflush(f_replay);
+ fflush(f_folio);
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST)
+ continue;
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ if (pipe1(mypipe) < 0) {
+ sts = -oserror();
+ break;
+ }
+ if (mypipe[1] > maxseenfd)
+ maxseenfd = mypipe[1];
+ if (mypipe[0] > maxseenfd)
+ maxseenfd = mypipe[0];
+
+ if ((rp->public.pid = fork()) == 0) {
+ /* pmlogger */
+ char loggerpath[MAXPATHLEN];
+ char fdnum[4];
+ int fd;
+ int i;
+
+ close(mypipe[1]);
+ snprintf(fdnum, sizeof(fdnum), "%d", mypipe[0]);
+ if (dir != NULL) {
+ /* trim trailing separator */
+ dir[strlen(dir)-1] = '\0';
+ if (chdir(dir) < 0) {
+ /* not good! */
+ exit(1);
+ }
+ }
+ /*
+ * leave stdin, tie stdout and stderr together, leave
+ * the ipc fd and close all other fds
+ */
+ dup2(2, 1); /* stdout -> stderr */
+ for (fd = 3; fd <= maxseenfd; fd++) {
+ if (fd != mypipe[0])
+ close(fd);
+ }
+ /*
+ * have:
+ * argv[0] ... argv[argc-1] from PM_REC_SETARG
+ * want:
+ * argv[0] "pmlogger"
+ * argv[1] ... argv[argc] from PM_REC_SETARG
+ * argv[argc+1], argv[argc+2] "-x" fdnum
+ * argv[argc+3], argv[argc+4] "-h" host
+ * argv[argc+5], argv[argc+6] "-l" log
+ * argv[argc+7], argv[argc+8] "-c" config
+ * argv[argc+9] basename
+ * argv[argc+10] NULL
+ */
+ rp->argv = (char **)realloc(rp->argv, (rp->argc+11)*sizeof(rp->argv[0]));
+ if (rp->argv == NULL) {
+ __pmNoMem("pmRecordControl: argv[]", (rp->argc+11)*sizeof(rp->argv[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (i = rp->argc; i > 0; i--)
+ rp->argv[i] = rp->argv[i-1];
+ rp->argv[0] = "pmlogger";
+ i = rp->argc+1;
+ rp->argv[i++] = "-x";
+ rp->argv[i++] = fdnum;
+ rp->argv[i++] = "-h";
+ rp->argv[i++] = rp->host;
+ rp->argv[i++] = "-l";
+ rp->argv[i++] = rp->logfile;
+ rp->argv[i++] = "-c";
+ rp->argv[i++] = rp->config;
+ rp->argv[i++] = rp->base;
+ rp->argv[i++] = NULL;
+#if DESPERATE
+fprintf(stderr, "Launching pmlogger:");
+for (i = 0; i < rp->argc+11; i++) fprintf(stderr, " %s", rp->argv[i]);
+fputc('\n', stderr);
+#endif
+ snprintf(loggerpath, sizeof(loggerpath), "%s%cpmlogger",
+ pmGetConfig("PCP_BINADM_DIR"), __pmPathSeparator());
+ execv(loggerpath, rp->argv);
+
+ /* this is really bad! */
+ exit(1);
+ }
+ else if (rp->public.pid < (pid_t)0) {
+ sts = -oserror();
+ break;
+ }
+ else {
+ /* the application launching pmlogger */
+ close(mypipe[0]);
+ rp->public.fd_ipc = mypipe[1];
+ /* send the protocol version */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'V', "0");
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* send the folio name */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'F', _folio);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* send the my name */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'P', _creator);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* do we know how to replay? */
+ if (_replay) {
+ ok = xmit_to_logger(rp->public.fd_ipc, 'R', NULL);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ }
+ }
+ rp->state = PM_REC_ON;
+ }
+
+ if (sts < 0) {
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->public.fd_ipc >= 0) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ }
+ }
+ else {
+ if (_replay) {
+ fclose(f_replay);
+ f_replay = NULL;
+ }
+ fclose(f_folio);
+ f_folio = NULL;
+ state = PM_REC_ON;
+ n_alive = n_record;
+ }
+ break;
+
+ case PM_REC_STATUS:
+ cmd = '?';
+ goto broadcast;
+
+ case PM_REC_OFF:
+ cmd = 'Q';
+ goto broadcast;
+
+ case PM_REC_DETACH:
+ cmd = 'D';
+
+broadcast:
+ if (state != PM_REC_ON) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rhp != NULL && rhp != &rp->public)
+ continue;
+ if (rp->state != PM_REC_ON) {
+ if (rhp != NULL)
+ /* explicit pmlogger, should be "on" */
+ sts = -EINVAL;
+ continue;
+ }
+ if (rp->public.fd_ipc >= 0) {
+ ok = xmit_to_logger(rp->public.fd_ipc, cmd, msg);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ }
+ }
+ else
+ sts = PM_ERR_IPC;
+
+ if (cmd != '?') {
+ n_alive--;
+ rp->state = PM_REC_OFF;
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ }
+ }
+
+ if (n_alive <= 0)
+ state = PM_REC_OFF;
+
+ break;
+
+ case PM_REC_SETARG:
+ if (state != PM_REC_HOST) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rhp != NULL && rhp != &rp->public)
+ continue;
+ rp->argv = (char **)realloc(rp->argv, (rp->argc+1)*sizeof(rp->argv[0]));
+ if (rp->argv == NULL) {
+ sts = -oserror();
+ rp->argc = 0;
+ }
+ else {
+ rp->argv[rp->argc] = strdup(msg);
+ if (rp->argv[rp->argc] == NULL)
+ sts = -oserror();
+ else
+ rp->argc++;
+ }
+ }
+ break;
+
+ default:
+ sts = -EINVAL;
+ break;
+ }
+
+ return sts;
+#else
+ return -EINVAL;
+#endif
+}