diff options
Diffstat (limited to 'src/pmlogrewrite/pmlogrewrite.c')
-rw-r--r-- | src/pmlogrewrite/pmlogrewrite.c | 1318 |
1 files changed, 1318 insertions, 0 deletions
diff --git a/src/pmlogrewrite/pmlogrewrite.c b/src/pmlogrewrite/pmlogrewrite.c new file mode 100644 index 0000000..625366d --- /dev/null +++ b/src/pmlogrewrite/pmlogrewrite.c @@ -0,0 +1,1318 @@ +/* + * pmlogrewrite - config-driven stream editor for PCP archives + * + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2011 Ken McDonell. All Rights Reserved. + * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <math.h> +#include <ctype.h> +#include <sys/stat.h> +#include <assert.h> +#include "pmapi.h" +#include "impl.h" +#include "logger.h" +#include <assert.h> + +global_t global; +indomspec_t *indom_root; +metricspec_t *metric_root; +int lineno; + +static pmLongOptions longopts[] = { + PMAPI_OPTIONS_HEADER("Options"), + PMOPT_DEBUG, + { "config", 1, 'c', "PATH", "file or directory to load rules from" }, + { "check", 0, 'C', 0, "parse config file(s) and quit (verbose warnings also)" }, + { "desperate", 0, 'd', 0, "desperate, save output archive even after error" }, + { "", 0, 'i', 0, "rewrite in place, input-archive will be over-written" }, + { "quick", 0, 'q', 0, "quick mode, no output if no change" }, + { "scale", 0, 's', 0, "do scale conversion" }, + { "verbose", 0, 'v', 0, "increased diagnostic verbosity" }, + { "warnings", 0, 'w', 0, "emit warnings [default is silence]" }, + PMAPI_OPTIONS_TEXT(""), + PMAPI_OPTIONS_TEXT("output-archive is required unless -i is specified"), + PMAPI_OPTIONS_END +}; + +static pmOptions opts = { + .short_options = "c:CdD:iqsvw?", + .long_options = longopts, + .short_usage = "[options] input-archive [output-archive]", +}; + +/* + * Global variables + */ +static int first_datarec = 1; /* first record flag */ +static char bak_base[MAXPATHLEN+1]; /* basename for backup with -i */ + +off_t new_log_offset; /* new log offset */ +off_t new_meta_offset; /* new meta offset */ + + +/* archive control stuff */ +inarch_t inarch; /* input archive control */ +outarch_t outarch; /* output archive control */ + +/* command line args */ +int nconf; /* number of config files */ +char **conf; /* list of config files */ +char *configfile; /* current config file */ +int Cflag; /* -C parse config and quit */ +int dflag; /* -d desperate */ +int iflag; /* -i in-place */ +int qflag; /* -q quick or quiet */ +int sflag; /* -s scale values */ +int vflag; /* -v verbosity */ +int wflag; /* -w emit warnings */ + +/* + * report that archive is corrupted + */ +static void +_report(FILE *fp) +{ + off_t here; + struct stat sbuf; + + here = lseek(fileno(fp), 0L, SEEK_CUR); + fprintf(stderr, "%s: Error occurred at byte offset %ld into a file of", + pmProgname, (long)here); + if (fstat(fileno(fp), &sbuf) < 0) + fprintf(stderr, ": stat: %s\n", osstrerror()); + else + fprintf(stderr, " %ld bytes.\n", (long)sbuf.st_size); + if (dflag) + fprintf(stderr, "The last record, and the remainder of this file will not be processed.\n"); + abandon(); + /*NOTREACHED*/ +} + +/* + * switch output volumes + */ +void +newvolume(int vol) +{ + FILE *newfp; + + if ((newfp = __pmLogNewFile(outarch.name, vol)) != NULL) { + fclose(outarch.logctl.l_mfp); + outarch.logctl.l_mfp = newfp; + outarch.logctl.l_label.ill_vol = outarch.logctl.l_curvol = vol; + __pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label); + fflush(outarch.logctl.l_mfp); + } + else { + fprintf(stderr, "%s: __pmLogNewFile(%s,%d) Error: %s\n", + pmProgname, outarch.name, vol, pmErrStr(-oserror())); + abandon(); + /*NOTREACHED*/ + } +} + +/* construct new archive label */ +static void +newlabel(void) +{ + __pmLogLabel *lp = &outarch.logctl.l_label; + + /* copy magic number, pid, host and timezone */ + lp->ill_magic = inarch.label.ll_magic; + lp->ill_pid = inarch.label.ll_pid; + if (global.flags & GLOBAL_CHANGE_HOSTNAME) + strncpy(lp->ill_hostname, global.hostname, PM_LOG_MAXHOSTLEN); + else + strncpy(lp->ill_hostname, inarch.label.ll_hostname, PM_LOG_MAXHOSTLEN); + lp->ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0'; + if (global.flags & GLOBAL_CHANGE_TZ) + strncpy(lp->ill_tz, global.tz, PM_TZ_MAXLEN); + else + strncpy(lp->ill_tz, inarch.label.ll_tz, PM_TZ_MAXLEN); + lp->ill_tz[PM_TZ_MAXLEN-1] = '\0'; +} + +/* + * write label records at the start of each physical file + */ +void +writelabel(int do_rewind) +{ + off_t old_offset; + + if (do_rewind) { + old_offset = ftell(outarch.logctl.l_tifp); + assert(old_offset >= 0); + rewind(outarch.logctl.l_tifp); + } + outarch.logctl.l_label.ill_vol = PM_LOG_VOL_TI; + __pmLogWriteLabel(outarch.logctl.l_tifp, &outarch.logctl.l_label); + if (do_rewind) + fseek(outarch.logctl.l_tifp, (long)old_offset, SEEK_SET); + + if (do_rewind) { + old_offset = ftell(outarch.logctl.l_mdfp); + assert(old_offset >= 0); + rewind(outarch.logctl.l_mdfp); + } + outarch.logctl.l_label.ill_vol = PM_LOG_VOL_META; + __pmLogWriteLabel(outarch.logctl.l_mdfp, &outarch.logctl.l_label); + if (do_rewind) + fseek(outarch.logctl.l_mdfp, (long)old_offset, SEEK_SET); + + if (do_rewind) { + old_offset = ftell(outarch.logctl.l_mfp); + assert(old_offset >= 0); + rewind(outarch.logctl.l_mfp); + } + outarch.logctl.l_label.ill_vol = 0; + __pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label); + if (do_rewind) + fseek(outarch.logctl.l_mfp, (long)old_offset, SEEK_SET); +} + +/* + * read next metadata record + */ +static int +nextmeta() +{ + int sts; + __pmLogCtl *lcp; + + lcp = inarch.ctxp->c_archctl->ac_log; + if ((sts = _pmLogGet(lcp, PM_LOG_VOL_META, &inarch.metarec)) < 0) { + if (sts != PM_ERR_EOL) { + fprintf(stderr, "%s: Error: _pmLogGet[meta %s]: %s\n", + pmProgname, inarch.name, pmErrStr(sts)); + _report(lcp->l_mdfp); + } + return -1; + } + + return ntohl(inarch.metarec[1]); +} + + +/* + * read next log record + * + * return status is + * 0 ok + * 1 ok, but volume switched + * PM_ERR_EOL end of file + * -1 fatal error + */ +static int +nextlog(void) +{ + int sts; + __pmLogCtl *lcp; + int old_vol; + + + lcp = inarch.ctxp->c_archctl->ac_log; + old_vol = inarch.ctxp->c_archctl->ac_log->l_curvol; + + if ((sts = __pmLogRead(lcp, PM_MODE_FORW, NULL, &inarch.rp, PMLOGREAD_NEXT)) < 0) { + if (sts != PM_ERR_EOL) { + fprintf(stderr, "%s: Error: __pmLogRead[log %s]: %s\n", + pmProgname, inarch.name, pmErrStr(sts)); + _report(lcp->l_mfp); + } + return -1; + } + + return old_vol == inarch.ctxp->c_archctl->ac_log->l_curvol ? 0 : 1; +} + +#ifdef IS_MINGW +#define S_ISLINK(mode) 0 /* no symlink support */ +#else +#ifndef S_ISLINK +#define S_ISLINK(mode) ((mode & S_IFMT) == S_IFLNK) +#endif +#endif + +/* + * parse command line arguments + */ +int +parseargs(int argc, char *argv[]) +{ + int c; + int sts; + int sep = __pmPathSeparator(); + struct stat sbuf; + + while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) { + switch (c) { + + case 'c': /* config file */ + if (stat(opts.optarg, &sbuf) < 0) { + pmprintf("%s: stat(%s) failed: %s\n", + pmProgname, opts.optarg, osstrerror()); + opts.errors++; + break; + } + if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) { + nconf++; + if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) != NULL) + conf[nconf-1] = opts.optarg; + } + else if (S_ISDIR(sbuf.st_mode)) { + DIR *dirp; + struct dirent *dp; + char path[MAXPATHLEN+1]; + + if ((dirp = opendir(opts.optarg)) == NULL) { + pmprintf("%s: opendir(%s) failed: %s\n", pmProgname, opts.optarg, osstrerror()); + opts.errors++; + } + else while ((dp = readdir(dirp)) != NULL) { + /* skip ., .. and "hidden" files */ + if (dp->d_name[0] == '.') continue; + snprintf(path, sizeof(path), "%s%c%s", opts.optarg, sep, dp->d_name); + if (stat(path, &sbuf) < 0) { + pmprintf("%s: %s: %s\n", pmProgname, path, osstrerror()); + opts.errors++; + } + else if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) { + nconf++; + if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) == NULL) + break; + if ((conf[nconf-1] = strdup(path)) == NULL) { + fprintf(stderr, "conf[%d] strdup(%s) failed: %s\n", nconf-1, path, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + } + } + if (dirp != NULL) + closedir(dirp); + } + else { + pmprintf("%s: Error: -c config %s is not a file or directory\n", pmProgname, opts.optarg); + opts.errors++; + } + if (nconf > 0 && conf == NULL) { + fprintf(stderr, "%s: Error: conf[%d] realloc(%d) failed: %s\n", pmProgname, nconf, (int)(nconf*sizeof(conf[0])), strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + break; + + case 'C': /* parse configs and quit */ + Cflag = 1; + vflag = 1; + wflag = 1; + break; + + case 'd': /* desperate */ + dflag = 1; + 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++; + } + else + pmDebug |= sts; + break; + + case 'i': /* in-place, over-write input archive */ + iflag = 1; + break; + + case 'q': /* quick or quiet */ + qflag = 1; + break; + + case 's': /* do scale conversions */ + sflag = 1; + break; + + case 'v': /* verbosity */ + vflag++; + break; + + case 'w': /* print warnings */ + wflag = 1; + break; + + case '?': + default: + opts.errors++; + break; + } + } + + if (opts.errors == 0) { + if ((iflag == 0 && opts.optind != argc-2) || + (iflag == 1 && opts.optind != argc-1)) + opts.errors++; + } + + return -opts.errors; +} + +static void +parseconfig(char *file) +{ + configfile = file; + if ((yyin = fopen(configfile, "r")) == NULL) { + fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n", + pmProgname, configfile, osstrerror()); + exit(1); + } + if (vflag > 1) + fprintf(stderr, "Start configfile: %s\n", file); + lineno = 1; + + if (yyparse() != 0) + exit(1); + + fclose(yyin); + yyin = NULL; + + return; +} + +char * +SemStr(int sem) +{ + static char buf[20]; + + if (sem == PM_SEM_COUNTER) snprintf(buf, sizeof(buf), "counter"); + else if (sem == PM_SEM_INSTANT) snprintf(buf, sizeof(buf), "instant"); + else if (sem == PM_SEM_DISCRETE) snprintf(buf, sizeof(buf), "discrete"); + else snprintf(buf, sizeof(buf), "bad sem? %d", sem); + + return buf; +} + +static void +reportconfig(void) +{ + indomspec_t *ip; + metricspec_t *mp; + int i; + int change = 0; + + printf("PCP Archive Log Rewrite Specifications Summary\n"); + change |= (global.flags != 0); + if (global.flags & GLOBAL_CHANGE_HOSTNAME) + printf("Hostname:\t%s -> %s\n", inarch.label.ll_hostname, global.hostname); + if (global.flags & GLOBAL_CHANGE_TZ) + printf("Timezone:\t%s -> %s\n", inarch.label.ll_tz, global.tz); + if (global.flags & GLOBAL_CHANGE_TIME) { + static struct tm *tmp; + char *sign = ""; + time_t time; + if (global.time.tv_sec < 0) { + time = (time_t)(-global.time.tv_sec); + sign = "-"; + } + else + time = (time_t)global.time.tv_sec; + tmp = gmtime(&time); + tmp->tm_hour += 24 * tmp->tm_yday; + if (tmp->tm_hour < 10) + printf("Delta:\t\t-> %s%02d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec); + else + printf("Delta:\t\t-> %s%d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec); + } + for (ip = indom_root; ip != NULL; ip = ip->i_next) { + int hdr_done = 0; + if (ip->new_indom != ip->old_indom) { + printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom)); + hdr_done = 1; + printf("pmInDom:\t-> %s\n", pmInDomStr(ip->new_indom)); + change |= 1; + } + for (i = 0; i < ip->numinst; i++) { + change |= (ip->inst_flags[i] != 0); + if (ip->inst_flags[i]) { + if (hdr_done == 0) { + printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom)); + hdr_done = 1; + } + printf("Instance:\t\[%d] \"%s\" -> ", ip->old_inst[i], ip->old_iname[i]); + if (ip->inst_flags[i] & INST_DELETE) + printf("DELETE\n"); + else { + if (ip->inst_flags[i] & INST_CHANGE_INST) + printf("[%d] ", ip->new_inst[i]); + else + printf("[%d] ", ip->old_inst[i]); + if (ip->inst_flags[i] & INST_CHANGE_INAME) + printf("\"%s\"\n", ip->new_iname[i]); + else + printf("\"%s\"\n", ip->old_iname[i]); + } + } + } + } + for (mp = metric_root; mp != NULL; mp = mp->m_next) { + if (mp->flags != 0 || mp->ip != NULL) { + change |= 1; + printf("\nMetric: %s (%s)\n", mp->old_name, pmIDStr(mp->old_desc.pmid)); + } + if (mp->flags & METRIC_CHANGE_PMID) { + printf("pmID:\t\t%s ->", pmIDStr(mp->old_desc.pmid)); + printf(" %s\n", pmIDStr(mp->new_desc.pmid)); + } + if (mp->flags & METRIC_CHANGE_NAME) + printf("Name:\t\t%s -> %s\n", mp->old_name, mp->new_name); + if (mp->flags & METRIC_CHANGE_TYPE) { + printf("Type:\t\t%s ->", pmTypeStr(mp->old_desc.type)); + printf(" %s\n", pmTypeStr(mp->new_desc.type)); + } + if (mp->flags & METRIC_CHANGE_INDOM) { + printf("InDom:\t\t%s ->", pmInDomStr(mp->old_desc.indom)); + printf(" %s\n", pmInDomStr(mp->new_desc.indom)); + if (mp->output != OUTPUT_ALL) { + printf("Output:\t\t"); + switch (mp->output) { + case OUTPUT_ONE: + if (mp->old_desc.indom != PM_INDOM_NULL) { + printf("value for instance"); + if (mp->one_inst != PM_IN_NULL) + printf(" %d", mp->one_inst); + if (mp->one_name != NULL) + printf(" \"%s\"", mp->one_name); + putchar('\n'); + } + else + printf("the only value (output instance %d)\n", mp->one_inst); + break; + case OUTPUT_FIRST: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("first value\n"); + else { + if (mp->one_inst != PM_IN_NULL) + printf("first and only value (output instance %d)\n", mp->one_inst); + else + printf("first and only value (output instance \"%s\")\n", mp->one_name); + } + break; + case OUTPUT_LAST: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("last value\n"); + else + printf("last and only value (output instance %d)\n", mp->one_inst); + break; + case OUTPUT_MIN: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("smallest value\n"); + else + printf("smallest and only value (output instance %d)\n", mp->one_inst); + break; + case OUTPUT_MAX: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("largest value\n"); + else + printf("largest and only value (output instance %d)\n", mp->one_inst); + break; + case OUTPUT_SUM: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("sum value (output instance %d)\n", mp->one_inst); + else + printf("sum and only value (output instance %d)\n", mp->one_inst); + break; + case OUTPUT_AVG: + if (mp->old_desc.indom != PM_INDOM_NULL) + printf("average value (output instance %d)\n", mp->one_inst); + else + printf("average and only value (output instance %d)\n", mp->one_inst); + break; + } + } + } + if (mp->ip != NULL) + printf("Inst Changes:\t<- InDom %s\n", pmInDomStr(mp->ip->old_indom)); + if (mp->flags & METRIC_CHANGE_SEM) { + printf("Semantics:\t%s ->", SemStr(mp->old_desc.sem)); + printf(" %s\n", SemStr(mp->new_desc.sem)); + } + if (mp->flags & METRIC_CHANGE_UNITS) { + printf("Units:\t\t%s ->", pmUnitsStr(&mp->old_desc.units)); + printf(" %s", pmUnitsStr(&mp->new_desc.units)); + if (mp->flags & METRIC_RESCALE) + printf(" (rescale)"); + putchar('\n'); + } + if (mp->flags & METRIC_DELETE) + printf("DELETE\n"); + } + if (change == 0) + printf("No changes\n"); +} + +static int +anychange(void) +{ + indomspec_t *ip; + metricspec_t *mp; + int i; + + if (global.flags != 0) + return 1; + for (ip = indom_root; ip != NULL; ip = ip->i_next) { + if (ip->new_indom != ip->old_indom) + return 1; + for (i = 0; i < ip->numinst; i++) { + if (ip->inst_flags[i]) + return 1; + } + } + for (mp = metric_root; mp != NULL; mp = mp->m_next) { + if (mp->flags != 0 || mp->ip != NULL) + return 1; + } + + return 0; +} + +static int +fixstamp(struct timeval *tvp) +{ + if (global.flags & GLOBAL_CHANGE_TIME) { + if (global.time.tv_sec > 0) { + tvp->tv_sec += global.time.tv_sec; + tvp->tv_usec += global.time.tv_usec; + if (tvp->tv_usec > 1000000) { + tvp->tv_sec++; + tvp->tv_usec -= 1000000; + } + return 1; + } + else if (global.time.tv_sec < 0) { + /* parser makes tv_sec < 0 and tv_usec >= 0 */ + tvp->tv_sec += global.time.tv_sec; + tvp->tv_usec -= global.time.tv_usec; + if (tvp->tv_usec < 0) { + tvp->tv_sec--; + tvp->tv_usec += 1000000; + } + return 1; + } + } + return 0; +} + +/* + * Link metricspec_t entries to corresponding indom_t entry if there + * are changes to instance identifiers or instance names (includes + * instance deletion) + */ +static void +link_entries(void) +{ + indomspec_t *ip; + metricspec_t *mp; + __pmHashCtl *hcp; + __pmHashNode *node; + int i; + int change; + + hcp = &inarch.ctxp->c_archctl->ac_log->l_hashpmid; + for (ip = indom_root; ip != NULL; ip = ip->i_next) { + change = 0; + for (i = 0; i < ip->numinst; i++) + change |= (ip->inst_flags[i] != 0); + if (change == 0 && ip->new_indom == ip->old_indom) + continue; + + for (node = __pmHashWalk(hcp, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) { + mp = start_metric((pmID)(node->key)); + if (mp->old_desc.indom == ip->old_indom) { + if (change) + mp->ip = ip; + if (ip->new_indom != ip->old_indom) { + if (mp->flags & METRIC_CHANGE_INDOM) { + /* indom already changed via metric clause */ + if (mp->new_desc.indom != ip->new_indom) { + char strbuf[80]; + snprintf(strbuf, sizeof(strbuf), "%s", pmInDomStr(mp->new_desc.indom)); + snprintf(mess, sizeof(mess), "Conflicting indom change for metric %s (%s from metric clause, %s from indom clause)", mp->old_name, strbuf, pmInDomStr(ip->new_indom)); + yysemantic(mess); + } + } + else { + mp->flags |= METRIC_CHANGE_INDOM; + mp->new_desc.indom = ip->new_indom; + } + } + } + } + } +} + +static void +check_indoms() +{ + /* + * For each metric, make sure the output instance domain will be in + * the output archive. + * Called after link_entries(), so if an input metric is associated + * with an instance domain that has any instance rewriting, we're OK. + * The case to be checked here is a rewritten metric with an indom + * clause and no associated indomspec_t (so no instance domain changes, + * but the new indom may not match any indom in the archive. + */ + metricspec_t *mp; + indomspec_t *ip; + __pmHashCtl *hcp; + __pmHashNode *node; + + + hcp = &inarch.ctxp->c_archctl->ac_log->l_hashindom; + + for (mp = metric_root; mp != NULL; mp = mp->m_next) { + if (mp->ip != NULL) + /* associated indom has instance changes, we're OK */ + continue; + if ((mp->flags & METRIC_CHANGE_INDOM) && mp->new_desc.indom != PM_INDOM_NULL) { + for (node = __pmHashWalk(hcp, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) { + /* + * if this indom has an indomspec_t, check that, else + * this indom will go to the archive without change + */ + for (ip = indom_root; ip != NULL; ip = ip->i_next) { + if (ip->old_indom == mp->old_desc.indom) + break; + } + if (ip == NULL) { + if ((pmInDom)(node->key) == mp->new_desc.indom) + /* we're OK */ + break; + } + else { + if (ip->new_indom != ip->old_indom && + ip->new_indom == mp->new_desc.indom) + /* we're OK */ + break; + } + } + if (node == NULL) { + snprintf(mess, sizeof(mess), "New indom (%s) for metric %s is not in the output archive", pmInDomStr(mp->new_desc.indom), mp->old_name); + yysemantic(mess); + } + } + } + + /* + * For each modified instance domain, make sure instances are + * still unique and instance names are unique to the first + * space. + */ + for (ip = indom_root; ip != NULL; ip = ip->i_next) { + int i; + for (i = 0; i < ip->numinst; i++) { + int insti; + char *namei; + int j; + if (ip->inst_flags[i] & INST_CHANGE_INST) + insti = ip->new_inst[i]; + else + insti = ip->old_inst[i]; + if (ip->inst_flags[i] & INST_CHANGE_INAME) + namei = ip->new_iname[i]; + else + namei = ip->old_iname[i]; + for (j = 0; j < ip->numinst; j++) { + int instj; + char *namej; + if (i == j) + continue; + if (ip->inst_flags[j] & INST_CHANGE_INST) + instj = ip->new_inst[j]; + else + instj = ip->old_inst[j]; + if (ip->inst_flags[j] & INST_CHANGE_INAME) + namej = ip->new_iname[j]; + else + namej = ip->old_iname[j]; + if (insti == instj) { + snprintf(mess, sizeof(mess), "Duplicate instance id %d (\"%s\" and \"%s\") for indom %s", insti, namei, namej, pmInDomStr(ip->old_indom)); + yysemantic(mess); + } + if (inst_name_eq(namei, namej) > 0) { + snprintf(mess, sizeof(mess), "Duplicate instance name \"%s\" (%d) and \"%s\" (%d) for indom %s", namei, insti, namej, instj, pmInDomStr(ip->old_indom)); + yysemantic(mess); + } + } + } + } +} + +static void +check_output() +{ + /* + * For each metric, if there is an INDOM clause, perform some + * additional semantic checks and perhaps a name -> instance id + * mapping. + * + * Note instance renumbering happens _after_ value selction from + * the INDOM -> ,,,, OUTPUT clause, so all references to + * instance names and instance ids are relative to the + * "old" set. + */ + metricspec_t *mp; + indomspec_t *ip; + + for (mp = metric_root; mp != NULL; mp = mp->m_next) { + if ((mp->flags & METRIC_CHANGE_INDOM)) { + if (mp->output == OUTPUT_ONE || mp->output == OUTPUT_FIRST) { + /* + * cases here are + * INAME "name" + * => one_name == "name" and one_inst == PM_IN_NULL + * INST id + * => one_name == NULL and one_inst = id + */ + if (mp->old_desc.indom != PM_INDOM_NULL && mp->output == OUTPUT_ONE) { + /* + * old metric is not singular, so one_name and one_inst + * are used to pick the value + * also map one_name -> one_inst + */ + int i; + ip = start_indom(mp->old_desc.indom); + for (i = 0; i < ip->numinst; i++) { + if (mp->one_name != NULL) { + if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) { + mp->one_name = NULL; + mp->one_inst = ip->old_inst[i]; + break; + } + } + else if (ip->old_inst[i] == mp->one_inst) + break; + } + if (i == ip->numinst) { + if (wflag) { + if (mp->one_name != NULL) + snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in old indom %s", mp->one_name, pmInDomStr(mp->old_desc.indom)); + else + snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in old indom %s", mp->one_inst, pmInDomStr(mp->old_desc.indom)); + yywarn(mess); + } + } + } + if (mp->new_desc.indom != PM_INDOM_NULL) { + /* + * new metric is not singular, so one_inst should be + * found in the new instance domain ... ignore one_name + * other than to map one_name -> one_inst if one_inst + * is not already known + */ + int i; + ip = start_indom(mp->new_desc.indom); + for (i = 0; i < ip->numinst; i++) { + if (mp->one_name != NULL) { + if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) { + mp->one_name = NULL; + mp->one_inst = ip->old_inst[i]; + break; + } + } + else if (ip->old_inst[i] == mp->one_inst) + break; + } + if (i == ip->numinst) { + if (wflag) { + if (mp->one_name != NULL) + snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in new indom %s", mp->one_name, pmInDomStr(mp->new_desc.indom)); + else + snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in new indom %s", mp->one_inst, pmInDomStr(mp->new_desc.indom)); + yywarn(mess); + } + } + /* + * use default rule (id 0) if INAME not found and + * and instance id is needed for output value + */ + if (mp->old_desc.indom == PM_INDOM_NULL && mp->one_inst == PM_IN_NULL) + mp->one_inst = 0; + } + } + } + } +} + +int +main(int argc, char **argv) +{ + int sts; + int stslog; /* sts from nextlog() */ + int stsmeta = 0; /* sts from nextmeta() */ + int i; + int ti_idx; /* next slot for input temporal index */ + int dir_fd = -1; /* poinless initialization to humour gcc */ + int needti = 0; + int doneti = 0; + __pmTimeval tstamp = { 0 }; /* for last log record */ + off_t old_log_offset = 0; /* log offset before last log record */ + off_t old_meta_offset; + + /* process cmd line args */ + if (parseargs(argc, argv) < 0) { + pmUsageMessage(&opts); + exit(1); + } + + /* input archive */ + if (iflag == 0) + inarch.name = argv[argc-2]; + else + inarch.name = argv[argc-1]; + inarch.logrec = inarch.metarec = NULL; + inarch.mark = 0; + inarch.rp = NULL; + + if ((inarch.ctx = pmNewContext(PM_CONTEXT_ARCHIVE, inarch.name)) < 0) { + fprintf(stderr, "%s: Error: cannot open archive \"%s\": %s\n", + pmProgname, inarch.name, pmErrStr(inarch.ctx)); + exit(1); + } + inarch.ctxp = __pmHandleToPtr(inarch.ctx); + assert(inarch.ctxp != NULL); + + if ((sts = pmGetArchiveLabel(&inarch.label)) < 0) { + fprintf(stderr, "%s: Error: cannot get archive label record (%s): %s\n", + pmProgname, inarch.name, pmErrStr(sts)); + exit(1); + } + + if ((inarch.label.ll_magic & 0xff) != PM_LOG_VERS02) { + fprintf(stderr,"%s: Error: illegal version number %d in archive (%s)\n", + pmProgname, inarch.label.ll_magic & 0xff, inarch.name); + exit(1); + } + + /* output archive */ + if (iflag && Cflag == 0) { + /* + * -i (in place) method outline + * + * + create one temporary base filename in the same directory is + * the input archive, keep a copy of this name this accessed + * via outarch.name + * + create a second (and different) temporary base file name + * in the same directory, keep this name in bak_base[] + * + close the temporary file descriptors and unlink the basename + * files + * + create the output as per normal in outarch.name + * + fsync() all the output files and the container directory + * + rename the _input_ archive files using the _second_ temporary + * basename + * + rename the output archive files to the basename of the input + * archive ... if this step fails for any reason, restore the + * original input files + * + unlink all the (old) input archive files + */ + char path[MAXPATHLEN+1]; + char dname[MAXPATHLEN+1]; + mode_t cur_umask; + int tmp_f1; /* fd for first temp basename */ + int tmp_f2; /* fd for second temp basename */ + +#if HAVE_MKSTEMP + strncpy(path, argv[argc-1], sizeof(path)); + path[sizeof(path)-1] = '\0'; + strncpy(dname, dirname(path), sizeof(dname)); + dname[sizeof(dname)-1] = '\0'; + if ((dir_fd = open(dname, O_RDONLY)) < 0) { + fprintf(stderr, "%s: Error: cannot open directory \"%s\" for reading: %s\n", pmProgname, dname, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + sprintf(path, "%s%cXXXXXX", dname, __pmPathSeparator()); + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + tmp_f1 = mkstemp(path); + umask(cur_umask); + outarch.name = strdup(path); + if (outarch.name == NULL) { + fprintf(stderr, "%s: Error: temp file strdup(%s) failed: %s\n", pmProgname, path, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + sprintf(bak_base, "%s%cXXXXXX", dname, __pmPathSeparator()); + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + tmp_f2 = mkstemp(bak_base); + umask(cur_umask); +#else + char fname[MAXPATHLEN+1]; + char *s; + + strncpy(path, argv[argc-1], sizeof(path)); + path[sizeof(path)-1] = '\0'; + strncpy(fname, basename(path), sizeof(fname)); + fname[sizeof(fname)-1] = '\0'; + strncpy(dname, dirname(path), sizeof(dname)); + dname[sizeof(dname)-1] = '\0'; + + if ((s = tempnam(dname, fname)) == NULL) { + fprintf(stderr, "%s: Error: first tempnam() failed: %s\n", pmProgname, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + else { + outarch.name = strdup(s); + if (outarch.name == NULL) { + fprintf(stderr, "%s: Error: temp file strdup(%s) failed: %s\n", pmProgname, s, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + tmp_f1 = open(outarch.name, O_WRONLY|O_CREAT|O_EXCL, 0600); + umask(cur_umask); + } + if ((s = tempnam(dname, fname)) == NULL) { + fprintf(stderr, "%s: Error: second tempnam() failed: %s\n", pmProgname, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + else { + strcpy(bak_base, s); + cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO); + tmp_f2 = open(bak_base, O_WRONLY|O_CREAT|O_EXCL, 0600); + umask(cur_umask); + } +#endif + if (tmp_f1 < 0) { + fprintf(stderr, "%s: Error: create first temp (%s) failed: %s\n", pmProgname, outarch.name, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + if (tmp_f2 < 0) { + fprintf(stderr, "%s: Error: create second temp (%s) failed: %s\n", pmProgname, bak_base, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + close(tmp_f1); + close(tmp_f2); + unlink(outarch.name); + unlink(bak_base); + } + else + outarch.name = argv[argc-1]; + + /* + * process config file(s) + */ + for (i = 0; i < nconf; i++) { + parseconfig(conf[i]); + } + + /* + * cross-specification dependencies and semantic checks once all + * config files have been processed + */ + link_entries(); + check_indoms(); + check_output(); + + if (vflag) + reportconfig(); + + if (Cflag) + exit(0); + + if (qflag && anychange() == 0) + exit(0); + + /* create output log - must be done before writing label */ + if ((sts = __pmLogCreate("", outarch.name, PM_LOG_VERS02, &outarch.logctl)) < 0) { + fprintf(stderr, "%s: Error: __pmLogCreate(%s): %s\n", + pmProgname, outarch.name, pmErrStr(sts)); + abandon(); + /*NOTREACHED*/ + } + + /* initialize and write label records */ + newlabel(); + outarch.logctl.l_state = PM_LOG_STATE_INIT; + writelabel(0); + + first_datarec = 1; + ti_idx = 0; + + /* + * loop + * - get next log record + * - write out new/changed meta data required by this log record + * - write out log + * - do ti update if necessary + */ + while (1) { + static long in_offset; /* for -Dappl0 */ + + fflush(outarch.logctl.l_mdfp); + old_meta_offset = ftell(outarch.logctl.l_mdfp); + assert(old_meta_offset >= 0); + + in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mfp); + stslog = nextlog(); + if (stslog < 0) { +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Log: read EOF @ offset=%ld\n", in_offset); +#endif + break; + } + if (stslog == 1) { + /* volume change */ + if (inarch.ctxp->c_archctl->ac_log->l_curvol >= outarch.logctl.l_curvol+1) + /* track input volume numbering */ + newvolume(inarch.ctxp->c_archctl->ac_log->l_curvol); + else + /* + * output archive volume number is ahead, probably because + * rewriting has forced an earlier volume change + */ + newvolume(outarch.logctl.l_curvol+1); + } +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + struct timeval stamp; + fprintf(stderr, "Log: read "); + stamp.tv_sec = inarch.rp->timestamp.tv_sec; + stamp.tv_usec = inarch.rp->timestamp.tv_usec; + __pmPrintStamp(stderr, &stamp); + fprintf(stderr, " numpmid=%d @ offset=%ld\n", inarch.rp->numpmid, in_offset); + } +#endif + + if (ti_idx < inarch.ctxp->c_archctl->ac_log->l_numti) { + __pmLogTI *tip = &inarch.ctxp->c_archctl->ac_log->l_ti[ti_idx]; + if (tip->ti_stamp.tv_sec == inarch.rp->timestamp.tv_sec && + tip->ti_stamp.tv_usec == inarch.rp->timestamp.tv_usec) { + /* + * timestamp on input pmResult matches next temporal index + * entry for input archive ... make sure matching temporal + * index entry added to output archive + */ + needti = 1; + ti_idx++; + } + } + + /* + * optionally rewrite timestamp in pmResult for global time + * adjustment ... flows to output pmResult, indom entries in + * metadata, temporal index entries and label records + * */ + fixstamp(&inarch.rp->timestamp); + + /* + * process metadata until find an indom record with timestamp + * after the current log record, or a metric record for a pmid + * that is not in the current log record + */ + for ( ; ; ) { + pmID pmid; /* pmid for TYPE_DESC */ + pmInDom indom; /* indom for TYPE_INDOM */ + + if (stsmeta == 0) { + in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mdfp); + stsmeta = nextmeta(); +#if PCP_DEBUG + if (stsmeta < 0 && pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Metadata: read EOF @ offset=%ld\n", in_offset); +#endif + } + if (stsmeta < 0) { + break; + } + if (stsmeta == TYPE_DESC) { + int i; + pmid = ntoh_pmID(inarch.metarec[2]); +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Metadata: read PMID %s @ offset=%ld\n", pmIDStr(pmid), in_offset); +#endif + /* + * if pmid not in next pmResult, we're done ... + */ + for (i = 0; i < inarch.rp->numpmid; i++) { + if (pmid == inarch.rp->vset[i]->pmid) + break; + } + if (i == inarch.rp->numpmid) + break; + /* + * rewrite if needed, delete if needed else output + */ + do_desc(); + } + else if (stsmeta == TYPE_INDOM) { + struct timeval stamp; + __pmTimeval *tvp = (__pmTimeval *)&inarch.metarec[2]; + indom = ntoh_pmInDom((unsigned int)inarch.metarec[4]); +#if PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "Metadata: read InDom %s @ offset=%ld\n", pmInDomStr(indom), in_offset); +#endif + stamp.tv_sec = ntohl(tvp->tv_sec); + stamp.tv_usec = ntohl(tvp->tv_usec); + if (fixstamp(&stamp)) { + /* global time adjustment specified */ + tvp->tv_sec = htonl(stamp.tv_sec); + tvp->tv_usec = htonl(stamp.tv_usec); + } + /* if time of indom > next pmResult stop processing metadata */ + if (stamp.tv_sec > inarch.rp->timestamp.tv_sec) + break; + if (stamp.tv_sec == inarch.rp->timestamp.tv_sec && + stamp.tv_usec > inarch.rp->timestamp.tv_usec) + break; + needti = 1; + do_indom(); + } + else { + fprintf(stderr, "%s: Error: unrecognised meta data type: %d\n", + pmProgname, stsmeta); + abandon(); + /*NOTREACHED*/ + } + free(inarch.metarec); + stsmeta = 0; + } + + if (first_datarec) { + first_datarec = 0; + /* any global time adjustment done after nextlog() above */ + outarch.logctl.l_label.ill_start.tv_sec = inarch.rp->timestamp.tv_sec; + outarch.logctl.l_label.ill_start.tv_usec = inarch.rp->timestamp.tv_usec; + /* need to fix start-time in label records */ + writelabel(1); + needti = 1; + } + + tstamp.tv_sec = inarch.rp->timestamp.tv_sec; + tstamp.tv_usec = inarch.rp->timestamp.tv_usec; + + if (needti) { + fflush(outarch.logctl.l_mdfp); + fflush(outarch.logctl.l_mfp); + new_meta_offset = ftell(outarch.logctl.l_mdfp); + assert(new_meta_offset >= 0); + fseek(outarch.logctl.l_mdfp, (long)old_meta_offset, SEEK_SET); + __pmLogPutIndex(&outarch.logctl, &tstamp); + fseek(outarch.logctl.l_mdfp, (long)new_meta_offset, SEEK_SET); + needti = 0; + doneti = 1; + } + else + doneti = 0; + + old_log_offset = ftell(outarch.logctl.l_mfp); + assert(old_log_offset >= 0); + + if (inarch.rp->numpmid == 0) + /* mark record, need index entry @ next log record */ + needti = 1; + + do_result(); + } + + if (!doneti) { + /* Final temporal index entry */ + fflush(outarch.logctl.l_mfp); + fseek(outarch.logctl.l_mfp, (long)old_log_offset, SEEK_SET); + __pmLogPutIndex(&outarch.logctl, &tstamp); + } + + if (iflag) { + /* + * fsync() to make sure new archive is safe before we start + * renaming ... + */ + if (fsync(fileno(outarch.logctl.l_mdfp)) < 0) { + fprintf(stderr, "%s: Error: fsync(%d) failed for output metadata file: %s\n", + pmProgname, fileno(outarch.logctl.l_mdfp), strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + if (fsync(fileno(outarch.logctl.l_mfp)) < 0) { + fprintf(stderr, "%s: Error: fsync(%d) failed for output data file: %s\n", + pmProgname, fileno(outarch.logctl.l_mfp), strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + if (fsync(fileno(outarch.logctl.l_tifp)) < 0) { + fprintf(stderr, "%s: Error: fsync(%d) failed for output index file: %s\n", + pmProgname, fileno(outarch.logctl.l_tifp), strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + if (fsync(dir_fd) < 0) { + fprintf(stderr, "%s: Error: fsync(%d) failed for output directory: %s\n", + pmProgname, dir_fd, strerror(errno)); + abandon(); + /*NOTREACHED*/ + } + close(dir_fd); + if (_pmLogRename(inarch.name, bak_base) < 0) { + abandon(); + /*NOTREACHED*/ + } + if (_pmLogRename(outarch.name, inarch.name) < 0) { + abandon(); + /*NOTREACHED*/ + } + _pmLogRemove(bak_base); + } + + exit(0); +} + +void +abandon(void) +{ + char path[MAXNAMELEN+1]; + if (dflag == 0) { + if (Cflag == 0 && iflag == 0) + fprintf(stderr, "Archive \"%s\" not created.\n", outarch.name); + + _pmLogRemove(outarch.name); + if (iflag) + _pmLogRename(bak_base, inarch.name); + while (outarch.logctl.l_curvol >= 0) { + snprintf(path, sizeof(path), "%s.%d", outarch.name, outarch.logctl.l_curvol); + unlink(path); + outarch.logctl.l_curvol--; + } + snprintf(path, sizeof(path), "%s.meta", outarch.name); + unlink(path); + snprintf(path, sizeof(path), "%s.index", outarch.name); + unlink(path); + } + else + fprintf(stderr, "Archive \"%s\" creation truncated.\n", outarch.name); + + exit(1); +} |