diff options
Diffstat (limited to 'src/libpcp_gui/src/record.c')
-rw-r--r-- | src/libpcp_gui/src/record.c | 758 |
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 +} |