/*
 * 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) {
	    __pmtimevalInc(tvp, &global.time);
	    return 1;
	}
	else if (global.time.tv_sec < 0) {
	    /*
	     * parser makes tv_sec < 0 and tv_usec >= 0 ...
	     * so cannot use __pmtimevalDec() here
	     */
	    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;
    int		seen_event = 0;

    /* 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 ( ; ; ) {
	    if (stsmeta == 0) {
		in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mdfp);
		stsmeta = nextmeta();
#if PCP_DEBUG
		if (pmDebug & DBG_TRACE_APPL0) {
		    if (stsmeta < 0)
			fprintf(stderr, "Metadata: read EOF @ offset=%ld\n", in_offset);
		    else if (stsmeta == TYPE_DESC)
			fprintf(stderr, "Metadata: read PMID %s @ offset=%ld\n", pmIDStr(ntoh_pmID(inarch.metarec[2])), in_offset);
		    else if (stsmeta == TYPE_INDOM)
			fprintf(stderr, "Metadata: read InDom %s @ offset=%ld\n", pmInDomStr(ntoh_pmInDom((unsigned int)inarch.metarec[4])), in_offset);
		}
#endif
	    }
	    if (stsmeta < 0) {
		break;
	    }

	    if (stsmeta == TYPE_DESC) {
		pmDesc	*dp = (pmDesc *)&inarch.metarec[2];
		pmID	pmid = ntoh_pmID(dp->pmid);
		int	type = ntohl(dp->type);
		/*
		 * We used to apply an optimization here to stop
		 * processing pmDesc metadata if the PMID of the
		 * next metadata did not match one of the PMIDs
		 * in the current pmResult.
		 *
		 * Unfortunately "event" records contain packed
		 * PMIDs for the event records and pseudo PMIDs
		 * for the metrics used to encode the event record
		 * parameters ... these just confuse the optimization
		 * and unpacking the event records does not help
		 * because the pmDesc for the pseudo PMIDs are 
		 * written in a batch when the first event record
		 * is output, even if they may not appear in a packed
		 * pmResult until later, or not at all.
		 *
		 * So if we've seen a metric of type PM_TYPE_EVENT
		 * or PM_TYPE_HIGHRES_EVENT, just keep processing the
		 * pmDesc metadata until it is exhausted, or we find
		 * a pmInDom metadata record with a timestamp after the
		 * current pmResult.
		 */

		if (!seen_event) {
		    if (type == PM_TYPE_EVENT || type == PM_TYPE_HIGHRES_EVENT)
			seen_event = 1;
		    else {
			/*
			 * 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];
		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);
}
